Next13+Storybook7启动报错NextRouter was not mounted问题求助
问题
使用Next.js 13 + Storybook 7,启动Storybook时出现NextRouter未挂载的报错,原因是AppContextProvider中使用了router对象实现错误跳转逻辑,注释掉router相关代码后Storybook可正常运行,但业务上需要保留根据错误内容跳转页面的功能。
报错信息:
NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted
Storybook配置代码:
import * as React from 'react'; import { initialize, mswDecorator } from 'msw-storybook-addon'; import { AppContextProvider } from '../src/context/AppContextProvider'; import { NextPageWithLayout } from '../src/pages/_app.page'; import { mockOrganization, mockList, } from '../src/stories/mocks/msw-handlers'; import 'style/index.css'; import 'style/utils/slider.css'; import 'tailwindcss/tailwind.css'; initialize(); export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, layout: 'fullscreen', controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, msw: { handlers: { '/organization': mockOrganization(), '/master': mockList(), }, }, chromatic: { viewports: [375, 768, 1200], }, }; export const decorators = [ (Story: NextPageWithLayout) => { const getLayout = Story.getLayout ?? (page => page); return <AppContextProvider>{getLayout(<Story />)}</AppContextProvider>; }, mswDecorator, ];
解决方案
1. 在AppContext中添加路由存在性判断
在AppContextProvider中使用router前先判断路由是否已挂载,避免在Storybook环境中执行跳转逻辑:
// src/context/AppContextProvider.jsx import { useRouter } from 'next/router'; export function AppContextProvider({ children }) { const router = useRouter(); const handleError = (error) => { // 判断router是否可用(避免Storybook环境报错) if (router?.push) { // 根据错误内容执行跳转 if (error.code === 'UNAUTHORIZED') { router.push('/login'); } else { router.push('/error'); } } else { // Storybook环境下打印模拟跳转日志 console.log('模拟跳转:', error.code === 'UNAUTHORIZED' ? '/login' : '/error'); } }; return ( <AppContext.Provider value={{ handleError }}> {children} </AppContext.Provider> ); }
2. 在Storybook中注入Mock Router
如果需要在Storybook中模拟完整跳转行为,可自定义Mock Router并通过装饰器传入:
首先创建Mock Router工具:
// src/stories/mocks/mockRouter.js export const mockRouter = { push: (path) => { console.log(`Storybook模拟跳转至: ${path}`); // 可按需添加更多模拟逻辑,比如更新状态 }, // 按需补充replace、back等其他router方法 };
修改Storybook装饰器,将Mock Router传入AppContextProvider:
// .storybook/preview.js import * as React from 'react'; import { initialize, mswDecorator } from 'msw-storybook-addon'; import { AppContextProvider } from '../src/context/AppContextProvider'; import { NextPageWithLayout } from '../src/pages/_app.page'; import { mockOrganization, mockList } from '../src/stories/mocks/msw-handlers'; import { mockRouter } from '../src/stories/mocks/mockRouter'; import 'style/index.css'; import 'style/utils/slider.css'; import 'tailwindcss/tailwind.css'; initialize(); const appContextDecorator = (Story) => { const getLayout = Story.getLayout ?? (page => page); // 若AppContextProvider支持传入router props,直接注入mockRouter return ( <AppContextProvider router={mockRouter}> {getLayout(<Story />)} </AppContextProvider> ); }; export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, layout: 'fullscreen', controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, msw: { handlers: { '/organization': mockOrganization(), '/master': mockList(), }, }, chromatic: { viewports: [375, 768, 1200], }, }; export const decorators = [ appContextDecorator, mswDecorator, ];
3. 封装安全路由Hook
封装自定义Hook统一处理Next.js与Storybook环境的路由调用:
// src/hooks/useSafeRouter.js import { useRouter } from 'next/router'; export function useSafeRouter() { const router = useRouter(); // 判断是否在浏览器环境且router已挂载 const isRouterAvailable = typeof window !== 'undefined' && router?.push; return { ...router, push: (path) => { if (isRouterAvailable) { return router.push(path); } else { console.log('安全跳转模拟:', path); return Promise.resolve(); } }, // 同理封装replace等其他路由方法 }; }
在AppContextProvider中使用该Hook替代原生useRouter:
// src/context/AppContextProvider.jsx import { useSafeRouter } from '../hooks/useSafeRouter'; export function AppContextProvider({ children }) { const router = useSafeRouter(); const handleError = (error) => { if (error.code === 'UNAUTHORIZED') { router.push('/login'); } else { router.push('/error'); } }; return ( <AppContext.Provider value={{ handleError }}> {children} </AppContext.Provider> ); }
内容的提问来源于stack exchange,提问作者aahhuhuhu
相关产品推荐
相关产品推荐

