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

当前回答

这里有一个简单、清晰和更好的解决方案。如果你使用网络服务器,它就可以工作。

每个web服务器都有能力在出现HTTP 404时将用户重定向到错误页面。要解决此问题,需要将用户重定向到索引页。

如果您使用Java基础服务器(Tomcat或任何Java应用程序服务器),解决方案可能如下:

web . xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!-- WELCOME FILE LIST -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- ERROR PAGES DEFINITION -->
    <error-page>
        <error-code>404</error-code>
        <location>/index.jsp</location>
    </error-page>

</web-app>

例子:

得到http://example.com/about web服务器抛出HTTP 404,因为此页面在服务器端不存在 错误页面配置通知服务器将index.jsp页面发送回用户 那么JavaScript将在客户端完成剩下的工作,因为客户端的URL仍然是http://example.com/about。

就是这样。不再需要魔法:)

其他回答

如果你正在使用Apache并且没有。htaccess文件,这是一个适合我的配置文件:

sites-enabled/somedomain.com.conf

<VirtualHost *:80>
    ServerName somedomain.com
    ServerAlias *.somedomain.com
    DocumentRoot /www/somedomain.com/build

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /www/somedomain.com/build/index.html [L,NC,QSA]

</VirtualHost>

服务器端与客户端

首先要理解的是,现在有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通用热示例

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

祝你好运!

下面是我发现的一个前端解决方案,不需要修改服务器上的任何内容。

假设你的网站是mysite.com,你有一个到mysite.com/about的React Route。 在index.js中,你可以挂载你的顶级组件,你可以像这样放另一个Router:

ReactDOM.render(
<Router>
    <div>
        <Route exact path="/" component={Home} />
        <Route exact path="/about"
            render={(props) => <Home {...props} refreshRout={"/about"}/>}
        />
    </div>
</Router>,

我假设原始路由器位于虚拟DOM的顶级组件之下。如果你正在使用Django,你还必须在你的。url中捕获url:

urlpatterns = [
       path('about/', views.index),
]

不过,这将取决于您使用的后端。请求mysite/about会让你进入index.js(在那里你挂载顶级组件),在那里你可以使用Route的渲染道具,而不是组件道具,并将'/about'作为道具传递给Home组件,在这个例子中。

在Home中,在componentDidMount()或useEffect()钩子中,执行以下操作:

useEffect() {
   //check that this.props.refreshRoute actually exists before executing the
   //following line
   this.props.history.replace(this.props.refreshRoute);
}

我假设你的Home组件是这样渲染的:

<Router>
   <Route exact path="/" component={SomeComponent} />
   <Route path="/about" component={AboutComponent} />
</Router>

关于如何在路由中向组件传递道具,请参考(将道具传递给React路由器渲染的组件)。

我还没有使用服务器端渲染,但我遇到了与OP相同的问题,其中Link在大多数时候似乎工作正常,但当我有一个参数时就失败了。我将在这里记录我的解决方案,看看它是否对任何人都有帮助。

我的主要JSX内容包括:

<Route onEnter={requireLogin} path="detail/:id" component={ModelDetail} />

这对于第一个匹配的链接很有效,但是当嵌套在该模型的详细页面上的< link >表达式中的:id发生变化时,浏览器栏中的URL会发生变化,但页面的内容最初并没有改变以反映链接的模型。

问题是我已经使用props.params.id在componentDidMount中设置了模型。组件只挂载一次,因此这意味着第一个模型是粘贴在页面上的模型,随后的链接更改了道具,但保持页面看起来不变。

在componentDidMount和componentWillReceiveProps的组件状态中设置模型(其中它基于下一个道具)可以解决问题,并且页面内容更改以反映所需的模型。

如果你在后端使用Express.js或其他框架,你可以添加如下所示的类似配置,并在配置中查看Webpack公共路径。如果你正在使用BrowserRouter,即使在重载时它也应该工作得很好。

expressApp.get('/*', (request, response) => {
    response.sendFile(path.join(__dirname, '../public/index.html'));
});