“单页” JS网站和SEO


128

如今,有许多很酷的工具可用于创建功能强大的“单页” JavaScript网站。在我看来,这是通过让服务器充当API(仅此而已)和让客户端处理所有HTML生成内容来正确完成的。这种“模式”的问题是缺乏搜索引擎支持。我可以想到两种解决方案:

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

第一种解决方案将在此处进一步讨论。我一直在一个网站上这样做,这不是一个很好的体验。它不是DRY,在我的情况下,我必须为客户端和服务器使用两个不同的模板引擎。

我想我已经看到了一些不错的Flash网站的第二种解决方案。与第一种方法相比,我更喜欢这种方法,并且在服务器上使用正确的工具可以轻松完成。

所以我真正想知道的是以下内容:

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

Answers:


44

尽管对于开发人员来说,#2可能更“轻松”,但它仅提供搜索引擎爬网。是的,如果Google发现您提供的内容不同,您可能会受到处罚(我不是专家,但是我听说过这种情况)。

SEO和可访问性(不仅适用于残障人士,而且可通过移动设备,触摸屏设备和其他非标准计算/支持互联网的平台进行可访问性)都具有相似的基本理念:“可访问”(即可以被所有这​​些不同的浏览器访问,查看,阅读,处理或以其他方式使用)。屏幕阅读器,搜索引擎搜寻器或启用JavaScript的用户都应该能够使用/索引/理解您网站的核心功能,而不会出现问题。

pushState根据我的经验,这不会增加此负担。它只会将过去的想法和“如果有时间的话”带到Web开发的最前沿。

您在选项1中描述的内容通常是最好的方法-但是,与其他可访问性和SEO问题一样,在使用pushState大量JavaScript的应用中执行此操作需要预先进行计划,否则将成为一个沉重的负担。应该从一开始就将其烘焙到页面和应用程序体系结构中-改造很痛苦,并且会导致不必要的重复。

pushState最近,我一直在与SEO一起使用几个不同的应用程序,我发现我认为这是一个很好的方法。它基本上遵循您的项目#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>

服务器呈现此内容后,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")
  });
});

这是一个非常简单的示例,但我认为这很重要。

在页面加载后实例化视图时,我将服务器呈现的表单的现有内容提供给视图实例,作为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>

现在,您需要遍历此列表,并将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();

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


这种处理服务器呈现的HTML并让我的JavaScript接管并运行它的方法,是使SEO,可访问性和pushState支持方面发展的好方法。

希望有帮助。


我明白您的意思,但是有趣的是在“您的JavaScript接管”之后如何进行渲染。在更复杂的示例中,您可能必须在客户端上使用未编译的模板,从而遍历一系列用户以构建列表。每当用户的模型更改时,视图都会重新渲染。在不复制模板的情况下(又不要求服务器为客户端呈现视图),您将如何做?
2011年

我链接的2篇博客文章应共同向您展示如何拥有可在客户端和服务器上使用的模板-无需重复。如果您希望整个页面可访问且对SEO友好,则服务器将需要呈现整个页面。我已经更新了答案,以包含一个附加到服务器呈现的用户列表的更复杂示例
Derick Bailey

22

我认为您需要这样做http : //code.google.com/web/ajaxcrawling/

您还可以安装特殊的后端,该后端通过在服务器上运行javascript“渲染”您的页面,然后将其提供给Google。

结合这两件事,您将获得一个无需两次编程的解决方案。(只要您的应用程序可以通过锚片段完全控制。)


实际上,这不是我想要的。这些是第一种解决方案的一些变体,正如我提到的那样,我对这种方法不是很满意。
user544941 2011年

2
你没有看完我的全部答案。您还使用一个特殊的后端为您呈现javascript-您不会重复编写任何东西。
Ariel

是的,我确实读过。但是,如果我没弄错,那将是程序的麻烦,因为它必须模拟触发pushState的每个动作。或者,我可以直接对其执行操作,但之后就不再那么干了。
user544941 2011年

2
我认为它基本上是没有前端的浏览器。但是,是的,您必须使程序完全可由锚片段控制。您还需要确保所有链接中都有正确的片段,以及onClicks或代替onClicks。
Ariel

17

因此,似乎主要关注的是干燥

  • 如果您使用的是pushState,则服务器将为所有url发送相同的确切代码(不包含用于提供图像的文件扩展名)。“ / mydir / myfile”,“ / myotherdir / myotherfile”或根目录“ / ”-所有请求均接收相同的确切代码。您需要具有某种URL重写引擎。您还可以提供少量的html,其余的可以来自CDN(使用require.js来管理依赖项-请参阅https://stackoverflow.com/a/13813102/1595913)。
  • (通过将链接转换为您的url方案并通过查询静态或动态源来测试内容是否存在,以测试链接的有效性。如果无效,则发送404响应。)
  • 如果请求不是来自Google漫游器,则只需正常处理即可。
  • 如果请求来自Google机器人,则可以使用phantom.js-无头Webkit浏览器(“无头浏览器只是功能齐全的Web浏览器,没有可视界面。”)在服务器上呈现html和javascript并发送google bot生成的html。当bot解析html时,它可以访问服务器上的其他“ pushState”链接/ somepage,服务器<a href="https://stackoverflow.com/someotherpage">mylink</a>将url重写为您的应用程序文件,将其加载到phantom.js中,并将生成的html发送到bot等等。 ..
  • 对于您的html,我假设您使用的是带有某种劫持的普通链接(例如,与steiner.js一起使用https://stackoverflow.com/a/9331734/1595913
  • 为避免与任何链接混淆,将提供json的api代码分隔到单独的子域中,例如api.mysite.com
  • 为了提高性能,您可以在非工作时间提前为搜索引擎预处理您的网站页面,方法是使用与phantom.js相同的机制创建页面的静态版本,然后将这些静态页面提供给Google bot。可以使用一些可以解析<a>标签的简单应用来完成预处理。在这种情况下,处理404更容易,因为您可以简单地检查静态文件的名称是否包含网址路径。
  • 如果您使用#!适用于您的网站链接的哈希爆炸语法适用于类似情况,不同之处在于重写URL服务器引擎会在url中查找_escaped_fragment_并将该URL格式化为您的url方案。
  • github上有node.js与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


4

如果您使用的是Rails,请尝试poirot。它是一颗宝石,可以轻松地重用客户端和服务器端的胡须车把模板。

在您的视图中创建一个文件,例如_some_thingy.html.mustache

渲染服务器端:

<%= render :partial => 'some_thingy', object: my_model %>

将模板放到客户端使用:

<%= template_include_tag 'some_thingy' %>

Rendre客户端:

html = poirot.someThingy(my_model)

3

换个角度来说,第二种解决方案在可访问性方面将是正确的解决方案……您将向无法使用javascript的用户(带有屏幕阅读器的用户等)提供替代内容。

这将自动增加SEO的好处,我认为Google不会将其视为“顽皮”的技术。


有没有人证明你错了?发表评论已经有一段时间了
jkulak 2014年

1

有趣。我一直在寻找可行的解决方案,但这似乎很成问题。

我实际上更倾向于您的第二种方法:

让服务器仅为搜索引擎机器人提供一个特殊的网站。如果普通用户访问http://example.com/my_path,则服务器应为他提供该网站的JavaScript重型版本。但是,如果Google漫游器访问,服务器应为其提供一些最小的HTML,其中包含我希望Google索引的内容。

这是我对解决问题的看法。尽管尚未确定它可以工作,但它可能会为其他开发人员提供一些见识或想法。

假设您使用的是支持“推送状态”功能的JS框架,而您的后端框架是Ruby on Rails。您有一个简单的博客网站,并且您希望搜索引擎将所有文章indexshow页面编入索引。

假设您的路线设置如下:

resources :articles
match "*path", "main#index"

确保每个服务器端控制器都呈现客户端框架运行所需的相同模板(html / css / javascript / etc)。如果请求中没有任何一个控制器匹配(在此示例中,我们只有RESTful的一组动作ArticlesController),则只需匹配其他任何内容并渲染模板,然后让客户端框架处理路由即可。命中控制器和命中通配符匹配器之间的唯一区别是能够基于禁用JavaScript的设备请求的URL呈现内容的能力。

据我了解,呈现浏览器不可见的内容是一个坏主意。因此,当Google将其编入索引时,人们会遍历Google来访问给定的页面,并且没有任何内容,那么您可能会受到惩罚。我想到的是,您div可以display: none在CSS 中的节点中呈现内容。

但是,我敢肯定,只要您执行以下操作就没有关系:

<div id="no-js">
  <h1><%= @article.title %></h1>
  <p><%= @article.description %></p>
  <p><%= @article.content %></p>
</div>

然后使用JavaScript,当禁用JavaScript的设备打开页面时,该JavaScript不会运行:

$("#no-js").remove() # jQuery

这样,对于Google以及使用JavaScript禁用设备的任何人,他们都会看到原始/静态内容。因此,内容物理上就在那里,并且任何使用了禁用JavaScript的设备的人都可以看到。

但是,当用户访问同一页面,实际上已经支持JavaScript,该#no-js节点将被删除,因此不会弄乱你的应用程序。然后,您的客户端框架将通过其路由器处理请求,并显示启用JavaScript时用户应看到的内容。

我认为这可能是一种有效且相当容易使用的技术。尽管这可能取决于您的网站/应用程序的复杂性。

不过,如果不是,请更正我。只是以为我会分享我的想法。


1
好吧,如果您首先显示内容,然后再删除它,那么最终用户很可能会注意到其浏览器中的内容闪烁/闪烁:在您的JS代码加载和执行之前延迟。你认为呢?
Evereq

1

在服务器端使用NodeJS,对您的客户端代码进行浏览器浏览,并将每个http请求(静态http资源除外)uri路由到服务器端客户端,以提供第一个“ bootsnap”(其状态页面的快照)。使用jsdom之类的东西来处理服务器上的jQuery dom-op。启动引导返回后,设置websocket连接。最好通过在客户端上建立某种包装连接来区分websocket客户端和服务器端客户端(服务器端客户端可以直接与服务器通信)。我一直在做这样的事情:https : //github.com/jvanveen/rnet/


0

使用Google Closure模板呈现页面。它可以编译为javascript或java,因此很容易在客户端或服务器端呈现页面。与每个客户的第一次接触时,呈现html并将javascript作为链接添加到标头中。爬网程序仅读取html,但浏览器将执行您的脚本。来自浏览器的所有后续请求都可以针对api完成,以最大程度地减少流量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.