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

React-router URLs don't work when refreshing or writing manually

我正在使用 React-router,当我点击 link 按钮时它工作正常,但是当我刷新我的网页时它没有加载我想要的东西。

例如,我在 localhost/joblist,一切都很好,因为我按 link 到达这里。但是if我刷新了我得到的网页:

Cannot GET /joblist

默认情况下,它不是这样工作的。最初我将 URL 作为 localhost/#/localhost/#/joblist,它们工作得很好。但是我不喜欢这种URL,所以试图抹掉那个#,我写道:

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

localhost/不会出现这个问题,这个总是returns我想要的。

这个应用程序是单页的,所以/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);
});

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

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

当您点击刷新时,您将绕过所有 React 和 React Router 代码。服务器收到 /joblist 的请求,它必须 return 一些东西。在服务器上,您需要将请求的路径传递给 run 方法,以便它呈现正确的视图。您可以使用相同的路线图,但您可能需要对 Router.run 进行不同的调用。正如查尔斯指出的那样,您可以使用 URL 重写来处理这个问题。另一种选择是使用 Node.js 服务器来处理所有请求并将路径值作为位置参数传递。

例如,在 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 });
    });
});

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

Server-side 对比 Client-side

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

有了 React Router 提供的 client-side 路由,事情就不那么简单了。起初,客户端还没有加载任何 JavaScript 代码。所以第一个请求总是发给服务器。然后 return 一个包含加载 React 和 React Router 等所需的脚本标签的页面。只有当这些脚本加载后,第 2 阶段才会开始。在阶段 2 中,当用户点击 'About us' 导航 link 时,例如 URL 将 locally only 更改为 http://example.com/about(通过 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 调用成为可能,它已经完成了。您已经从 主页 转换到 关于我们,而没有任何服务器请求解雇了。

所以基本上,当您单击 link 时,某些 Java 脚本 运行 会操纵地址栏中的 URL, 而不会导致页面刷新,这反过来会导致 React Router 在 client-side.

上执行页面转换

但现在考虑如果您 copy-paste 地址栏中的 URL 并 e-mail 给朋友会发生什么。您的朋友尚未加载您的网站。也就是说,她还处于阶段1。 运行她的机器上还没有 React Router。所以她的浏览器将向http://example.com/about.

发出服务器请求

这就是您的麻烦开始的地方。到目前为止,您只需在服务器的 webroot 中放置一个静态 HTML 即可。但是当从服务器 请求时,这会给所有其他 URLs 带来 404 错误。那些相同的 URLs 在 client-side 上工作正常 ,因为 React Router 正在为你做路由,但它们在 server-side除非你让你的服务器理解它们。

结合服务器和 client-side 路由

如果您希望 http://example.com/about URL 同时在服务器和 client-side 上工作,您需要在服务器和 client-side。很有道理吧?

这就是您开始选择的地方。解决方案的范围从完全绕过问题,通过 return 是 bootstrap HTML 的 catch-all 路由,到 full-on 同构方法,其中服务器和客户端 运行 相同 Java脚本代码。

完全绕过问题:哈希历史

使用 Hash History, instead of Browser History,关于页面的 URL 将如下所示: http://example.com/#/about

散列(#)符号后的部分不发送到服务器。所以服务器只看到 http://example.com/ 并按预期发送索引页面。 React Router 将选择 #/about 部分并显示正确的页面。

缺点

  • 'ugly'URLs
  • Server-side 这种方法无法进行渲染。就 search engine optimization (SEO) 而言,您的网站由一个页面组成,上面几乎没有任何内容。

Catch-all

使用这种方法,您确实可以使用浏览器历史记录,但只需在服务器上设置一个 catch-all,将 /* 发送到 index.html,实际上会给您带来大致相同的情况与哈希历史一样。然而,您确实拥有干净的 URLs,您可以稍后改进此方案,而不必使所有用户的收藏夹无效。

缺点

  • 设置更复杂
  • 仍然没有好的 SEO

混合

在混合方法中,您通过为特定路由添加特定脚本来扩展 catch-all 场景。您可以制作一些简单的 PHP 脚本到 return 您网站中包含内容的最重要页面,这样 Googlebot 至少可以看到您页面上的内容。

缺点

  • 设置起来更复杂
  • 只有那些你给予特殊待遇的路线才有好的 SEO
  • 复制用于在服务器和客户端呈现内容的代码

同构

如果我们使用 Node.js 作为我们的服务器,那么我们可以在两端使用 运行 相同的 Java 脚本代码呢?现在,我们在单个 react-router 配置中定义了所有路由,我们不需要复制渲染代码。这就是 'the holy grail' 可以这么说。服务器发送的标记与我们在客户端发生页面转换时最终得到的标记完全相同。此解决方案在 SEO 方面是最佳的。

缺点

  • 服务器必须(能够)运行Java脚本。我已经尝试将 Java 与 Nashorn 结合使用,但它对我不起作用。在实践中,它主要意味着您必须使用基于 Node.js 的服务器。
  • 许多棘手的环境问题(在 server-side 上使用 window,等等)
  • 陡峭的学习曲线

我应该使用哪个?

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

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

如果您想了解更多关于使用 React 进行同构(也称为 'universal')渲染的知识,有一些关于该主题的很好的教程:

此外,为了让您入门,我建议您查看一些入门工具包。选择一个与您的技术栈选择相匹配的(请记住,React 只是 MVC 中的 V,您需要更多的东西来构建完整的应用程序)。先看看Facebook自己发布的那篇:

或者从社区中选择一个。现在有一个不错的网站试图为所有这些网站编制索引:

我从这些开始:

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

祝你任务顺利!

如果您确实有 index.html 的后备方案,请确保在您的 index.html 文件中包含:

<script>
  System.config({ baseURL: '/' });
</script>

这可能因项目而异。

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

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

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

请参阅 my answer to React-router 2.0 browserHistory doesn't work when refreshing 以了解为什么需要这样做的更详细原因。

Webpack Dev Server 有一个选项可以启用此功能。打开 package.json 并添加 --history-api-fallback。 这个解决方案对我有用。

react-router-tutorial

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

我的主要 JSX 内容包含:

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

这对于第一个匹配 link 工作正常,但是当嵌套在该模型的详细信息页面上的 <Link> 表达式中的 :id 发生变化时,浏览器栏中的 URL 也会发生变化,但页面内容最初并未更改以反映 linked 模型。

问题是我在componentDidMount中使用了props.params.id来设置模型。该组件只安装一次,所以这意味着第一个模型是粘在页面上的模型,随后的 Links 更改道具,但页面看起来没有变化。

componentDidMountcomponentWillReceiveProps 中将模型设置为组件状态(它基于下一个道具)解决了问题,并且页面内容发生变化以反映所需的模型。

如果您使用 Apache 作为您的网络服务器,您可以将其插入到您的 .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" 此方法是万能的,可能是最简单的入门方法。

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

每个 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 页面返回给用户
  • 然后Java脚本将在客户端完成剩下的工作,因为客户端的URL仍然是http://example.com/about.

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

这可以解决您的问题。

我在生产模式下的 React 应用程序中也遇到了同样的问题。下面是解决问题的两种方法。

解决方案 1. 将路由历史记录更改为“hashHistory”而不是 browserHistory

<Router history={hashHistory} >
   <Route path="/home" component={Home} />
   <Route path="/aboutus" component={AboutUs} />
</Router>

现在使用命令构建应用程序

sudo npm run build

然后将构建文件夹放入 var/www/ 文件夹中。现在应用程序在每个 URL 中添加 # 标签后工作正常。喜欢

localhost/#/home
localhost/#/aboutus

解决方案 2:不使用 # 标签使用 browserHistory,

在您的路由器中设置您的历史记录 = {browserHistory}。现在使用 sudo npm run build.

构建它

您需要创建“conf”文件来解决404 not found page。 conf文件应该是这样的。

打开终端,输入以下命令

cd /etc/apache2/sites-available
ls
nano sample.conf

在其中添加以下内容。

<VirtualHost *:80>
    ServerAdmin admin@0.0.0.0
    ServerName 0.0.0.0
    ServerAlias 0.0.0.0
    DocumentRoot /var/www/html/

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    <Directory "/var/www/html/">
            Options Indexes FollowSymLinks
            AllowOverride all
            Require all granted
    </Directory>
</VirtualHost>

现在您需要使用以下命令启用 sample.conf 文件:

cd /etc/apache2/sites-available
sudo a2ensite sample.conf

然后它会要求您重新加载 Apache 服务器,使用

sudo service apache2 reload or restart

然后打开你的 localhost/build 文件夹并添加 .htaccess 文件,内容如下。

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^.*$ / [L,QSA]

现在应用程序运行正常。

注意:将 0.0.0.0 IP 地址更改为您的本地 IP 地址。

对于 React Router V4 用户:

如果您尝试通过其他答案中提到的哈希历史技术解决此问题,请注意

<Router history={hashHistory} >

不适用于 V4。请改用HashRouter

import { HashRouter } from 'react-router-dom'

<HashRouter>
  <App/>
</HashRouter>

参考:HashRouter

如果您在 IIS 中托管:将此添加到我的 webconfig 解决了我的问题

<httpErrors errorMode="Custom" defaultResponseMode="ExecuteURL">
    <remove statusCode="500" subStatusCode="100" />
    <remove statusCode="500" subStatusCode="-1" />
    <remove statusCode="404" subStatusCode="-1" />
    <error statusCode="404" path="/" responseMode="ExecuteURL" />
    <error statusCode="500" prefixLanguageFilePath="" path="/error_500.asp" responseMode="ExecuteURL" />
    <error statusCode="500" subStatusCode="100" path="/error_500.asp" responseMode="ExecuteURL" />
</httpErrors>

您可以为任何其他服务器进行类似的配置。

我遇到了同样的问题,this solution 为我们工作...

背景:

我们在同一台服务器上托管多个应用程序。当我们刷新服务器时,它不会理解在该特定应用程序的目标文件夹中的何处查找我们的索引。以上 link 将带您了解对我们有用的东西...

我们正在使用:

文件package.json:

"dependencies": {
  "babel-polyfill": "^6.23.0",
  "ejs": "^2.5.6",
  "express": "^4.15.2",
  "prop-types": "^15.5.6",
  "react": "^15.5.4",
  "react-dom": "^15.5.4",
  "react-redux": "^5.0.4",
  "react-router": "^3.0.2",
  "react-router-redux": "^4.0.8",
  "redux": "^3.6.0",
  "redux-persist": "^4.6.0",
  "redux-thunk": "^2.2.0",
  "webpack": "^2.4.1"
}

我的webpack.config.js文件:

/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const babelPolyfill = require('babel-polyfill');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
  template: __dirname + '/app/views/index.html',
  filename: 'index.html',
  inject: 'body'
});

module.exports = {
  entry: [
    'babel-polyfill', './app/index.js'
  ],
  output: {
    path: __dirname + '/dist/your_app_name_here',
    filename: 'index_bundle.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      query : {
          presets : ["env", "react", "stage-1"]
      },
      exclude: /node_modules/
    }]
  },
  plugins: [HTMLWebpackPluginConfig]
}

我的index.js文件:

import React from 'react'
import ReactDOM from 'react-dom'
import Routes from './Routes'
import { Provider } from 'react-redux'
import { createHistory } from 'history'
import { useRouterHistory } from 'react-router'
import configureStore from './store/configureStore'
import { syncHistoryWithStore } from 'react-router-redux'
import { persistStore } from 'redux-persist'

const store = configureStore();

const browserHistory = useRouterHistory(createHistory) ({
  basename: '/your_app_name_here'
})
const history = syncHistoryWithStore(browserHistory, store)

persistStore(store, {blacklist: ['routing']}, () => {
  console.log('rehydration complete')
})
// persistStore(store).purge()

ReactDOM.render(
    <Provider store={store}>
      <div>
        <Routes history={history} />
      </div>
    </Provider>,
  document.getElementById('mount')
)

我的app.js文件:

var express = require('express');
var app = express();

app.use(express.static(__dirname + '/dist'));
// app.use(express.static(__dirname + '/app/assets'));
app.set('views', __dirname + '/dist/your_app_name_here');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

app.get('/*', function (req, res) {
    res.render('index');
});

app.listen(8081, function () {
  console.log('MD listening on port 8081!');
});

如果您使用的是 Create React App:

针对许多主要托管平台,您可以找到 here on the Create React App page. For example, I use React Router v4 and Netlify 用于我的前端代码的解决方案,对此问题进行了很好的演练。只需将一个文件添加到我的 public 文件夹(“_redirects”),并在该文件中添加一行代码:

/*  /index.html  200

现在我的网站可以正确呈现像 mysite.com/pricing 这样的路径,当进入浏览器或有人点击刷新时。

生产堆栈:React、React Router v4、BrowswerRouter,Express.js、Nginx

  1. 用于漂亮 URL 的用户 BrowserRouter

    文件app.js

     import { BrowserRouter as Router } from 'react-router-dom'
    
     const App = () {
       render() {
         return (
             <Router>
                // Your routes here
             </Router>
         )
       }
     }
    
  2. 使用/*

    index.html添加到所有未知请求

    文件server.js

     app.get('/*', function(req, res) {
       res.sendFile(path.join(__dirname, 'path/to/your/index.html'), function(err) {
         if (err) {
           res.status(500).send(err)
         }
       })
     })
    
  3. 捆绑 Webpackwebpack -p

  4. 运行 nodemon server.jsnode server.js

您可能希望让 nginx 在服务器块中处理此问题并忽略第 2 步:

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

我刚才用 Create React App 做了一个网站,这里出现了同样的问题。

我使用 react-router-dom 包中的 BrowserRouting。我在 Nginx 服务器上 运行 并将以下内容添加到 /etc/nginx/yourconfig.conf 为我解决了这个问题:

location / {
  if (!-e $request_filename){
    rewrite ^(.*)$ /index.html break;
  }
}

这对应于将以下内容添加到 .htaccess 以防您是 运行 Apache:

Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

这好像也是Facebook自己推荐的解决方案,可以找到here

将此添加到 webpack.config.js:

devServer: {
    historyApiFallback: true
}

Preact 与 preact-router 的解决方案

适用于刷新和直接访问

对于那些通过 Google 发现这一点的人,这里有一个 preact-router + 哈希历史的演示:

const { h, Component, render } = preact; /** @jsx h */
const { Router } = preactRouter;
const { createHashHistory } = History;
const App = () => (
    <div>
        <AddressBar />

        <Router history={createHashHistory()}>
            <div path="/">
                <p>
                    all paths in preact-router are still /normal/urls.
                    using hash history rewrites them to /#/hash/urls
                </p>
                Example: <a href="/page2">page 2</a>
            </div>
            <div path="/page2">
                <p>Page Two</p>
                <a href="/">back to home</a><br/>
            </div>
        </Router>
    </div>
);

jsfiddle

如果您在 IIS 上托管 React 应用程序,只需添加一个 web.config 文件,其中包含:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <httpErrors errorMode="Custom" existingResponse="Replace">
            <remove statusCode="404" subStatusCode="-1" />
            <error statusCode="404" path="/" responseMode="ExecuteURL" />
        </httpErrors>
    </system.webServer>
</configuration>

这将告诉 IIS 服务器 return 客户端的主页而不是 404 错误,并且不需要使用哈希历史记录。

JavaScript SPA 和 Laravel

的 React 解决方案

接受的答案是对为什么会出现此类问题的最好解释。如前所述,您必须同时配置客户端和服务器端。

在您的 blade 模板中,包含 JavaScript 捆绑文件,确保使用 URL facade,如下所示:

<script src="{{ URL::to('js/user/spa.js') }}"></script>

在您的路线中,确保将其添加到 blade 模板所在的主要端点。例如,

Route::get('/setting-alerts', function () {
   return view('user.set-alerts');
});

以上是 blade 模板的主要端点。现在也添加一条可选路线,

Route::get('/setting-alerts/{spa?}', function () {
  return view('user.set-alerts');
});

发生的问题是首先加载 blade 模板,然后加载 React 路由器。因此,当您加载 '/setting-alerts' 时,它会加载 HTML 内容和 JavaScript 代码。

但是当您加载 '/setting-alerts/about' 时,它首先在服务器端加载。由于它在服务器端,因此此位置上没有任何内容,并且 returns 未找到。当你有那个可选的路由器时,它加载相同的页面并且 React Router 也被加载,然后 React 加载器决定显示哪个组件。

如果您通过 AWS Static 托管 React 应用程序 S3 Hosting and CloudFront

此问题由 CloudFront 以 403 Access Denied 消息响应而出现,因为它预计 /some/other/path 存在于我的 S3 文件夹,但该路径仅存在于 React 的路由内部 React Router.

解决方案是设置分发 错误页面 规则。转到 CloudFront 设置并选择您的分配。接下来,转到“错误页面”选项卡。单击“创建自定义错误响应”并添加 403 条目,因为这是我们获得的错误状态代码。

响应页面路径设置为/index.html并将状态代码设置为200。

最终结果以其简单性令我惊讶。提供索引页面,但 URL 保留在浏览器中,因此一旦 React 应用程序加载,它会检测 URL 路径并导航到所需的路径。

Error Pages 403 Rule

当你在刷新 DOM 组件后无法得到 403 错误时,这很简单。

只需在 Webpack 配置中添加这一行,'historyApiFallback: true '。这节省了我一整天的时间。

如果你在后端使用Express.js或其他框架,你可以添加类似下面的配置,并查看配置中的Webpack public路径。如果您使用的是 BrowserRouter,即使重新加载它也应该可以正常工作。

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

在您的 index.html 文件的 head 中,添加以下内容:

<base href="/">
<!-- This must come before the CSS and JavaScript code -->

然后,当运行 Webpack 开发服务器时,使用这个命令。

webpack-dev-server --mode development --hot --inline --content-base=dist --history-api-fallback

--history-api-fallback是重点

对于那些正在使用 IIS 10 的人,这是您应该做的事情。

请确保您使用的是 browserHistory。至于参考,我会给出路由的代码,但这不是重点。重要的是下面组件代码之后的下一步:

class App extends Component {
    render() {
        return (
            <Router history={browserHistory}>
                <div>
                    <Root>
                        <Switch>
                            <Route exact path={"/"} component={Home} />
                            <Route path={"/home"} component={Home} />
                            <Route path={"/createnewproject"} component={CreateNewProject} />
                            <Route path={"/projects"} component={Projects} />
                            <Route path="*" component={NotFoundRoute} />
                        </Switch>
                    </Root>
                </div>
            </Router>
        )
    }
}
render (<App />, window.document.getElementById("app"));

由于问题是 IIS 从客户端浏览器接收请求,它会将 URL 解释为请求页面,然后 returns 404 页面,因为没有可用的页。执行以下操作:

  1. 打开 IIS
  2. 展开服务器,然后打开站点文件夹
  3. 单击 website/application
  4. 转到错误页面
  5. 打开列表中的404错误状态项
  6. 将选项“将静态文件中的内容插入到错误响应中”改为“在此站点上执行 URL”并将“/”斜线值添加到 URL .

它现在可以正常工作了。

尝试使用以下代码在 public 文件夹中添加一个“.htaccess”文件。

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

RewriteRule ^ /index.html [L]

如果您使用的是 Firebase,您所要做的就是确保在 firebase.json[=30 中重写 属性 =] 文件在你的应用程序的根目录中(在托管部分)。

例如:

{
  "hosting": {
    "rewrites": [{
      "source":"**",
      "destination": "/index.html"
    }]
  }
}

关于该主题的进一步阅读:

我喜欢这种处理方式。尝试添加: yourSPAPageRoute/* 在服务器端解决这个问题。

我采用了这种方法,因为即使是本机 HTML5 历史记录 API 也不支持页面刷新时的正确重定向(据我所知)。

注意:选择的答案已经解决了这个问题,但我试图更具体一些。

Express Route

修复刷新或直接调用 URL 时的 "cannot GET /URL" 错误。

配置您的 webpack.config.js 以期待给定的 link 这样的路线。

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

我找到了 SPA with React Router (Apache). Just add this in file .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 configuration for React Router

我正在使用 Webpack,我遇到了同样的问题。

解决方案:

在您的 server.js 文件中:

const express = require('express');
const app = express();

app.use(express.static(path.resolve(__dirname, '../dist')));
  app.get('*', function (req, res) {
    res.sendFile(path.resolve(__dirname, '../dist/index.html'));
    // res.end();
  });

Why doesn't my application render after refreshing?

当我使用 ASP.NET Core 时,这样的事情对我有帮助:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        var url = Request.Path + Request.QueryString;
        return App(url);
    }

    [Route("App")]
    public IActionResult App(string url)
    {
        return View("/wwwroot/app/build/index.html");
    }

}

基本上在ASP.NET MVC 端,所有不匹配的路由都会按照startup.cs 中指定的那样落入Home/Index。在 Index 中可以获得原始请求 URL 并在需要的地方传递它。

文件startup.cs

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");

    routes.MapSpaFallbackRoute(
        name: "spa-fallback",
        defaults: new { controller = "Home", action = "Index" });
});

正在向 添加更多信息。

如果您正在使用 Firebase 并希望同时使用根路由和子目录路由,您需要在 firebase.json 中添加以下代码:

{
  "hosting": {
    "rewrites": [
      {
        "source": "*",
        "destination": "/index.html"
      },
      {
        "source": "/subdirectory/**",
        "destination": "/subdirectory/index.html"
      }
    ]
  }
}

示例:

您正在为客户建立网站。您希望网站所有者在 https://your.domain.com/management while the users of the website will navigate to https://your.domain.com.

中添加信息

在这种情况下,您的 firebase.json 文件将如下所示:

{
  "hosting": {
    "rewrites": [
      {
        "source": "*",
        "destination": "/index.html"
      },
      {
        "source": "/management/**",
        "destination": "/management/index.html"
      }
    ]
  }
}

假设您有以下 Home 路由定义:

<Route exact path="/" render={routeProps => (
   <Home routeProps={routeProps}/>
)}/>

{/* Optional catch-all router */}
<Route render={routeProps => (
       <div><h4>404 not found</h4></div>
)}/>

在您的 Home 组件中,您可以在 ComponentWillMount 事件中拦截请求,

const searchPath = this.props.routeProps.location.search;

if (searchPath){
    this.props.routeProps.history.push("/" + searchPath.replace("?",""));
}
else{
    /*.... originally Home event */
}

现在,您可以请求 /?joblist,而不是在 URL 调用 /joblist,并且 < Home> 组件会自动将请求重定向到 /joblist(注意路径中的额外问号)。

如果你来这里并且你正在使用 Apache and don’t have a .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>

这是我发现的前端解决方法,不需要在服务器上修改任何内容。

假设您的站点是 mysite.com,并且您有一个到我的站点的 React 路由。com/about。 在 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 (你挂载顶级组件的地方),你可以在其中使用路由的 render prop,而不是 component prop,并将 '/about' 作为在此示例中,支持 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>

感谢 (Pass props to a component rendered by React Router) 如何将道具传递给路由中的组件。

我们使用了Express.js' 404 handling approach.

// Path to the static React build directory
const frontend = path.join(__dirname, 'react-app/build');

// Map the requests to the static React build directory
app.use('/', express.static(frontend));

// All the unknown requests are redirected to the React SPA
app.use(function (req, res, next) {
    res.sendFile(path.join(frontend, 'index.html'));
});

它就像一个魅力。现场演示是 our site.

如果您使用“create-react-app”命令,

要生成 React 应用程序,则 package.json 文件需要进行一项更改,以便在浏览器中正确地 运行 生产构建 React SPA。打开文件 package.json 并向其中添加以下代码段,

"start": "webpack-dev-server --inline --content-base . --history-api-fallback"

这里最重要的部分是“--history-api-fallback”启用历史API回调。

如果您使用 Spring 或任何其他后端 API,有时您会收到 404 错误。所以在这种情况下,你需要在后端有一个控制器来将任何请求(你想要的)转发到 index.html 文件以由 react-router 处理。下面演示了使用 Spring.

编写的示例控制器
@Controller
public class ForwardingController {
    @RequestMapping("/<any end point name>/{path:[^\.]+}/**")
    public String forward(HttpServletRequest httpServletRequest) {
        return "forward:/";
    }
}

例如,如果我们将后端 API REST 端点作为“abc” (http://localhost:8080/abc/**),任何到达该端点的请求端点将重定向到 React 应用程序(index.html 文件),然后 react-router 将处理它。

使用 HashRouter 对我也适用于 Redux。只需简单地替换:

import {
    Router //replace Router
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
        <Provider store={Store}>
            <Router history={history}> // Replace here saying Router
                <Layout/>
            </Router>
        </Provider>
    </LocaleProvider>, document.getElementById("app"));

registerServiceWorker();

与:

import {
    HashRouter // Replaced with HashRouter
} from "react-router-dom";

ReactDOM.render(
    <LocaleProvider locale={enUS}>
        <Provider store={Store}>
            <HashRouter history={history}> //replaced with HashRouter
                <Layout/>
            </HashRouter>
        </Provider>
    </LocaleProvider>, document.getElementById("app"));

registerServiceWorker();

我通过更改文件 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
}

我正在使用 .NET Core 3.1 并且刚刚添加了扩展 MapFallbackToController:

文件Startup.cs

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        endpoints.MapFallbackToController("Index", "Home");
    });

另一种请求数据的方式,即使您直接指向 URL,也是让每个组件都有一个调用最后一个参数的方法,比如 /about/test.

然后到你的状态提供者,你有连接到你想要请求数据的组件的功能。

如果您 运行 它在 Google Bucket 上,对此的简单解决方案是考虑 'index.html' 错误(404 未找到)页面。

这样做:

  1. 在存储桶列表中,找到您创建的存储桶。
  2. 单击与存储桶关联的存储桶溢出菜单 (...),然后select 编辑网站配置。
  3. 在网站配置对话框中,也将主页指定为错误页面。

如果尝试从 IIS 虚拟目录(不是网站的根)提供 React 应用程序:

设置重定向时,“/”无法单独使用。对我来说,它也需要虚拟目录名称。这是我的网络配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <defaultDocument>
            <files>
                <remove value="default.aspx" />
                <remove value="iisstart.htm" />
                <remove value="index.htm" />
                <remove value="Default.asp" />
                <remove value="Default.htm" />
            </files>
        </defaultDocument>
        <rewrite>
            <rules>
                <rule name="React Routes" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                        <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/YOURVIRTUALDIRECTORYNAME/" />
                </rule>
            </rules>
        </rewrite>
        <directoryBrowse enabled="false" />
        <httpErrors errorMode="Custom" defaultResponseMode="ExecuteURL">
            <remove statusCode="500" subStatusCode="100" />
            <remove statusCode="500" subStatusCode="-1" />
            <remove statusCode="404" subStatusCode="-1" />
            <remove statusCode="403" subStatusCode="18" />
            <error statusCode="403" subStatusCode="18" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="404" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="500" prefixLanguageFilePath="" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
            <error statusCode="500" subStatusCode="100" path="/YOURVIRTUALDIRECTORYNAME/" responseMode="ExecuteURL" />
        </httpErrors>
    </system.webServer>
</configuration>

除了 web.config 文件之外,React 应用程序本身也需要一些更改:

在文件 package.json 中,您需要添加一个 'homepage' 条目:

{
  "name": "sicon.react.crm",
  "version": "0.1.0",
  "private": true,
  "homepage": "/YOURVIRTUALDIRECTORYNAME/",
  "dependencies": {
...

我将基本名称添加到我传递到路由器以访问历史记录的浏览器历史记录对象中:

import  {createBrowserHistory } from 'history';

export default createBrowserHistory({
    //Pass the public URL as the base name for the router basename: process.env.PUBLIC_URL
});

我还在我的 React 路由器上添加了这个 属性 文件 App.js:

<Router history={history} basename={process.env.PUBLIC_URL}>

最后,在文件 index.html 中,我在 'title' 标签上方添加了以下选项卡:

<base href="%PUBLIC_URL%/">

可能有些步骤不是必需的,但这对我来说似乎已经完成了。我不知道如何在不重新编译的情况下在站点的根目录或虚拟目录中将其设置为 运行,因为 package.json 中的主页在构建后无法交换据我所知。

在后端使用 Express.js 并在前端使用 React(没有 react-create-app)和 reach/router,显示正确的 reach/router 路由 React 组件,并在按 Enter[= 时将菜单 link 设置为活动样式57=] 在地址栏中,例如 http://localhost:8050/pages.

请检查以下内容,或直接转到我的存储库 https://github.com/nickjohngray/staticbackeditor。所有代码都在那里。

Webpack:

设置代理。这允许来自端口 3000 (React) 的任何调用来调用服务器, 包括调用 get index.html 或按下 Enter 键时地址栏中的任何内容。它还允许调用 API 路由,以获取 JSON 数据。

喜欢 await axios.post('/api/login', {email, pwd}):

devServer: {
    port: 3000,
    open: true,
    proxy: {
      '/': 'http://localhost:8050',
    }
  }

设置Express.js路线

app.get('*', (req, res) => {
    console.log('sending index.html')
    res.sendFile(path.resolve('dist', 'index.html'))

});

这将匹配来自 React 的任何请求。它只是 returns index.html 页面,它在我的 dist 文件夹中。当然,这个页面还有更多 single-page React 应用程序。 (请注意任何其他路线都应出现在此之上,在我的情况下,这些是我的 API 路线。)

反应路线

<Router>
    <Home path="/" />
    <Pages path="pages"/>
    <ErrorPage path="error"/>
    <Products path="products"/>
    <NotFound default />
</Router>

这些路由是在我的Layout组件中定义的,当路径匹配时会加载相应的组件。

React Layout 构造器

constructor(props) {
    super(props);

    this.props.changeURL({URL: globalHistory.location.pathname});
}

布局构造函数在加载后立即调用。在这里,我调用我的菜单监听的 redux 操作 changeURL,因此它可以突出显示正确的菜单项,如下所示:

菜单代码

<nav>
    {this.state.links.map( (link) =>
    <Link className={this.getActiveLinkClassName(link.path) } to={link.path}>
      {link.name}
    </Link>)}
</nav>

我正在使用 React.js + Webpack 模式。我在 package.json 文件中添加了 --history-api-fallback 参数。然后页面刷新正常。

每次我更改代码时,网页都会自动刷新。

"scripts": {
  "start": "rimraf build && cross-env NODE_ENV='development' webpack --mode development && cross-env NODE_ENV=development webpack-dev-server --history-api-fallback",
  ...
}

前面的回答都没有解决你想用proxy pass的浏览器路由器,以及不能用root的问题。

对我来说,解决方案非常简单。

假设您有一个 URL 指向某个端口。

location / {
  proxy_pass http://127.0.0.1:30002/;
  proxy_set_header    Host            $host;
  port_in_redirect    off;
}

现在由于浏览器路由器的原因,子路径被破坏了。但是,你知道子路径是什么。

解决这个问题的方法是什么?对于子路径 /contact

# Just copy paste.
location /contact/ {
  proxy_pass http://127.0.0.1:30002/;
  proxy_set_header    Host            $host;
}

我尝试过的其他方法均无效,但这个简单的修复有效。

在我的例子中,当我在其中使用参数时 URL 没有加载。

作为快速修复,我添加了 <base href="<yourdomain/IP>"></base> 在构建文件夹中 index.html 文件的 标签下。</p> <p>这正好解决了我的问题。</p> </section> <section class="answer"> <p>如果您使用 <a href="https://en.wikipedia.org/wiki/Nginx" rel="nofollow noreferrer">nginx</a> 托管并且需要快速修复...</p> <p>将以下行添加到 <em>location</em> 块内的 nginx 配置中:</p> <pre><code>location / { try_files $uri /index.html; } </code></pre> </section> <section class="answer"> <p>我在 <a href="https://en.wikipedia.org/wiki/Electron_(software_framework)" rel="nofollow noreferrer">Electron</a> 中遇到这个问题,当时我将 React 用于前端,<code>react-router-dom</code> 用于路由。</p> <p>我用 <code>HashRouter</code> 替换了 <code>BrowserRouter</code> 并修复了。</p> <p>这是一个简单的例子:</p> <pre><code>import { HashRouter as Router, Switch, Route, } from "react-router-dom"; </code></pre> </section> <section class="answer"> <p>HashRouter 将是一个简单的实现,</p> <pre><code>import {HashRouter as Router,Switch,Route,Link} from 'react-router-dom'; function App() { return ( <Router> <Switch> <Route path="/" exact component={InitialComponent} /> <Route path="/some" exact component={SomeOtherComponent} /> </Switch> </Router> ); } </code></pre> <p>在浏览器中会是这样的—— <strong>http:localhost:3000/#/</strong> , <strong>http:localhost:3000/#/some</strong></p> </section> <section class="answer"> <p>您可以将 Vercel 的托管用于您的 React 应用程序,并在您的 React 应用程序中使用与浏览器路由相同的旧路由方式。</p> <p>您需要在项目的根目录下添加一个<em>vercel.json</em>文件,并向其中添加以下代码:</p> <pre><code>{ "rewrites": [ { "source": "/((?!api/.*).*)", "destination": "/index.html" } ] } </code></pre> <p>这很好用。</p> </section> <section class="answer"> <p>我正在使用 <strong>ASP.NET Core</strong> 和 <strong>React</strong>。生产环境中手动路由和刷新路由问题的解决方法是在 ASP.NET Core 的 <strong>main project</strong> 的根目录下创建 <code>web.config</code> 文件,它将配置在生产服务器上路由。</p> <p>文件在项目中的位置:</p> <p><WBIMG:16833743-1.png></p> <p><code>web.config</code> 文件的内容:</p> <pre><code><?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Rewrite Text Requests" stopProcessing="true"> <match url=".*" /> <conditions> <add input="{HTTP_METHOD}" pattern="^GET$" /> <add input="{HTTP_ACCEPT}" pattern="^text/html" /> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite> </system.webServer> </configuration> </code></pre> </section> <section class="answer"> <p>通过在我的 <a href="https://en.wikipedia.org/wiki/Nginx" rel="nofollow noreferrer">nginx</a> 配置中进行以下简单更改,我能够克服硬刷新 Web 应用程序中断和手动 URL 键入 Web 应用程序中断的问题。</p> <ul> <li>反应版本:17.0.2</li> <li>网络服务器:nginx</li> <li>OS:<a href="https://en.wikipedia.org/wiki/Ubuntu_version_history#Ubuntu_20.04_LTS_(Focal_Fossa)" rel="nofollow noreferrer">Ubuntu Server 20.04</a>(焦点窝)</li> </ul> <p><strong>之前</strong></p> <pre><code>location / { try_files $uri $uri/ =404; } </code></pre> <p><strong>之后</strong></p> <pre><code>location / { try_files $uri /index.html; } </code></pre> <p>可能还有其他解决方案,但这对我来说真的既快又省时。</p> </section> </div> </div> <div class="line"></div> <div id="footer">©2023 WhoseBug</div> </body> </html>