React 19 + Next.js 16(App Router)项目中集成 MSW

React 19 + Next.js 16(App Router)项目中集成 MSW 的完整流程。


一、安装 MSW

bash 复制代码
npm install msw --save-dev

二、初始化 Service Worker 文件

MSW 依赖一个 mockServiceWorker.js 文件来拦截浏览器端的网络请求,该文件必须放在 public 目录 下:

bash 复制代码
# 将 mockServiceWorker.js 生成到 public 目录
npx msw init public/ --save

执行后,public/mockServiceWorker.js 文件会被创建。该文件必须能通过浏览器直接访问(如 http://localhost:3000/mockServiceWorker.js),否则 Service Worker 无法注册。


三、创建 MSW 配置文件结构

src/mocks/ 目录下创建以下文件:

1. src/mocks/handlers.ts(定义请求处理器)
ts 复制代码
import { http, HttpResponse } from 'msw';

// 模拟用户数据
const users = [
  { id: 1, name: '张三', email: 'zhangsan@example.com', role: 'admin' },
  { id: 2, name: '李四', email: 'lisi@example.com', role: 'user' },
  { id: 3, name: '王五', email: 'wangwu@example.com', role: 'user' },
];

export const handlers = [
  // GET /api/users - 获取用户列表
  http.get('/api/users', ({ request }) => {
    const url = new URL(request.url);
    const page = url.searchParams.get('page') || '1';
    const limit = url.searchParams.get('limit') || '10';

    return HttpResponse.json({
      code: 200,
      data: {
        page: Number(page),
        limit: Number(limit),
        total: users.length,
        list: users,
      },
    });
  }),

  // GET /api/users/:id - 获取单个用户
  http.get('/api/users/:id', ({ params }) => {
    const { id } = params;
    const user = users.find((u) => u.id === Number(id));

    if (!user) {
      return HttpResponse.json(
        { code: 404, message: '用户不存在' },
        { status: 404 }
      );
    }

    return HttpResponse.json({ code: 200, data: user });
  }),

  // POST /api/login - 用户登录
  http.post('/api/login', async ({ request }) => {
    const body = await request.json();
    const { username, password } = body;

    if (username === 'admin' && password === '123456') {
      return HttpResponse.json({
        code: 200,
        data: {
          token: 'mock-jwt-token-abc123',
          user: { id: 1, name: '管理员', role: 'admin' },
        },
      });
    }

    return HttpResponse.json(
      { code: 401, message: '用户名或密码错误' },
      { status: 401 }
    );
  }),

  // POST /api/users - 创建用户
  http.post('/api/users', async ({ request }) => {
    const body = await request.json();
    const newUser = { id: Date.now(), ...body };
    users.push(newUser);

    return HttpResponse.json(
      { code: 201, message: '创建成功', data: newUser },
      { status: 201 }
    );
  }),
];
2. src/mocks/browser.ts(浏览器环境配置)
ts 复制代码
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

// 创建 MSW worker 实例
export const worker = setupWorker(...handlers);

// 导出启动函数
export async function startWorker() {
  return worker.start({
    // 未匹配到 handler 的请求直接放行,发送到真实服务器
    onUnhandledRequest: 'bypass',
    // 指定 Service Worker 文件路径
    serviceWorker: {
      url: '/mockServiceWorker.js',
    },
    // 是否静默模式
    quiet: false,
  });
}
3. src/mocks/server.ts(Node.js 环境配置,用于测试)
ts 复制代码
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

// 创建 Node.js 环境的 server 实例
export const server = setupServer(...handlers);

四、创建 MSW 启动包装组件(Next.js App Router 方式)

由于 Next.js 的 App Router 包含服务端组件(Server Component),而 Service Worker 只能在浏览器端运行,因此需要通过一个客户端包装组件来启动 MSW。

src/components/MSWProvider.tsx
tsx 复制代码
'use client';

import { useEffect, useState } from 'react';

export function MSWProvider({ children }: { children: React.ReactNode }) {
  const [isMockReady, setIsMockReady] = useState(false);

  useEffect(() => {
    async function enableMocks() {
      // 只在开发环境启动 MSW
      if (process.env.NODE_ENV === 'development') {
        try {
          const { startWorker } = await import('@/mocks/browser');
          await startWorker();
          console.log('[MSW] Mock Service Worker 已启动');
        } catch (error) {
          console.error('[MSW] 启动失败:', error);
        }
      }
      setIsMockReady(true);
    }

    enableMocks();
  }, []);

  // 等待 MSW 初始化完成后再渲染子组件
  if (!isMockReady) {
    return <div>加载模拟数据中...</div>;
  }

  return <>{children}</>;
}

五、在根布局中集成 MSWProvider

src/app/layout.tsx
tsx 复制代码
import { MSWProvider } from '@/components/MSWProvider';
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body>
        {/* 用 MSWProvider 包裹整个应用 */}
        <MSWProvider>
          {children}
        </MSWProvider>
      </body>
    </html>
  );
}

六、通过环境变量控制 MSW 开关(可选)

.env.development 中添加:

env 复制代码
NEXT_PUBLIC_API_MOCKING=enabled

然后在 MSWProvider 中增加判断:

ts 复制代码
const enableMSW =
  process.env.NODE_ENV === 'development' &&
  process.env.NEXT_PUBLIC_API_MOCKING === 'enabled';

if (enableMSW) {
  const { startWorker } = await import('@/mocks/browser');
  await startWorker();
}

这样可以灵活地通过环境变量控制是否启用 Mock。


七、在客户端组件中使用模拟 API

src/app/users/page.tsx
tsx 复制代码
'use client';

import { useEffect, useState } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

export default function UsersPage() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function loadUsers() {
      try {
        // 这个请求会被 MSW 拦截并返回模拟数据
        const response = await fetch('/api/users?page=1&limit=10');

        if (!response.ok) {
          throw new Error(`请求失败: ${response.status}`);
        }

        const result = await response.json();
        setUsers(result.data.list);
      } catch (err) {
        setError(err instanceof Error ? err.message : '未知错误');
      } finally {
        setLoading(false);
      }
    }

    loadUsers();
  }, []);

  if (loading) return <div>加载中...</div>;
  if (error) return <div className="error">❌ {error}</div>;

  return (
    <div className="users-page">
      <h1>用户列表</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            <strong>{user.name}</strong> - {user.email} ({user.role})
          </li>
        ))}
      </ul>
    </div>
  );
}

八、关键注意事项

  • Service Worker 限制 :只能在 httpslocalhost127.0.0.1 下工作。如果通过局域网 IP 访问,MSW 可能无法正常拦截请求。
  • Next.js 多运行时 :Next.js 同时有客户端和服务端运行时,Service Worker 仅存在于浏览器端。因此 MSW 只能在客户端组件中生效,服务端数据获取(如 generateStaticParams、Server Actions)需要使用 msw/nodesetupServer 来模拟。
  • 生产环境 :通过 process.env.NODE_ENV === 'development' 确保 MSW 只在开发环境启动,生产构建中不会包含 MSW 代码。
  • 零侵入性:MSW 在网络层拦截请求,前端业务代码无需任何修改即可在模拟数据和真实 API 之间切换------只需开启或关闭 MSW 即可。
  • 与 Next.js API Routes 共存 :MSW 拦截的是浏览器发出的请求,不会影响 Next.js 自身的 API Routes(app/api/pages/api/),两者可以共存。
相关推荐
Mr.Daozhi1 小时前
跨境电商选品完整流水线:Google Trends筛词+Meta广告分析,CLI工具设计实战
开发语言·爬虫·python·跨境电商·工具链·选品
多彩电脑1 小时前
Swift里字符串的索引
开发语言·swift
SoftLipaRZC1 小时前
C语言预处理详解:从宏定义到条件编译
c语言·开发语言
会周易的程序员1 小时前
C++ 对象池深度解析:架构设计与实现原理
开发语言·c++·物联网·iot·aiot
L_09071 小时前
【C++】智能指针
开发语言·c++·智能指针
程序猿乐锅1 小时前
【苍穹外卖|Day02】后台接口自测闭环:Token、DTO 与 yml 配置
java·开发语言
冰暮流星1 小时前
javascript之对象的建立-使用Object
开发语言·javascript·ecmascript
AI_零食1 小时前
呼吸灯 - 通过鸿蒙PC Electron框架技术完成-在焦虑时代守护每一次呼吸的数字禅修
前端·javascript·华为·electron·前端框架·鸿蒙
qq_2518364571 小时前
基于java 税务管理系统设计与实现
java·开发语言