SEO for AngularJS apps made easy. Based on PhantomJS and yearofmoo's article.
This version has an updated with a detailed Nginx file that passes request Host information to phantomjs.
So only one instance of phantomjs is required to server multiple static sites.
Note: it can also be used to serve a single website if a URL is passed in start parameters
You will need PhantomJS to make this work, as it will render the application to HTML.
The solution is made of 3 parts:
- small modification of your static HTML file
- an AngularJS module, that you have to include and call
- PhantomJS script
Just add this to your <head>
to enable AJAX indexing by the crawlers.
<meta name="fragment" content="!" />
Just include angular-seo.js
and then add the seo
module to you app:
angular.module('app', ['ng', 'seo']);
If you are using RequireJS, the script will detect it and auto define itself BUT you will need to have an angular
shim defined, as angular-seo
requires it:
requirejs.config({
paths: {
angular: 'http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min',
},
shim: {
angular: {
exports: 'angular'
}
}
});
Then you must call $scope.htmlReady()
when you think the page is complete. This is nescessary because of the async nature of AngularJS (such as with AJAX calls).
function MyCtrl($scope) {
Items.query({}, function(items) {
$scope.items = items;
$scope.htmlReady();
});
}
And that's all there is to do on the app side.
For the app to be properly rendered, you will need to run the angular-seo-server.js
with PhantomJS.
Make sure to disable caching:
To serve a snapshot of a single website, use
$ phantomjs --disk-cache=no angular-seo-server.js [port] [URL prefix]
To serve multiple website using a single instance of phantomJS, just dont pass a URL prefix
$ phantomjs --disk-cache=no angular-seo-server.js [port]
URL prefix
is the URL that will be prepended to the path the crawlers will try to get.
Some examples:
$ phantomjs --disk-cache=no angular-seo-server.js 8888 http://localhost:8000/myapp
$ phantomjs --disk-cache=no angular-seo-server.js 8888 file:///path/to/index.html
Google and Bing replace #!
(hashbang) with ?_escaped_fragment_=
so htttp://localhost/app.html#!/route
becomes htttp://localhost/app.html?_escaped_fragment_=/route
.
So say you app is running on http://localhost:8000/index.html
(works with file://
URLs too).
First, run PhantomJS:
$ phantomjs --disk-cache=no angular-seo-server.js 8888 http://localhost:8000/index.html
Listening on 8888...
Press Ctrl+C to stop.
Then try with cURL:
$ curl 'http://localhost:8888/?_escaped_fragment_=/route'
You should then have a complete, rendered HTML output.
If course you don't want regular users to see this, only crawlers.
To detect that, just look for an _escaped_fragment_
in the query args.
For instance with Nginx:
# Production config
server {
server_name <%= server_name %> default;
listen <%= server_port %>;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
# If not search enging pass static
root "<%= base_dir %>";
if ($args ~ _escaped_fragment_) {
proxy_pass http://localhost:8888;
break;
}
try_files $uri $uri/ /index.html;
}
}