当前位置: 首页 > 知识库问答 >
问题:

express - React,使用BrowserRouter,路由携带参数时,刷新时页面出现空白?

诸彬郁
2024-05-13

环境

在写一个react + express的项目,前端使用了react-router-dom@5.3.4的BrowserRouter。

  • 对于普通的路由,如/dashboard这样的,刷新没问题
  • 对于携带参数的路由,如jobEdit/xxx,从页面点击跳转没问题,但刷新后页面空白。

项目结构是这样的:

- client    - build  // 打包的目录        - index.html        - xxx.js- server    - app.js

情况1:生产模式

错误展示

服务端的路由配置如下:

// app.js// 挂载请求路由// ...// 匹配其他路由app.get('*', (req, res) => {  res.sendFile(     path.resolve(__dirname, '..', 'client', 'build', 'index.html')  );});

当访问这个路由/jobEdit/xxx时,一切正常。然后!我刷新了!发送了这个请求:
image.png

它返回了一个html给我。如下图,它居然多了一个jobEdit文件夹。但我预想是它应该在根目录下返回html给我:
image.png

上面的请求很明显发送给服务端了,但/jobEdit/xxx原本是在前端配置的。我希望它保持在根目录的index.html中,然后使用前端配置的路由,不需要向服务端发送请求。

// client端的路由 { path: "/jobEdit/:id",  key: "jobEdit", component: <JobEdit/> },

错误的后果就是页面空白

解决思路

查看控制台的错误,服务端发给我的html中引用的js路径出现了问题。
image.png

1. 修改webpack配置

output: {     path: path.OUTPUT,     filename: '[name].[contenthash].js',     clean: true,+    publicPath: '/',  },

控制台的文件结构如下,一切正常:
image.png

它成功的把返回的index.html的引用路径切到了根目录的js。刷新之后也正常。

但是!会从零开始请求一堆东西:
image.png

这跟我的预期还是有很大差异。

2. 服务端重定向

我不想要多出来的这个jobEdit文件夹,于是我想到了下面的重定向方法。

app.get('/', (req, res) => {  res.sendFile(     path.resolve(__dirname, '..', 'client', 'build', 'index.html')  );});app.get('*', (req, res) => {  res.redirect('/');});

好的,现在文件结构让人舒服了:
image.png
但是它有两个问题:

  1. 每发送一个api请求,都会有下面的请求,返回的是根目录的html。很奇怪,这是为什么?有什么解决方法吗?
    image.png
  2. 每次刷新都不会保留曾经的路由,比如在/jobEdit/xxx,刷新之后又回到前端设置的最初路由了。

情况2:调试模式

我在client中代理服务端,登录 -> 跳转到/jobEdit/xxx 都正常,而当我在这个页面重新刷新的时候,就又回到了情况1。其中为了正常运行,webpack进行了如下设置:

   devServer: {      historyApiFallback: true,   },

补充信息

前端部分

webpack的配置:

      output: {         path: path.OUTPUT, // build文件夹         filename: '[name].[contenthash].js',         clean: true,      },

根目录

import './style.css';import { createRoot } from 'react-dom/client';import App from './App';import Error from './views/Error'import Login from './views/Login';import Register from './views/Register';import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';import { Toaster } from 'react-hot-toast';const root = createRoot(document.getElementById('root'));root.render(   <>      <Toaster />      <Router>         <Switch>            <Route path="/login" exact>               <Login />            </Route>            <Route path="/register" exact>               <Register />            </Route>            <Route path="/" component={App} />            <Route path='*' component={Error} />         </Switch>      </Router>   </>);

App.tsx

import { MainRoutes } from '@/routes';import { Header, LeftNav, MainContent } from '@/components/Layout';export default function App() {   return (      <div>            <Header />            <div className="flex">               <LeftNav />               <MainContent>                  <MainRoutes />               </MainContent>            </div>         </div>   );}

MainRoutes.tsx

import { Redirect, Route, Switch } from 'react-router-dom';import NotFound from '@/views/NotFound';import PrivateRoute from '@/routes/PrivateRoute';import Dashboard from "@/views/Dashboard";import JobList from '@/views/JobList'import JobAdd from '@/views/JobAdd'import JobEdit from '@/views/JobEdit'import Profile from '@/views/Profile'const MainList = [    { path: "/dashboard", key: "dashboard", title: "Dashboard", component: <Dashboard/> },    { path: "/jobs", key: "jobs", title: "All Jobs", component: <JobList/> },    { path: "/jobAdd",  key: "jobAdd", title: "Add Job", component: <JobAdd/> },    { path: "/jobEdit/:id",  key: "jobEdit", title: "Update Job", component: <JobEdit/> },    { path: "/profile", key: "profile", title: "Profile", component: <Profile/> },];const MainRoutes = () => {   return (      <Switch>         <Route exact path="/" key="redirect">            <Redirect to="/dashboard" />         </Route>         {MainList.map((item) => (            <PrivateRoute path={item.path} key={item.key}>               {item.component}            </PrivateRoute>         ))}         <Route key="not-found">            <NotFound />         </Route>      </Switch>   );};export default MainRoutes;

PrivateRoute.tsx

const PrivateRoute = ({ children, ...rest }) => {   const token = localStorage.getItem('token');   return (      <Route         {...rest}         render={({ location }) =>            token ? (               children            ) : (               <Redirect                  to={{                     pathname: '/login',                     state: { from: location },                  }}               />            )         }      />   );};export default PrivateRoute

接口返回授权失败是也进行了跳转设置:

.catch((err) => {        if (err.response.status === 401) {            toast.error('用户凭证出现问题,请重新登录')            location.href = '/login'            throw new Error('用户凭证出现问题,请重新登录')        }        return data    })

后端部分

app.use(express.static(path.resolve(__dirname, '..', 'client', 'build')));// 注册路由const mountRoutes = require('./utils/mouteRouter');mountRoutes(app);app.get('*', (req, res) => {   res.sendFile(      path.resolve(__dirname, '..', 'client', 'build', 'index.html')   );});app.use(notFoundMD); // 匹配其他未定义app.use(errCaptureMiddleware); // 全局错误捕获

mouteRouter.js

const authRoute = require('../routes/auth');const jobsRoute = require('../routes/jobs');const userRoute = require('../routes/user');const authMiddleware = require('../middleware/auth');const apiLimiterMiddleware = require('../middleware/api-limiter');const mountRoutes = (app) => {   app.use('/api/v1/auth', apiLimiterMiddleware, authRoute);   app.use('/api/v1/jobs', authMiddleware, jobsRoute);   app.use('/api/v1/user', authMiddleware, userRoute);};module.exports = mountRoutes;

预期效果

有什么办法,可以让我在刷新/jobEdit/xxx时,实现一下效果:

  • 不会重新请求index.html
  • 在原来的html中,跳转到前端配置的/jobEdit/xxx

后续测试

我尝试刷新那些正常的路由如/dashboard,观察控制台:
image.png
image.png
跟我上面情况1的修改webpack配置的呈现一模一样。(通过webpack配置index.html引用资源的路径,使其指向根目录)

同理,对于client的代理模式,如果使用browserRouter除了需要设置historyApiFallback,还要从根目录获取其他资源(默认情况下index.html是从同级别的路径获取资源)

module.exports = {   devServer: {      static: '/',      historyApiFallback: true   },};

难道这就是最终的解决方案吗?我还是有些孤陋寡闻了

共有1个答案

鄂育
2024-05-13

解决方案

针对您描述的问题,解决方案主要集中在确保服务端能够正确处理前端路由,并且前端能够正确加载资源。以下是一些建议:

服务端配置

确保服务端配置能够正确响应前端路由请求,并返回 index.html 文件。通常,这意味着对所有未找到的资源请求都返回 index.html

// 确保 express.static 在所有路由之前,以处理静态文件请求app.use(express.static(path.resolve(__dirname, '..', 'client', 'build')));// 所有其他未找到的路由都应该返回 index.htmlapp.get('/*', (req, res) => {   res.sendFile(path.resolve(__dirname, '..', 'client', 'build', 'index.html'));});

注意,这里使用了 /* 而不是 *。这是因为 * 也会匹配到文件路径(比如 jobEdit/xxx/file.png),而 /* 则只匹配到路由路径。

前端配置

确保前端构建时 publicPath 设置为正确的值,以便所有资源引用都是正确的。在 webpack 配置中,您已经设置了 publicPath: '/',这是正确的,确保它指向正确的根路径。

刷新页面时保持路由

当刷新页面时,由于浏览器会向服务器发送请求,您需要确保服务端配置能返回 index.html。然而,浏览器的 URL 应该保持原样(比如 /jobEdit/xxx),这样 React Router 可以在客户端接管并渲染正确的组件。

由于您已经设置了服务端返回 index.html,现在需要确保 React Router 能够根据 URL 渲染正确的组件。这通常在客户端通过 BrowserRouter 和相应的路由配置来完成。

确保在客户端的路由配置中使用了 BrowserRouter 而不是 HashRouter,因为 BrowserRouter 使用 HTML5 History API 来处理 URL,这允许您使用普通的 URL 路径,而不是带有井号(#)的哈希路径。

避免不必要的资源请求

如果每次刷新页面都会重新请求很多资源,那可能是因为缓存控制不当或资源 URL 发生了变化。检查 webpack 配置的 output.filename,确保它使用了内容哈希([contenthash]),这样当文件内容没有变化时,URL 就不会变,从而可以利用浏览器缓存。

此外,检查服务端的静态文件缓存头设置,确保合适的缓存策略被应用。

总结

综上所述,您应该:

  1. 确保服务端对所有未找到的路由请求返回 index.html
  2. 确保前端构建配置正确,特别是 publicPath
  3. 使用 BrowserRouter 来处理客户端路由。
  4. 确保资源被正确缓存,避免不必要的重新请求。

通过遵循这些步骤,您应该能够在刷新带有参数的路由时保持页面内容不变,并且不会重新请求整个 index.html 文件。

 类似资料:
  • router配置 点击进去页面时 这样点击后页面会出现但是刷新或者跳转别的路由会报错 刷新报错: 跳转其他路由报错: 如何解决?

  • 问题内容: 我有以下webpack配置文件: 我想要做的就是在本地主机上运行我的应用程序,但是当我打以下命令时:“ http:// localhost:8080 / src / client / home ”(根据我的routes.jsx和运行webpack-dev- server之后) 我懂了 “无法获取/ src / client / home”。 问题答案: 您在路线中提到的第一件事是具有p

  • 问题内容: 我有一个正在使用的应用程序,每当刷新页面时都会遇到问题。我正在使用嵌套视图(命名视图)来构建应用程序。每当我刷新页面时,都不会重新加载当前状态,只会使页面空白。 页面加载等于 我正在通过文件并通过状态循环从文件中读取导航。这就是我可以显示的内容: 对于嵌套json对象中的每个项目,将执行此代码。如果还有其他有用的方法,请告诉我。 问题答案: 有一个问题:AngularJS-UI-rou

  • 我使用的是带有一个简单的路由器插座元件的Angular2 RC5和Angular2/router。我有一个可观察的,在视图中运行良好,但当你刷新页面时(F5或CTRL-F5),它就不起作用了。我在Chrome(最新版本)和IE11(可能还有其他浏览器)中都有这个功能。目前我有一个变通方法,但我不明白为什么我的更改没有自动反映在视图中。 该项目相当大,因此请尽量减少: 在MyService.ts 在

  • 问题内容: 我正在使用Express,它从静态目录加载AngularJS。通常,我会要求Express为其提供我和所有正确的Angular文件等。在我的Angular应用中,我有这些路由设置,它们替换了内容: 在我的主页上,我有到的链接,该链接将成功加载模板并将我定向到或指定的任何ID。问题是当我尝试将浏览器定向到页面或刷新页面时,请求将发送到Express / Node服务器,该服务器返回。 我

  • 本文向大家介绍AngularJS实现页面定时刷新,包括了AngularJS实现页面定时刷新的使用技巧和注意事项,需要的朋友参考一下 有时我们在前端可能会有这样的需求: 1、每隔一段时间刷新一下页面中的数据 2、根据需要可以暂停和启用刷新 接下来我们就来看下AngularJS的实现方法: 首先我们了解到AngularJS中$interval可以用来处理间歇性处理一些事情,那么我们的间歇性刷新就使用i