+1

Sử dụng prerender.io trong AngularJS SEO

AngularJS là một framework tốt để xây dựng trang web và ứng dụng. Các tính năng tích hợp sẵn như routing, data-binding và các directives cho phép AngularJS hoàn toàn xử lý được nhiều kiểu ứng dụng front-end. Trong khi đó, nhược điểm của AngularJS cho đến bây giờ là dùng với Search Engine Optimization(SEO). Trong bài viết này sẽ giới thiệu về cách làm cho trang web hoặc ứng dụng AngularJS có thể thu thập bởi Google.

Vấn đề

Search engines crawlers (bots) ban đầu thiết kế để thu thập dữ liệu nội dung HTML của các trang web.Khi trang web phát triển các công nghệ cung cấp các trang web và Javascript trở thành ngôn ngữ thực tế của web. AJAX cho phép hoạt động không đồng bộ trên web. AngularJS hoàn toàn là mô hình không đồng bộ và đây là vấn đề khi bots của Google thu thập thông tin. Nếu sử dụng AngularJS đầy đủ để build web hoặc ứng dụng chắc chắn chỉ có một trang HTML được cung cấp các partial views không đồng bộ. Routing và logic của ứng dụng sẽ hoàn thiện ở phía client cho dù thực hiện chuyển trang hoặc thực hiện CRUD khác cũng chỉ thực hiện trên một trang mà thôi.

Giải pháp

Google có cách để đánh index các ứng dụng AJAX vậy ứng dụng AngularJS có thể thu thập được thông tin, đánh index và hiển thị trong kết quả search như các trang web khác. Có vài bước phải thực hiện các phương thức và hoàn toàn hỗ trợ bởi Google. Trong bài viết này sẽ build một ứng dụng demo để cho Google bot và các bot khác như Bing bot, .... có thể thu thập thông tin được. Bằng cách này sẽ giải quyết được vấn đề trên và có kết quả search đúng như mong muốn.

Tại sao có hiệu quả

  • Khi search engine crawler chạy qua ứng dụng sẽ gặp <meta name="fragment" content="!"> nó sẽ thêm tag ?_escaped_fragment_= trong URL
  • Máy server sẽ chặn yêu cầu này và gửi nó tới middleware sẽ xử lý yêu cầu thu thập thông tin đặc biệt này.
  • Prerender.io sẽ check xem nếu trang đang yêu cầu đã tồn tại (cached page) nếu có thì chuyển trang đó cho crawler, nếu chưa thì prerender sẽ thực hiện gọi PhantomJS để render toàn bộ nội dung của trang và hiển thị nó cho crawler.
  • Các trang không có cached mà phải gọi đến PhantomJS sẽ mất thời gian render lâu một chút dẫn đến kéo dài thời gian response vậy thường xuyên nên cache các trang.
  • Ngoài ra còn có cách khác tương tự
    • Tự tạo service để render và phục vụ snapshots cho search engines
    • Sử các service có sẵn khác như BromBone, Seo.js hoặc SEO4AJAX

Prerender.io

Prerender.io là một service tương thích trên nhiều nền tảng khác nhau như Node, PHP và Ruby. Service này là open-source nhưng họ cung cấp một host nếu không muốn phải trải qua những rắc rối khi thiết lập server riêng của minh cho SEO.

Setup package.json cho Node

Chúng ta sẽ build một ứng dụng Node/AngularJS đơn gian mà có nhiều trang với nội dung thay đổi theo từng trang. Chúng ta sẽ dùng Node.js là server backend với Express. Theo file package.json dưới sẽ biết các dependencies cần dùng để build ứng dụng.

{
  "name": "Angular-SEO-Prerender",
  "description": "...",
  "version": "1.0.0",
  "main": "server.js",
  "author": "limkimhuor",
  "private": "true",
  "scripts": {
    "build": "rimraf dist && webpack --bail --progress --profile",
    "server": "webpack --bail --progress --profile && node server",
    "test": "karma start",
    "test-watch": "karma start --auto-watch --no-single-run",
    "start": "npm run server"
  },
  "dependencies": {
    "angular": "^1.6.0"
  },
  "devDependencies": {
    "angular-mocks": "^1.5.0",
    "angular-ui-router": "^1.0.3",
    "autoprefixer": "^6.0.3",
    "babel-core": "^6.2.1",
    "babel-loader": "^6.2.0",
    "babel-preset-es2015": "^6.1.18",
    "bootstrap": "^3.3.7",
    "copy-webpack-plugin": "4.0.1",
    "css-loader": "0.26.1",
    "extract-text-webpack-plugin": "2.0.0-beta.5",
    "file-loader": "^0.9.0",
    "html-webpack-plugin": "^2.7.1",
    "istanbul-instrumenter-loader": "^1.0.0",
    "node-libs-browser": "2.0.0",
    "node-sass": "^4.5.3",
    "null-loader": "^0.1.1",
    "phantomjs-prebuilt": "^2.1.4",
    "postcss-loader": "1.2.2",
    "prerender-node": "^2.7.2",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.5.1",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.13.0",
    "webpack": "2.2.0",
    "webpack-dev-server": "2.2.0"
  }
}

Ở đây chúng ta đã có package.json và để tiếp tục cài đặt các dependencies bằng nhập câu lệnh npm install.

Setup server.js cho Node

Trong file server.js sẽ yêu cầu Prerender service và kết nối bằng prerender token hoặc prerender local server. Trong bài viết này chỉ dùng local prerender server bởi vì chưa có domain thực tế để test với Prerender.io service được. Prerender local server có thể tải với link này local prerender server.

var express = require("express");
var app = module.exports = express();
var path = require("path");
const port = 8080;
var prerenderio = require("prerender-node");

prerenderio.set( "prerenderServiceUrl", "http://localhost:3000/");
prerenderio.set('protocol', 'http');
prerenderio.set('host', 'localhost:8080');

app.use("/", express.static(path.join(__dirname, "dist")));
app.use(prerenderio);

app.get("*", function(req, res) {
  res.sendFile(path.resolve(__dirname, "dist", "public.html"));
});

app.listen(port);
console.log("Web listening port " + port+ "\nhttp://localhost:" + port)
module.exports = app;

Trang chủ public.html

Chúng ta sẽ tạo một trang chủ như thông thường nhưng có một thay đổi là thêm meta tag <meta name="fragment" content="!"> vào trong <head> của trang. Meta tag này sẽ cho engine crawlers biết rằng trang web hoặc ứng dụng này chứa nội dụng JavaScript động mà cần để thu thập thông tin.

Ngoài ra, nếu trang web không được cache đúng hoặc bị mất nội dung thì có thể thêm đoạn script này: window.prerenderReady = false; sẽ báo cho Prerender service đợi đến tất cả nội dung của trang được render xong trước khi bắt đầu lấy snapshot. Sau đó cần phải xét window.prerenderReady = true; sau khi nội dung tải về xong. Với script này thì không cần thiết lắm như là sự lựa chọn nếu cần hoặc gặp lỗi cache không đủ nội dung.

<!DOCTYPE html>
<html lang="en" ng-app="prerenderApp">
  <head>
    <meta charset="UTF-8">
    <meta name="fragment" content="!">
    <base href="/">
    <title>Angular SEO Prerender | {{seo.pageTitle}}</title>
    <meta name="description" content="{{seo.pageDescripton}}">
  </head>
  <body style="margin-top: 60px;">
    <div class="container">
      <div class="bs-example bs-navbar-top-example">
        <nav class="navbar navbar-default navbar-fixed-top">
          <div class="navbar-header">
            <a class="navbar-brand" href="/">Angular SEO Prerender</a>
          </div>

          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
            <li><a href="/about">About</a></li>
          </ul>
        </nav>
      </div>

      <ui-view></ui-view>
    </div>
  </body>
</html>

Setup Angular app.js

Trong app.config.js cần thêm route config $locationProvider.hashPrefix('!'); để biết ! là prefix của URL. Nếu sử dụng html5Mode sẽ không thấy sự khác biệt mặt khác sẽ thấy url sẽ thành http://localhost:3000/#!/home so với url chuẩn http://localhost:3000/#/home. #! trong url rất quan trọng vì nó sẽ cảnh báo tới crawler rằng ứng dụng có nội dụng AJAX.

routing.$inject = ["$stateProvider", "$urlRouterProvider", "$locationProvider"];

export default function routing($stateProvider, $urlRouterProvider, $locationProvider) {
  $locationProvider.html5Mode(true);
  $locationProvider.hashPrefix("!");
  $urlRouterProvider.otherwise("/");
}

Từ đó app.config.js sẽ được dùng trong app.js như sau

import "bootstrap/dist/css/bootstrap.css";

import home from "./features/home";
import about from "./features/about";
import routing from "./app.config";

import "./styles/app.scss";

const MODULE_NAME = "prerenderApp";

angular.module(MODULE_NAME, ["ui.router", "home", "about"])
  .config(routing);

export default MODULE_NAME;

Trong trường hợp sử dụng $locationProvider.html5Mode(true); phải có base tag <base href="/"> trong file index.html hoặc trang html chủ vậy url sẽ thành http://localhost:3000/home, nếu không có base tag sẽ gặp lỗi Uncaught Error: [$location:nobase] $location in HTML5 mode requires a <base> tag to be present!.

Kết quả sau khi dùng prerender service

Khi một crawler đi qua trang http://localhost:3000/#!/about URL sẽ biến thành http://localhost:3000/?<em>escaped_fragment</em>=/about khi đó prerender middleware sẽ thấy url kiểu này và nó sẽ gọi đến prerender service. Tương tự như vậy khi sử dụng html5Mode thì khi crawler đi qua trang http://localhost:3000/about URL sẽ biến thành http://localhost:3000/about?<em>escaped_fragment</em>=.

Prerender service sẽ kiểm tra xem nếu nó đã có một snapshot hoặc đã render page cho trang đó thì nó sẽ gửi nội dung cho crawler nếu chưa nó sẽ thực hiện render một snapshot và gửi html đã render cho crawler để đánh đúng index. Dưới đây là kết quả chạy trong Google Webmaster Tool qua ngrok, trước đó phải add property là đường chạy bằng ngrok rồi nhập link cần render.

Note

  • Cung cập cho các crawler html prerender thay vì JavaScript.
  • Nếu bị dình với # trong url, phải chắc chắn set hashPrefix(!) vậy url sẽ có dạng #!.
  • Nếu như có nhiều trang và nội dung thì nên có sitemap.xml và robots.txt
  • Google chỉ thu thập một số trang trong một ngày phụ thuộc vào PageRank. Khi cung cấp thêm sitemap.xml sẽ tăng được độ ưu tiên để đánh index.
  • Khi kiểm tra các trang của AngularJS render thế nào trong Google Webmaster Tools, phải thêm #! hoặc ?_escaped_fragment_= bởi vì lúc đó nó không làm việc giống như crawler.
  • Tại sao không đặt tên là index.html mà là public.html bởi vì route / sẽ không gọi index.html file trong thư mục public. link tham khảo

Kết luận

Hy vọng bài này có thể giúp việc SEO cho ứng dụng AngularJS càng dễ dàng hơn cũng như biết cách để khắc phục lỗi khi gặp phải.

Tham khảo


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.