React Router v6生产环境Link/navigate失效问题求助
React Router v6.12.1生产环境路由跳转异常排查
问题描述
生产环境使用React Router v6.12.1时,点击<Link>组件或调用navigate()函数后,浏览器URL路径正常变化,但页面完全无响应;刷新页面后,能正常跳转到对应视图。本地开发环境未使用Docker构建镜像直接运行时,不存在此问题。
可能的原因
- Nginx缺少SPA路由重写配置:生产环境用Nginx托管React静态资源,
createBrowserRouter使用HTML5 History模式,访问非根路径时,Nginx会尝试查找对应静态文件,找不到就返回404,导致React Router无法接管路由。必须在Nginx配置中添加规则,把所有请求转发到index.html。 - 自定义Guard组件未响应路由变化:路由中大量使用的
Guard组件如果没监听路由参数/路径变化,或者内部状态未在路由切换时更新,会导致页面不重新渲染。检查Guard是否在路由变化时重新执行权限逻辑,是否正确触发组件更新。 - Router实例被重复初始化:
renderApp函数被KeycloakService.initKeycloak和TranslationService.initTranslations分别调用,可能导致RouterProvider重复渲染,路由内部状态混乱。需确保renderApp只执行一次,或通过状态控制避免重复初始化。 - 生产构建缓存问题:如果构建后的静态资源没有添加哈希后缀,浏览器可能缓存旧代码,导致路由逻辑未更新。检查React构建配置,确保生成的静态资源文件名包含哈希值,强制浏览器加载最新资源。
相关配置代码
路由配置
export const router = createBrowserRouter([ { path: '/', element: <RootPage/> }, { path: '/dashboard', element: <Guard fallback={<DashboardAppShellSkeleton/>} uri={'/dashboard/groups'} unwantedRoles={['GYM']}><DashboardAppShellComponent/></Guard>, children: [ { path: 'memberships', element: <Guard fallback={<CustomerMembershipsSkeleton/>} uri={'/dashboard'} unwantedRoles={['GYM']}><MyMembershipsPage/></Guard>, }, { path: 'bookings', element: <Guard fallback={<CustomerMembershipsSkeleton/>} uri={'/dashboard'} unwantedRoles={['GYM']}><CustomerBookingsPage/></Guard>, }, { path: 'groups', element: <Guard fallback={<CustomerGroupsSkeleton/>} uri={'/dashboard'} unwantedRoles={['GYM']}><CustomerGroupsPage/></Guard>, }, { path: 'groups/:groupId', element: <Guard fallback={<CustomerSelectedGroupSkeleton/>} uri={'/dashboard'} unwantedRoles={['GYM']}><CustomerSelectedGroupPage/></Guard>, }, { path: "my-profile", element: <Guard fallback={<CustomerMyProfileSkeleton/>} uri={'/dashboard/my-profile'} unwantedRoles={['GYM']}><CustomerMyProfilePage/></Guard> } ] }, { path: '/admin', element: <Guard fallback={<AdminAppShellSkeleton/>} uri={'/admin/members'} wantedRoles={['GYM']}><AdminAppShellComponent/></Guard>, children: [ { path: "home", element: <Guard uri={'/admin/home'} wantedRoles={['GYM']}><HomePage/></Guard> }, { path: "members", element: <Guard fallback={<MembersSkeletonPage/>} uri={'/admin/members'} wantedRoles={['GYM']} ><MembersPage/></Guard>, }, { path: "members/:memberId", element: <Guard fallback={<SelectedMemberSkeleton/>} uri={'/admin/members'} wantedRoles={['GYM']} ><SelectedMemberPage/></Guard> }, { path: "memberships", element: <Guard fallback={<MembershipsSkeletonPage/>} uri={'/admin/memberships'} wantedRoles={['GYM']} ><MembershipsPage/></Guard> }, { path: "memberships/:templateId", element: <Guard fallback={<SelectedMembershipSkeletonPage/>} uri={'/admin/memberships'} wantedRoles={['GYM']} ><SelectedMembershipPage/></Guard> }, { path: "classes", element: <Guard fallback={<ClassesSkeletonPage/>} uri={'/admin/classes'} wantedRoles={['GYM']} ><ClassesPage/></Guard>, }, { path: "classes/:classId", element: <Guard fallback={<SelectedClassSkeleton/>} uri={'/admin/classes'} wantedRoles={['GYM']} ><SelectedClassPage/></Guard> }, { path: "classes/:classId/groups/:groupId", element: <Guard fallback={<SelectedGroupSkeleton/>} uri={'/admin/classes'} wantedRoles={['GYM']} ><SelectedGroupPage/></Guard> }, { path: "my-profile", element: <Guard fallback={<MyProfileSkeleton/>} uri={'/admin/my-profile'} wantedRoles={['GYM']}><MyProfilePage/></Guard> }, { path: "statistics", element: <Guard fallback={<StatisticsSkeleton/>} uri={'/admin/my-profile'} wantedRoles={['GYM']}><StatisticsPage/></Guard> } ] }, { path: '/check-in-screen', element: <Guard fallback={<CheckInScreenSkeleton/>} uri={'/admin/members'} wantedRoles={['GYM']} ><CheckInScreenPage/></Guard>, }, { path: '/terms-conditions', element: <Suspense fallback={<AdminAppShellSkeleton/>}><TermsConditionsPage/></Suspense>, }, { path: '/gym-terms', element: <Suspense fallback={<AdminAppShellSkeleton/>}><GymTermsPage/></Suspense>, }, { path: '*', element: <Suspense fallback={<AdminAppShellSkeleton/>}><NotFoundPage/></Suspense>, } ] )
React Router初始化代码
import React from 'react'; import ReactDOM from 'react-dom/client'; import reportWebVitals from './reportWebVitals'; import { ProvidersComponent } from "./features/admin/layout"; import KeycloakService from "./common/utils/auth/keycloak"; import { RouterProvider } from "react-router-dom"; import { router } from "./app.routing"; import HttpService from "./common/utils/rest/HttpService"; import TranslationService from "./i18n"; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); const renderApp = () => root.render( <ProvidersComponent> <RouterProvider router={router}/> </ProvidersComponent> ); KeycloakService.initKeycloak(renderApp); TranslationService.initTranslations(renderApp) HttpService.configure();
Dockerfile
# Use the latest LTS version of Node.js as the base image FROM node:18.15.0 as builder # Set the working directory to /app inside the container WORKDIR /app # Copy app files COPY . . # Install dependencies (yarn.lock ensures the exact versions of dependencies are installed) RUN yarn install --frozen-lockfile # Build the app RUN yarn run build # Bundle static assets with nginx FROM nginx:1.21.0 as production # Set the environment variable ENV NODE_ENV local # Copy built assets from `builder` image COPY --from=builder /app/build /usr/share/nginx/html # Add your nginx.conf COPY nginx.conf /etc/nginx/conf.d/default.conf # Expose port EXPOSE 80 # Start nginx CMD ["nginx", "-g", "daemon off;"]
内容的提问来源于stack exchange,提问作者vkatsou
相关产品推荐
相关产品推荐

