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 限制 :只能在
https、localhost或127.0.0.1下工作。如果通过局域网 IP 访问,MSW 可能无法正常拦截请求。 - Next.js 多运行时 :Next.js 同时有客户端和服务端运行时,Service Worker 仅存在于浏览器端。因此 MSW 只能在客户端组件中生效,服务端数据获取(如
generateStaticParams、Server Actions)需要使用msw/node的setupServer来模拟。 - 生产环境 :通过
process.env.NODE_ENV === 'development'确保 MSW 只在开发环境启动,生产构建中不会包含 MSW 代码。 - 零侵入性:MSW 在网络层拦截请求,前端业务代码无需任何修改即可在模拟数据和真实 API 之间切换------只需开启或关闭 MSW 即可。
- 与 Next.js API Routes 共存 :MSW 拦截的是浏览器发出的请求,不会影响 Next.js 自身的 API Routes(
app/api/或pages/api/),两者可以共存。