Javascript AngularJS静态网页搜索引擎优化(S3 CDN)

Javascript AngularJS静态网页搜索引擎优化(S3 CDN),javascript,angularjs,amazon-web-services,amazon-s3,seo,Javascript,Angularjs,Amazon Web Services,Amazon S3,Seo,我一直在寻找改进angularJS应用程序SEO的方法,这些应用程序托管在类似AmazonS3的CDN上(即没有后端的简单存储)。大多数解决方案都依赖于后端来识别爬虫程序生成的?\u转义\u片段\uurl,然后从其他地方获取相关页面。即使最终需要您这样做,即使您提前生成快照页面 这基本上依赖于使用cloudflare作为反向代理,这似乎有点浪费,因为他们的服务提供的大多数安全设备等对于静态站点来说是完全冗余的。按照建议自己设置反向代理似乎也有问题,因为它需要i)通过一个代理服务器路由我需要的所有

我一直在寻找改进angularJS应用程序SEO的方法,这些应用程序托管在类似AmazonS3的CDN上(即没有后端的简单存储)。大多数解决方案都依赖于后端来识别爬虫程序生成的
?\u转义\u片段\u
url,然后从其他地方获取相关页面。即使最终需要您这样做,即使您提前生成快照页面

这基本上依赖于使用cloudflare作为反向代理,这似乎有点浪费,因为他们的服务提供的大多数安全设备等对于静态站点来说是完全冗余的。按照建议自己设置反向代理似乎也有问题,因为它需要i)通过一个代理服务器路由我需要的所有AngularJS应用程序,这可能会影响性能;或者ii)为每个应用程序设置单独的代理服务器,在这一点上,我还可以设置后端,这在我的工作范围内是无法承受的

有没有这样做的方法,或者在谷歌更新他们的爬虫程序之前,静态托管的AngularJS应用程序的搜索引擎优化基本上是不可能的



以下是John Conde的评论。

如果您以有趣的方式使用ng-Clope,可能会有一个很好的解决方案

我自己也没试过,但理论上应该行得通

解决方案高度依赖于CSS,但它应该非常好。 例如,您的angular应用程序中有三种状态: -索引(路径名:#/) -关于(路径名:#/about) -联系人(路径名:#/contact)

索引的基本情况也可以添加进去,但会很棘手,所以我暂时不考虑

使您的HTML如下所示:

<body>
    <div ng-app="myApp" ng-cloak>
        <!-- Your whole angular app goes here... -->
    </div>
    <div class="static">
        <div id="about class="static-other">
            <!-- Your whole about content here... -->
        </div>
        <div id="contact" class="static-other">
            <!-- Your whole contact content here... -->
        </div>
        <div id="index" class="static-main">
            <!-- Your whole index content here... -->
        </div>
    </div>
</body>
[ng-cloak], .static { display: none; }
[ng-cloak] ~ .static { display: block; }
.static-other {display: none;}
.static-other:target {display: block;}
.static-other:target ~ .static-main {display: none;}
不管怎样,这对你来说可能已经足够好了。 mg斗篷指令将在angular未加载时隐藏angular应用程序,并显示静态内容。Google将以HTML格式获取您的静态内容。 作为奖励,最终用户还可以在角度加载时很好地查看静态内容的样式

如果您开始在CSS中使用:target伪选择器,那么您可以变得更有创意。您可以在静态内容中使用实际链接,但只需将它们链接到各种ID即可。所以在#index div中,确保你有到#about和#contact的链接。请注意链接中缺少的“/”。HTML id不能以斜杠开头

然后使您的CSS如下所示:

<body>
    <div ng-app="myApp" ng-cloak>
        <!-- Your whole angular app goes here... -->
    </div>
    <div class="static">
        <div id="about class="static-other">
            <!-- Your whole about content here... -->
        </div>
        <div id="contact" class="static-other">
            <!-- Your whole contact content here... -->
        </div>
        <div id="index" class="static-main">
            <!-- Your whole index content here... -->
        </div>
    </div>
</body>
[ng-cloak], .static { display: none; }
[ng-cloak] ~ .static { display: block; }
.static-other {display: none;}
.static-other:target {display: block;}
.static-other:target ~ .static-main {display: none;}
你现在有了一个功能齐全的静态应用程序,它在angular启动之前就可以运行了

另外一个好处是,当angular启动时,它足够聪明,可以自动将#即将#转换为##/about,这种体验甚至不会中断

当然,不要忘记SEO问题已经完全解决了。我还没有使用这项技术,因为我一直都有一个服务器需要配置,但我非常感兴趣的是这项技术如何适用于您


希望这能有所帮助。

事实上,这是一项非常麻烦的任务,但我已经设法让SEO与我的AngularJS SPA站点(托管在AWS S3上)在一起很好地工作。其主要思想是预生成内容并将其填充到HTML中。加载页面时仍将加载模板,并替换预呈现的内容


您可以在上阅读有关我的解决方案的更多信息,但请注意,有很多条件。

我已经找了好几天来找到解决方案。据我所知,这个问题没有很好的解决办法。我希望firebase最终能够启用用户代理重定向。如果您有钱,可以使用MaxCDN enterprise。它们提供边缘规则,包括用户代理的重定向


以下是如何在存储服务(如S3)上使用漂亮的URL(no#)和grunt使您的应用程序SEO友好的完整概述,在构建后使用简单的命令执行:

grunt seo
这仍然是一个难题的解决办法,但它的工作,这是最好的你可以做的。感谢@ericluwj和他的博文,是他启发了我

概述 目标和url结构

目标是在angular应用程序中为每个州创建一个html文件。唯一主要的假设是通过使用html5history(您应该这么做!)从url中删除“#”,并且所有路径都是绝对路径或使用角度状态。有很多帖子解释了如何做到这一点

URL以如下所示的尾随斜杠结尾

就我个人而言,我确保(没有尾随斜杠)也能到达它的目的地,但这是离题的。我还确保每种语言都有不同的状态和不同的url

搜索引擎优化逻辑

我们的目标是,当有人通过http请求访问您的网站时:

  • 如果是搜索引擎爬虫:让他保持在包含所需html的页面上。该页面还包含角度逻辑(例如启动你的应用程序),但爬虫程序无法读取,因此他故意使用你提供给他的html,并将其索引
  • 对于普通人和智能机器:确保angular被激活,删除生成的html并正常启动应用程序
咕哝任务

下面是grunt任务:

  //grunt plugins you will need:
  grunt.loadNpmTasks('grunt-prerender');
  grunt.loadNpmTasks('grunt-replace');
  grunt.loadNpmTasks('grunt-wait');
  grunt.loadNpmTasks('grunt-aws-s3');

  //The grunt tasks in the right order
  grunt.registerTask('seo', 'First launch server, then prerender and replace', function (target) {
    grunt.task.run([
      'concurrent:seo' //Step 1: in parrallel launch server, then perform so-called seotasks
    ]);
  });

  grunt.registerTask('seotasks', [
    'http', //This is an API call to get all pages on my website. Skipping this step in this tutorial.
    'wait', // wait 1.5 sec to make sure that server is launched
    'prerender', //Step 2: create a snapshot of your website
    'replace', //Step 3: clean the mess
    'sitemap', //Create a sitemap of your production environment
    'aws_s3:dev' //Step 4: upload
  ]);
第1步:启动本地服务器并发:seo 我们首先需要启动一个本地服务器(比如grunt serve),这样我们就可以拍摄网站的快照

//grunt config
concurrent: {
  seo: [
    'connect:dist:keepalive', //Launching a server and keeping it alive
    'seotasks' //now that we have a running server we can launch the SEO tasks
  ]
}
步骤2:使用grunt prerender创建网站快照 grunt prerender插件允许您使用PhantomJS拍摄任何网站的快照。在我们的例子中,我们希望对刚刚启动的localhost网站的所有页面进行快照

//grunt config
prerender: {
  options: {
    sitePath: 'http://localhost:9001', //points to the url of the server you just launched. You can also make it point to your production website.
    //As you can see the source urls allow for multiple languages provided you have different states for different languages (see note below for that)
    urls: ['/', '/projects/', '/portal/','/en/', '/projects/en/', '/portal/en/','/fr/', '/projects/fr/', '/portal/fr/'],//this var can be dynamically updated, which is done in my case in the callback of the http task
    hashed: true,
    dest: 'dist/SEO/',//where your static html files will be stored
    timeout:5000,
    interval:5000, //taking a snapshot of how the page looks like after 5 seconds.
    phantomScript:'basic',
    limit:7 //# pages processed simultaneously 
  }
}
第3步:用grunt replace清理杂物 如果打开预渲染文件,它们将适用于爬虫,但不适用于人类。对于使用chrome的用户,您的指令将加载两次。因此,在angular被激活之前,您需要将智能浏览器重定向到您的主页(即右)
<div ui-view autoscroll="true" id="ui-view"></div>

<!-- this script is needed to clear ui-view BEFORE angular starts to remove the static html that has been generated for search engines who cannot read angular -->
<script> 
  if(!/bot|googlebot|crawler|spider|robot|crawling/i.test( navigator.userAgent)) { document.getElementById('ui-view').innerHTML = ""; }
</script>
aws_s3: {
  options: {
    accessKeyId: "<%= aws.accessKeyId %>", // Use the variables
    secretAccessKey: "<%= aws.secret %>", // You can also use env variables
    region: 'eu-west-1',
    uploadConcurrency: 5, // 5 simultaneous uploads
  },
  dev: {
    options: {
      bucket: 'xxxxxxxx'
    },
    files: [
      {expand: true, cwd: 'dist/', src: ['**'], exclude: 'SEO/**', dest: '', differential: true},
      {expand: true, cwd: 'dist/SEO/', src: ['**'], dest: '', differential: true},
    ]
  }
}
'use strict';

exports.handler = (event, context, callback) => {
    console.log("Event received is", JSON.stringify(event));
    console.log("Context received is", context);
    const request = event.Records[0].cf.request;
    if (request.uri.endsWith(".rt")) {
        console.log("URI is matching with .rt, the URI is ", request.uri);
        request.uri = "/";
    } else {
        console.log("URI is not ending with rt so letting it go URI is", request.uri);
    }
    console.log("Final request URI is", request.uri);
    callback(null, request);
};