开发者问题收集

React-router URL 在刷新或手动写入时不起作用

2015-01-13
868586

我正在使用 React-router,当我单击链接按钮时它工作正常,但当我刷新网页时它不会加载我想要的内容。

例如,我在 localhost/joblist 中,一切都很好,因为我按链接到达这里。但是 如果 我刷新网页,我会得到:

Cannot GET /joblist

默认情况下,它不是这样工作的。最初,我的 URL 为 localhost/#/localhost/#/joblist ,它们工作正常。但是我不喜欢这种 URL,因此尝试删除该 # ,我写道:

Router.run(routes, Router.HistoryLocation, function (Handler) {
 React.render(<Handler/>, document.body);
});

此问题不会发生在 localhost/ 上,它始终返回我想要的内容。

此应用程序是单页的,因此 /joblist 不需要向任何服务器询问任何内容。

我的整个路由器。

var routes = (
    <Route name="app" path="/" handler={App}>
        <Route name="joblist" path="/joblist" handler={JobList}/>
        <DefaultRoute handler={Dashboard}/>
        <NotFoundRoute handler={NotFound}/>
    </Route>
);

Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});
3个回答

服务器端与客户端

首先要理解的是,现在有 2 个地方解释 URL,而“过去”只有 1 个地方。过去,当生活很简单时,一些用户向服务器发送 http://example.com/about 的请求,服务器检查 URL 的路径部分,确定用户正在请求关于页面,然后返回该页面。

使用客户端路由(React Router 提供的功能),事情就没那么简单了。起初,客户端尚未加载任何 JavaScript 代码。因此,第一个请求始终是发往服务器的。然后,服务器将返回一个页面,其中包含加载 React 和 React Router 等所需的脚本标记。只有当这些脚本加载完成后,第 2 阶段才会启动。在第 2 阶段,例如,当用户点击“关于我们”导航链接时,URL 仅在本地更改为 http://example.com/about (由 History API 实现),但 不会向服务器发出任何请求 。相反,React Router 在客户端执行其操作,确定要呈现哪个 React 视图,然后呈现它。假设您的关于页面不需要进行任何 REST 调用,它已经完成了。您已从 主页 转换到 关于我们 ,且未触发任何服务器请求。

因此,基本上,当您单击链接时,会运行一些 JavaScript 来操纵地址栏中的 URL, 不会导致页面刷新 ,这反过来会导致 React Router 在 客户端 执行页面转换。

但是现在考虑一下,如果您将 URL 复制粘贴到地址栏中并通过电子邮件发送给朋友,会发生什么情况。您的朋友尚未加载您的网站。换句话说,她仍处于 阶段 1 。她的机器上尚未运行 React Router。因此,她的浏览器将向 http://example.com/about 发出 服务器请求

这就是您的麻烦开始的地方。到目前为止,您只需在服务器的 Web 根目录中放置静态 HTML 即可。但是,当从服务器请求时,所有其他 URL 都会出现 404 错误。这些相同的 URL 在客户端 可以正常工作 ,因为 React Router 正在为您进行路由,但它们在服务器端 会失败 ,除非您让服务器理解它们。

结合服务器端和客户端路由

如果您希望 http://example.com/about URL 在服务器端和客户端都能正常工作,则需要在服务器端和客户端为其设置路由。这很有意义,对吧?

这就是您的选择开始的地方。解决方案包括通过返回引导 HTML 的万能路由完全绕过问题,以及服务器和客户端都运行相同 JavaScript 代码的完全同构方法。

彻底绕过问题:哈希历史记录

使用 哈希历史记录 ,而不是 浏览器历史记录 ,您的关于页面的 URL 将如下所示: http://example.com/#/about

哈希 ( # ) 符号后面的部分不会发送到服务器。因此,服务器只能看到 http://example.com/ 并按预期发送索引页。React Router 将拾取 #/about 部分并显示正确的页面。

缺点

  • “丑陋”的 URL
  • 使用此方法无法进行服务器端渲染。就 搜索引擎优化 (SEO) 而言,您的网站由一个几乎没有任何内容的页面组成。

全部捕获

使用此方法,您确实使用了浏览器历史记录,但只需在服务器上设置一个将 /* 发送到 index.html 的捕获,实际上会给您带来与哈希历史记录相同的情况。但是,您确实拥有干净的 URL,并且以后可以改进此方案,而不必使所有用户的收藏无效。

缺点

  • 设置更复杂
  • 仍然不是好的 SEO

混合

在混合方法中,您可以通过为特定路线添加特定脚本来扩展包罗万象的方案。您可以编写一些简单的 PHP 脚本来返回包含内容的站点中最重要的页面,这样 Googlebot 至少可以看到您的页面上的内容。

缺点

  • 设置起来更加复杂
  • 只有您给予特殊处理的路由才会获得良好的 SEO
  • 在服务器和客户端上重复呈现内容的代码

同构

如果我们使用 Node.js 作为我们的服务器,以便我们可以在两端运行 相同 的 JavaScript 代码,会怎么样?现在,我们在单个 react-router 配置中定义了所有路由,并且我们不需要重复我们的渲染代码。可以说,这就是“圣杯”。服务器发送的标记与我们在客户端发生页面转换时最终得到的标记完全相同。此解决方案在 SEO 方面是最佳的。

缺点

  • 服务器 必须 (能够)运行 JavaScript。我曾尝试将 Java 与 Nashorn 结合使用,但对我来说不起作用。实际上,这主要意味着您必须使用基于 Node.js 的服务器。
  • 许多棘手的环境问题(在服务器端使用 window 等)
  • 学习曲线陡峭

我应该使用哪一个?

选择你可以使用的那个。就我个人而言,我认为 catch-all 的设置非常简单,所以这是我的最低要求。这种设置允许你随着时间的推移不断改进。如果你已经使用 Node.js 作为你的服务器平台,我肯定会研究做一个同构应用程序。是的,一开始很难,但一旦你掌握了它,它实际上是一个非常优雅的问题解决方案。

所以基本上,对我来说,这将是决定性因素。如果我的服务器在 Node.js 上运行,我会选择同构;否则,我会选择 Catch-all 解决方案,并随着时间的推移和 SEO 要求对其进行扩展(混合解决方案)。

如果您想了解有关使用 React 进行同构(也称为“通用”)渲染的更多信息,这里有一些关于该主题的优秀教程:

此外,为了帮助您入门,我建议您查看一些入门套件。选择一个与您的技术堆栈选择相匹配的套件(请记住,React 只是 MVC 中的 V,您需要更多东西才能构建完整的应用程序)。首先查看 Facebook 自己发布的版本:

或者从社区提供的众多版本中选择一个。现在有一个不错的网站尝试索引所有这些:

我从这些开始:

目前,我正在使用一个自制版本的通用渲染,该版本受到上述两个入门套件的启发,但它们现在已经过时了。

祝你好运!

Stijn de Witt
2016-04-14

如果您使用 Apache 作为 Web 服务器,则可以将其插入到 .htaccess 文件中:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-l
  RewriteRule . /index.html [L]
</IfModule>

我正在使用 react: "^16.12.0"react-router: "^5.1.2" 此方法是 Catch-all,可能是最简单的入门方法。

BrahimS
2016-11-14

这里的答案都非常有用。配置我的 Webpack 服务器以期望路由对我有用。

devServer: {
   historyApiFallback: true,
   contentBase: './',
   hot: true
},

historyApiFallback 为我解决了这个问题。现在路由工作正常,我可以刷新页面或直接输入 URL。无需担心 Node.js 服务器上的解决方法。这个答案显然只有在使用 Webpack 时才有效。

请参阅 我对 React-router 2.0 browserHistory 在刷新时不起作用 的回答,以了解为什么这样做是必要的更详细原因。

jmancherje
2016-05-26