You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 07:35:16