"单页"JS网站和SEO

use*_*941 128 javascript seo singlepage backbone.js pushstate

现在有许多很酷的工具可用于制作功能强大的"单页"JavaScript网站.在我看来,这是通过让服务器充当API(而不是更多)并让客户端处理所有HTML生成的东西来完成的.这种"模式"的问题是缺乏搜索引擎支持.我可以想到两个解决方案:

  1. 当用户进入网站时,让服务器完全按照客户端导航时的方式呈现页面.因此,如果我http://example.com/my_path直接访问服务器将呈现与客户端/my_path通过pushState时相同的事情.
  2. 让服务器只为搜索引擎机器人提供一个特殊的网站.如果普通用户访问http://example.com/my_path服务器应该给他一个JavaScript重型版本的网站.但是,如果谷歌机器人访问,服务器应该给它一些最小的HTML与我希望谷歌索引的内容.

这里将进一步讨论第一种解决方案.我一直在网站上做这个,这不是一个非常好的经历.这不是DRY,在我的情况下,我必须为客户端和服务器使用两个不同的模板引擎.

我想我已经看到了一些优秀的Flash网站的第二个解决方案.我比第一个更喜欢这种方法,并且在服务器上使用正确的工具,它可以非常轻松地完成.

所以我真正想知道的是:

  • 你能想到更好的解决方案吗?
  • 第二种解决方案有哪些缺点?如果谷歌以某种方式发现我没有像普通用户那样为谷歌机器人提供完全相同的内容,那么我会在搜索结果中受到惩罚吗?

Der*_*ley 44

虽然#2对于您作为开发人员来说可能"更容易",但它只提供搜索引擎抓取功能.是的,如果谷歌发现你服务的内容不同,你可能会受到惩罚(我不是那里的专家,但我听说过它正在发生).

搜索引擎优化和可访问性(不仅适用于残疾人,而是通过移动设备,触摸屏设备和其他非标准计算/互联网平台实现的可访问性)都具有类似的基本理念:语义丰富的标记"可访问"(即可以可以访问,查看,阅读,处理或以其他方式使用)到所有这些不同的浏览器.屏幕阅读器,搜索引擎爬虫或启用了JavaScript的用户都应该能够毫无问题地使用/索引/了解您网站的核心功能.

pushState根据我的经验,这并没有增加这种负担.它只会带来以前的事后想法,"如果我们有时间",那就是Web开发的最前沿.

您在选项#1中描述的通常是最好的方式 - 但是,与其他可访问性和搜索引擎优化问题一样,pushState在JavaScript密集的应用程序中执行此操作需要预先计划,否则将成为一个重要的负担.它应该从一开始就融入到页面和应用程序架构中 - 改造是痛苦的,并且会导致比必要的更多重复.

pushState最近和几个不同的应用程序一起工作和搜索引擎优化,我发现我认为这是一个很好的方法.它基本上遵循您的项目#1,但说明不重复html /模板.

大多数信息都可以在这两篇博文中找到:

http://lostechies.com/derickbailey/2011/09/06/test-driving-backbone-views-with-jquery-templates-the-jasmine-gem-and-jasmine-jquery/

http://lostechies.com/derickbailey/2011/06/22/rendering-a-rails-partial-as-a-jquery-template/

它的要点是我使用ERB或HAML模板(运行Ruby on Rails,Sinatra等)进行服务器端渲染,并创建Backbone可以使用的客户端模板,以及我的Jasmine JavaScript规范.这样就减少了服务器端和客户端之间的重复标记.

从那里,你需要采取一些额外的步骤让你的JavaScript使用服务器呈现的HTML - 真正的渐进增强; 采用已交付的语义标记并使用JavaScript增强它.

例如,我正在构建一个图像库应用程序pushState.如果您/images/1从服务器请求,它将在服务器上呈现整个图像库,并将所有HTML,CSS和JavaScript发送到您的浏览器.如果您禁用了JavaScript,它将完美地运行.您执行的每个操作都将从服务器请求不同的URL,服务器将为您的浏览器呈现所有标记.但是,如果你启用了JavaScript,那么JavaScript将获取已经呈现的HTML以及服务器生成的一些变量并从那里接管.

这是一个例子:

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>
Run Code Online (Sandbox Code Playgroud)

在服务器呈现之后,JavaScript会将其拾取(在此示例中使用Backbone.js视图)

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running JavaScript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});
Run Code Online (Sandbox Code Playgroud)

这是一个非常简单的例子,但我认为它得到了重点.

当我在页面加载后实例化视图时,我将服务器呈现的表单的现有内容提供给视图实例作为el视图.我调用render或具有视图生成一个el对我来说,第一个视图加载时.在视图启动并运行后,我有一个可用的渲染方法,页面都是JavaScript.如果需要,这可以让我稍后重新渲染视图.

在启用JavaScript的情况下单击"说我的名字"按钮将导致警告框.如果没有JavaScript,它会回发到服务器,服务器可以将名称呈现给某个地方的html元素.

编辑

考虑一个更复杂的示例,其中有一个需要附加的列表(来自下面的注释)

假设您有一个<ul>标签中的用户列表.当浏览器发出请求时,服务器会呈现此列表,结果如下所示:

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>
Run Code Online (Sandbox Code Playgroud)

现在,您需要遍历此列表并将Backbone视图和模型附加到每个<li>项目.通过使用该data-id属性,您可以轻松找到每个标记的模型.然后,您将需要一个足够智能的集合视图和项目视图,以将其自身附加到此HTML.

UserListView = Backbone.View.extend({
  attach: function(){
    this.el = $("#user-list");
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = this.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

var userData = {...};
var userList = new UserCollection(userData);
var userListView = new UserListView({collection: userList});
userListView.attach();
Run Code Online (Sandbox Code Playgroud)

在此示例中,UserListView将循环遍历所有<li>标记,并为每个标记附加具有正确模型的视图对象.它为模型的名称更改事件设置事件处理程序,并在发生更改时更新元素的显示文本.


这种过程,采取服务器呈现的HTML并让我的JavaScript接管并运行它,是一个很好的方式来为SEO,辅助功能和pushState支持滚动.

希望有所帮助.


Ari*_*iel 22

我认为你需要这个:http://code.google.com/web/ajaxcrawling/

您还可以安装一个特殊的后端,通过在服务器上运行javascript来"呈现"您的页面,然后将其提供给谷歌.

将两者结合起来就可以得到解决方案而无需编程两次.(只要您的应用程序可通过锚片段完全控制.)

  • 你没有读完我的答案.你还使用一个特殊的后端为你呈现javascript - 你不会写两次东西. (2认同)
  • 我认为它基本上是一个没有前端的浏览器.但是,是的,你必须使程序完全可以从锚片段中控制.您还需要确保所有链接中都包含正确的片段,以及onClicks,或者代替onClicks. (2认同)

小智 17

因此,似乎主要担心的是DRY

  • 如果您正在使用pushState,您的服务器会为所有网址发送相同的确切代码(不包含用于提供图像的文件扩展名等)"/ mydir/myfile","/ myotherdir/myotherfile"或root"/ " - 所有请求都收到相同的确切代码.你需要有一些url重写引擎.您还可以提供一些html,其余的可以来自您的CDN(使用require.js来管理依赖关系 - 请参阅/sf/answers/966917171/).
  • (通过将链接转换为您的网址方案来测试链接的有效性,并通过查询静态或动态源来测试内容的存在.如果它无效,则发送404响应.)
  • 当请求不是来自谷歌机器人时,您只需正常处理.
  • 如果请求来自谷歌机器人,你使用phantom.js - 无头webkit浏览器("无头浏览器只是一个没有可视界面的全功能网络浏览器.")在服务器上呈现html和javascript并发送谷歌机器人由此产生的HTML.当机器人解析html它可以命中你服务器上的其他"pushState"链接/某个页面时,服务器<a href="/someotherpage">mylink</a>会将url重写为你的应用程序文件,将其加载到phantom.js中,然后将生成的html发送到机器人,依此类推. ..
  • 对于你的html我假设你正在使用带有某种劫持的普通链接(例如使用backbone.js /sf/answers/653221411/)
  • 为避免与任何链接混淆,请将为json提供服务的api代码分隔为单独的子域,例如api.mysite.com
  • 为了提高性能,您可以在非工作时间提前预处理搜索引擎的网站页面,方法是使用与phantom.js相同的机制创建页面的静态版本,然后将静态页面提供给谷歌机器人.可以使用一些可以解析<a>标签的简单应用程序来完成预处理.在这种情况下,处理404更容易,因为您只需检查是否存在具有包含url路径的名称的静态文件.
  • 如果你使用#!除了重写url服务器引擎会在url中查找_escaped_fragment_并将url格式化为url方案之外,您的站点链接的hash bang语法也适用类似的场景.
  • node.js与github上的phantom.js有几个集成,你可以使用node.js作为web服务器来生成html输出.

以下是使用phantom.js进行seo的几个示例:

http://backbonetutorials.com/seo-for-single-page-apps/

http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering


Cli*_*ive 3

从稍微不同的角度来看,您的第二个解决方案在可访问性方面是正确的……您将为无法使用 javascript 的用户(具有屏幕阅读器的用户等)提供替代内容。

这将自动增加搜索引擎优化的好处,并且在我看来,谷歌不会将其视为“顽皮”的技术。