ChatGPT解决这个技术问题 Extra ChatGPT

“单页” JS 网站和 SEO

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

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

第一种解决方案将进一步讨论here。我一直在做一个网站,这不是一个很好的体验。它不是 DRY,就我而言,我不得不为客户端和服务器使用两个不同的模板引擎。

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

所以我真正想知道的是以下几点:

你能想出更好的解决方案吗?

第二种解决方案有什么缺点?如果谷歌以某种方式发现我为谷歌机器人提供的内容与普通用户不同,那么我会在搜索结果中受到惩罚吗?


B
Bruno Reis

虽然 #2 对于作为开发人员的您来说可能“更容易”,但它只提供搜索引擎抓取。是的,如果 Google 发现您提供不同的内容,您可能会受到处罚(我不是这方面的专家,但我听说过这种情况)。

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

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

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

我最近一直在为几个不同的应用程序使用 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 提供给视图实例。在加载第一个视图时,我调用渲染或让视图为我生成 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();

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

这种获取服务器呈现的 html 并让我的 JavaScript 接管并运行它的过程,是让 SEO、可访问性和 pushState 支持滚动的好方法。

希望有帮助。


我明白你的意思,但有趣的是在“你的 JavaScript 接管”之后渲染是如何完成的。在更复杂的示例中,您可能必须在客户端上使用未编译的模板,循环访问一组用户来构建列表。每次用户的模型更改时,视图都会重新呈现。如果不复制模板(并且不要求服务器为客户端呈现视图),您将如何做到这一点?
我链接的 2 篇博客文章应该共同向您展示如何拥有可在客户端和服务器上使用的模板 - 无需重复。如果您希望它可访问且对 SEO 友好,服务器将需要呈现整个页面。我已经更新了我的答案,以包含一个更复杂的附加到服务器呈现的用户列表的示例
A
Ariel

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

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

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


其实,这不是我要找的。这些是第一个解决方案的一些变体,正如我所提到的,我对这种方法不太满意。
您没有阅读我的全部答案。您还可以使用一个特殊的后端来为您呈现 javascript - 您不会写两次。
是的,我确实读过。但是,如果我确实让您正确,那将是一个地狱般的程序,因为它必须模拟触发 pushState 的每个动作。或者,我可以直接给它动作,但是我们不再那么干燥了。
我认为它基本上是一个没有前端的浏览器。但是,是的,您必须使程序完全可以从锚片段中控制。您还需要确保所有链接都包含正确的片段,以及 onClicks 或代替 onClicks。
C
Community

所以,似乎主要的问题是干燥

如果您使用 pushState,则让您的服务器为所有 url 发送相同的确切代码(不包含提供图像的文件扩展名等)“/mydir/myfile”、“/myotherdir/myotherfile”或 root“/ " -- 所有请求都收到相同的确切代码。你需要有某种 url 重写引擎。您还可以提供一小部分 html,其余的可以来自您的 CDN(使用 require.js 来管理依赖项——请参阅 https://stackoverflow.com/a/13813102/1595913)。

(通过将链接转换为您的 url 方案并通过查询静态或动态源来测试内容是否存在来测试链接的有效性。如果它无效,则发送 404 响应。)

当请求不是来自 google bot 时,您只需正常处理即可。

如果请求来自 google bot,则使用 phantom.js——headless webkit 浏览器(“无头浏览器只是一个没有可视界面的全功能 Web 浏览器。”)在服务器上呈现 html 和 javascript 并发送google bot 生成的 html。当机器人解析 html 时,它可以点击服务器上的其他“pushState”链接/somepage mylink,服务器将 url 重写为您的应用程序文件,将其加载到 phantom.js并将生成的 html 发送到机器人,依此类推......

对于您的html,我假设您正在使用带有某种劫持的普通链接(例如,使用backbone.js https://stackoverflow.com/a/9331734/1595913)

为避免与任何链接混淆,请将提供 json 的 api 代码分隔到单独的子域中,例如 api.mysite.com

为了提高性能,您可以在下班时间通过使用与 phantom.js 相同的机制创建页面的静态版本来提前为搜索引擎预处理您的网站页面,然后将静态页面提供给谷歌机器人。预处理可以通过一些可以解析 标签的简单应用来完成。在这种情况下,处理 404 会更容易,因为您可以简单地检查名称包含 url 路径的静态文件是否存在。

如果你使用 #!您的站点链接的 hash bang 语法适用类似的场景,除了重写 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


T
Tim Scott

如果您使用的是 Rails,请尝试 poirot。这是一个让重用 mustachehandlebars 模板客户端和服务器端变得非常简单的 gem。

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

渲染服务器端:

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

将模板放在您的脑海中以供客户端使用:

<%= template_include_tag 'some_thingy' %>

渲染客户端:

html = poirot.someThingy(my_model)

C
Clive

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

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


有没有人证明你错了?评论发表已经有一段时间了
M
Michael van Rooijen

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

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

让服务器只为搜索引擎机器人提供一个特殊的网站。如果普通用户访问 http://example.com/my_path,服务器应该为他提供网站的 JavaScript 重版本。但是如果谷歌机器人访问,服务器应该给它一些我希望谷歌索引的内容的最小 HTML。

这是我对解决问题的看法。虽然它没有被确认可以工作,但它可能会为其他开发人员提供一些见解或想法。

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

假设您的路线设置如下:

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

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

据我了解,呈现浏览器不可见的内容是一个坏主意。所以当谷歌索引它时,人们通过谷歌访问给定的页面并且没有任何内容,那么你可能会受到惩罚。想到的是您在 div 节点中呈现内容,而 display: none 在 CSS 中。

但是,我很确定您是否只是这样做并不重要:

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

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

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

这样,对于 Google 和任何使用 JavaScript 禁用设备的人来说,他们将看到原始/静态内容。因此,内容是物理存在的,并且任何使用禁用 JavaScript 的设备的人都可以看到。

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

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

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


好吧,如果您首先显示内容然后稍后将其删除,那么最终用户很可能会注意到他的浏览器中的内容闪烁/闪烁 :) 特别是如果它是慢速浏览器,您尝试显示/删除的 HTML 内容很大以及一些在您的 JS 代码加载和执行之前延迟。你认为呢?
P
Phrearch

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


A
Aleš Kotnik

使用 Google Closure Template 呈现页面。它编译为 javascript 或 java,因此很容易在客户端或服务器端呈现页面。在第一次遇到每个客户端时,呈现 html 并将 javascript 添加为标题中的链接。爬虫只会读取 html,但浏览器会执行你的脚本。来自浏览器的所有后续请求都可以针对 api 完成以最小化流量。


S
Sherry Malik

这可能会对您有所帮助:https://github.com/sharjeel619/SPA-SEO

逻辑

浏览器从服务器请求您的单页应用程序,该应用程序将从单个 index.html 文件加载。

您编写一些中间服务器代码来拦截客户端请求并区分请求是来自浏览器还是某些社交爬虫机器人。

如果请求来自某个爬虫机器人,请对您的后端服务器进行 API 调用,收集您需要的数据,将该数据填充到 html 元标记,并将这些标记以字符串格式返回给客户端。

如果请求不是来自某个爬虫机器人,那么只需从单页应用程序的 build 或 dist 文件夹返回 index.html 文件。