React Router v6自定义404页面异常显示问题求助
问题场景
想要在访问不存在的页面时显示自定义404页面,添加了<Route path='*' element={<MissingPage />} />路由,但除主页外,所有页面底部都会显示该404页面,尝试旧版本的exact属性无效。
依赖版本:
"dependencies": { "@craco/craco": "^6.4.3", "@emotion/react": "^11.9.0", "react": "^18.1.0", "react-dom": "^18.1.0" }, "devDependencies": { "react-router-dom": "^6.3.0" }
原主路由结构:
return ( <ThemeProvider theme={theme}> <SharedContextProvider> <div className='main'> <BrowserRouter> <SharedDataManagement> <Fragment> <AuthRoutes /> <DashboardRoutes /> <SubscriptionsContextProvider> <SubscriptionsRoutes /> </SubscriptionsContextProvider> <PaymentRoutes /> <CommitmentRoutes /> <CoursesRoutes /> <ContactUsRoutes /> <ErrorsRoutes /> <Routes> <Route path='/' element={<HomePageComponent />} /> <Route path='*' element={<MissingPage />} /> </Routes> </Fragment> </SharedDataManagement> </BrowserRouter> </div> </SharedContextProvider> </ThemeProvider> )
子路由示例(PaymentRoutes):
export function PaymentRoutes() { return ( <PaymentContextProvider> <Routes> <Route path='/payment' element={<PaymentPage />} /> <Route path='/payment/success' element={<SubscriptionPage />} /> </Routes> </PaymentContextProvider> ) }
子路由示例(CommitmentRoutes):
export function CommitmentRoutes() { return ( <Routes> <Route path='/privacy-policy' element={<PrivacyPolicyPage />} /> <Route path='/terms-and-conditions' element={<TermsAndConditionsPage />} /> <Route path='/cookie' element={<CookiePage />} /> </Routes> ) }
问题原因
React Router v6中,每个<Routes>组件会独立进行路由匹配,只要当前路径符合组件内的路由规则,就会渲染对应的元素。你现在的代码里存在多个独立的<Routes>(主路由的<Routes>、PaymentRoutes里的<Routes>、CommitmentRoutes里的<Routes>等):
- 当访问
/payment时,PaymentRoutes里的<Routes>匹配到/payment并渲染PaymentPage; - 同时主路由的
<Routes>里的path='*'会匹配所有非/的路径,因此也会渲染MissingPage; - 两个组件同时渲染,导致404页面出现在正常页面底部。
另外,React Router v6已经移除了exact属性,所有路由默认都是精确匹配(嵌套路由除外),所以exact无效是正常的。
解决方案
将所有路由统一到同一个<Routes>组件中,确保只有一个路由匹配上下文,并且把path='*'的404路由放在最后,这样只有当没有其他路由匹配时才会触发它。
修改步骤
- 改造所有子路由组件,移除内部的
<Routes>,直接返回<Route>元素(可以用<>包裹); - 在主路由的
<Routes>中,引入所有子路由的<Route>,并将404路由放在最后。
修改后的代码示例
改造后的PaymentRoutes:
export function PaymentRoutes() { return ( <PaymentContextProvider> <> <Route path='/payment' element={<PaymentPage />} /> <Route path='/payment/success' element={<SubscriptionPage />} /> </> </PaymentContextProvider> ) }
改造后的CommitmentRoutes:
export function CommitmentRoutes() { return ( <> <Route path='/privacy-policy' element={<PrivacyPolicyPage />} /> <Route path='/terms-and-conditions' element={<TermsAndConditionsPage />} /> <Route path='/cookie' element={<CookiePage />} /> </> ) }
改造后的主路由结构:
return ( <ThemeProvider theme={theme}> <SharedContextProvider> <div className='main'> <BrowserRouter> <SharedDataManagement> <Routes> {/* 主页路由 */} <Route path='/' element={<HomePageComponent />} /> {/* 引入各子路由 */} <AuthRoutes /> <DashboardRoutes /> {/* 需要上下文包裹的子路由,用嵌套路由处理 */} <Route element={<SubscriptionsContextProvider><Outlet /></SubscriptionsContextProvider>}> <SubscriptionsRoutes /> </Route> <PaymentRoutes /> <CommitmentRoutes /> <CoursesRoutes /> <ContactUsRoutes /> <ErrorsRoutes /> {/* 404路由必须放在最后 */} <Route path='*' element={<MissingPage />} /> </Routes> </SharedDataManagement> </BrowserRouter> </div> </SharedContextProvider> </ThemeProvider> )
如果子路由组件需要上下文包裹(比如SubscriptionsRoutes需要SubscriptionsContextProvider),可以用嵌套路由的方式:通过element属性设置上下文组件,内部用<Outlet />渲染子路由内容,这样子路由的所有页面都会继承该上下文。
这样修改后,只有当没有任何路由匹配当前路径时,才会渲染MissingPage,正常页面不会再显示404内容。
内容的提问来源于stack exchange,提问作者Dmitr Zar

