我正在使用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);
});

当前回答

我通过修改webpack.config.js文件解决了这个问题。

我的新配置如下:

之前

output: {
  path: path.join(__dirname, '/build/static/js'),
  filename: 'index.js'
},


devServer: {
  port: 3000
}

output: {
  path: path.join(__dirname, '/build/static/js'),
  filename: 'index.js',
  publicPath: '/'
},


devServer: {
  historyApiFallback: true,
  port: 3000
}

其他回答

路由器可以用两种不同的方式调用,这取决于导航是发生在客户端还是服务器上。您已经将其配置为客户端操作。关键参数是run方法的第二个参数,即location。

当你使用React Router Link组件时,它会阻塞浏览器导航并调用transitionTo来做客户端导航。您正在使用HistoryLocation,因此它使用HTML5历史API通过在地址栏中模拟新URL来完成导航的错觉。如果您使用的是较旧的浏览器,这将不起作用。您将需要使用HashLocation组件。

When you hit refresh, you bypass all of the React and React Router code. The server gets the request for /joblist and it must return something. On the server you need to pass the path that was requested to the run method in order for it to render the correct view. You can use the same route map, but you'll probably need a different call to Router.run. As Charles points out, you can use URL rewriting to handle this. Another option is to use a Node.js server to handle all requests and pass the path value as the location argument.

例如,在Express.js中,它看起来是这样的:

var app = express();

app.get('*', function (req, res) { // This wildcard method handles all requests

    Router.run(routes, req.path, function (Handler, state) {
        var element = React.createElement(Handler);
        var html = React.renderToString(element);
        res.render('main', { content: html });
    });
});

请注意,正在传递请求路径以运行。为此,需要有一个服务器端视图引擎,可以将呈现的HTML传递给该引擎。在使用renderToString和在服务器上运行React时,还有许多其他注意事项。一旦页面在服务器上呈现,当你的应用程序在客户端加载时,它将再次呈现,并根据需要更新服务器端呈现的HTML。

如果你正在使用nginx托管,需要快速修复…

在你的nginx配置中添加以下代码:

location / {
  try_files $uri /index.html;
}

我用React路由器(Apache)为我的SPA找到了解决方案。只要在文件.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>

来源:Apache的React路由器配置

修复在刷新或直接调用URL时“不能GET /URL”的错误。

配置你的webpack.config.js,以期望给定的链接像这样的路由。

module.exports = {
  entry: './app/index.js',
  output: {
       path: path.join(__dirname, '/bundle'),
       filename: 'index_bundle.js',
       publicPath: '/'
  },

服务器端与客户端

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

With client-side routing, which is what React Router provides, things are less simple. At first, the client does not have any JavaScript code loaded yet. So the very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router, etc. Only when those scripts have loaded does phase 2 start. In phase 2, when the user clicks on the 'About us' navigation link, for example, the URL is changed locally only to http://example.com/about (made possible by the History API), but no request to the server is made. Instead, React Router does its thing on the client-side, determines which React view to render, and renders it. Assuming your about page does not need to make any REST calls, it's done already. You have transitioned from Home to About Us without any server request having fired.

所以基本上,当你点击一个链接时,一些JavaScript会在地址栏中操作URL,而不会引起页面刷新,这反过来会导致React路由器在客户端执行页面转换。

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

你的麻烦就从这里开始了。到目前为止,您只需要在服务器的webroot中放置一个静态HTML就可以了。但是,当从服务器请求其他url时,会出现404错误。这些相同的url在客户端工作得很好,因为有React路由器为你做路由,但它们在服务器端失败,除非你让你的服务器理解它们。

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

如果希望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的服务器。 许多棘手的环境问题(在服务器端使用窗口等) 陡峭的学习曲线

我应该用哪一种?

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

所以基本上,对我来说,这就是决定因素。如果我的服务器运行在Node.js上,我会采用同构;否则,我会去抓所有的解决方案,只是扩展它(混合解决方案)随着时间的推移和搜索引擎优化的需求。

如果你想了解更多关于React的同构(也称为“通用”)渲染,这里有一些关于这个主题的很好的教程:

用同构应用应对未来 在ReactJS中创建同构应用的痛苦和快乐 如何实现Node + React同构JavaScript &为什么它很重要

另外,为了让你开始学习,我建议你看看一些入门套件。选择一个与你的技术堆栈选择相匹配的(记住,React只是MVC中的V,你需要更多的东西来构建一个完整的应用程序)。先来看看Facebook自己发布的一篇文章:

创建React应用

或者从社区中选择一个。现在有一个很好的网站试图索引所有这些:

选择你最完美的React启动项目

我从这些开始:

React同构启动器 React Redux通用热示例

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

祝你好运!