基于Angularjs单页面应用的SEO优化

在之前的文章我曾提到基于Angularjs的单页面应用在用户体验上的种种好处。然而任何事情都不是完美的,Angular和类似的框架通过应用内做页面路由的实现给SEO(也俗称搜索引擎优化)带来了不少麻烦。

首先,我们来看看页面内路由是如何实现的。默认Angularjs生成的页面uri类型如下,

http://mydomain.com/#/app/page1

浏览器请求上面这个uri的时候,实际发送给服务器的请求地址是http://mydomain.com/, web服务器会将默认的页面响应给浏览器,比如index.htmlindex.php等。

浏览器返回的页面里面引入了Angularjs和其他应用需要的JS库。Angularjs应用开始执行后,尝试处理路由**/app/page1**。如果应用定义了该路由,将加载必要的JS库和其他html片段来完成页面的渲染。

理解了Angularjs页面内路由的原理后,我们知道了对浏览器或搜索引擎爬虫而言,单页面应用所有的页面对浏览器和搜索引擎都是一个网址,比如http://mydomain.com/。这样对爬虫抓取站内链接造成了困难,因为所有应用内的链接都被认做了同一个链接。

我们理解了uri http://mydomain.com/#/app/page1给SEO造成的麻烦,接下来就是讨论如何针对SEO来作的优化。

最理想的情况当然是搜索引擎爬虫变的更加智能,它能理解网站的框架,并且针对此种情况做出优化。但截止到目前,包括Google在内的所有爬虫都无法做到这点。那我们SEO的优化只能在应用这边来做了。

Angularjs提供了一种HTML5 mode模式可以利用HTML5 History API来实现页面内路由。打开的方法如下,

1$locationProvider.html5Mode(true);

同时在index.html页面加上如下标签,

1<base href="/">

在打开HTML5 mode后的Angularjs应用的链接看起来就是这样了,

http://mydomain.com/app/page1

新的链接模式和站内跳转通过访问网站主页请求将没有任何问题。然而直接在浏览器请求如上链接的话,Web服务器将尝试请求/app/page1,通常会得到404的页面响应。因为服务器上并没有部署页面/app/page1

这时就需要在Web应用服务器或应用里面实现URL Rewrite。将/app/page1的请求转到单页面应用html文件上。

下面是一些Web服务器或应用的参考配置,

  • Apache Rewrites

     1<VirtualHost *:80>
     2    ServerName my-app
     3
     4    DocumentRoot /path/to/app
     5
     6    <Directory /path/to/app>
     7        RewriteEngine on
     8
     9        # Don't rewrite files or directories
    10        RewriteCond %{REQUEST_FILENAME} -f [OR]
    11        RewriteCond %{REQUEST_FILENAME} -d
    12        RewriteRule ^ - [L]
    13
    14        # Rewrite everything else to index.html to allow html5 state links
    15        RewriteRule ^ index.html [L]
    16    </Directory>
    17</VirtualHost>
  • Nginx Rewrites

    1server {
    2    server_name my-app;
    3
    4    root /path/to/app;
    5
    6    location / {
    7        try_files $uri $uri/ /index.html;
    8    }
    9}
  • Azure IIS Rewrites

     1<system.webServer>
     2  <rewrite>
     3    <rules> 
     4      <rule name="Main Rule" stopProcessing="true">
     5        <match url=".*" />
     6        <conditions logicalGrouping="MatchAll">
     7          <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />                                 
     8          <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
     9        </conditions>
    10        <action type="Rewrite" url="/" />
    11      </rule>
    12    </rules>
    13  </rewrite>
    14</system.webServer>
  • Express Rewrites

     1var express = require('express');
     2var app = express();
     3
     4app.use('/js', express.static(__dirname + '/js'));
     5app.use('/dist', express.static(__dirname + '/../dist'));
     6app.use('/css', express.static(__dirname + '/css'));
     7app.use('/partials', express.static(__dirname + '/partials'));
     8
     9app.all('/*', function(req, res, next) {
    10    // Just send the index.html for other files to support HTML5Mode
    11    res.sendFile('index.html', { root: __dirname });
    12});
    13
    14app.listen(3006); //the port you want to use
    
  • ASP.Net C# Rewrites

    1private const string ROOT_DOCUMENT = "/default.aspx";
    2
    3protected void Application_BeginRequest( Object sender, EventArgs e )
    4{
    5    string url = Request.Url.LocalPath;
    6    if ( !System.IO.File.Exists( Context.Server.MapPath( url ) ) )
    7        Context.RewritePath( ROOT_DOCUMENT );
    8}