微前端与 Multi-Zone
当产品线足够复杂——主应用、管理后台、文档站、营销官网——你可能不想把它们全部放在一个 Next.js 应用里。但你又希望用户感觉是在同一个网站。Next.js Multi-Zone 就是为这个场景设计的:多个独立 Next.js 应用通过 rewrites 合并成一个域名,独立开发、独立部署、统一访问。本章覆盖 Multi-Zone 和 Module Federation 两种微前端方案。
1. 微前端方案对比
1.1 三种方案
| 方案 | 适用场景 | 复杂度 | 框架限制 |
|---|---|---|---|
| Multi-Zone | 多个 Next.js 应用统一域名 | ★★ | 仅 Next.js |
| Module Federation | 跨应用共享组件,运行时加载 | ★★★★ | 框架无关 |
| iframe | 完全隔离,遗留系统集成 | ★ | 框架无关 |
推荐:Next.js 项目优先用 Multi-Zone(简单、原生支持);需要跨框架或运行时组件共享时用 Module Federation。
1.2 什么时候需要微前端
✅ 需要微前端:
- 团队 > 20 人,不同团队负责不同子产品
- 子产品有不同的发布节奏
- 需要渐进式迁移(从旧框架迁到 Next.js)
- 子产品技术栈不完全相同
❌ 不需要微前端:
- 团队 < 10 人
- 产品逻辑紧耦合
- 技术栈统一
- → 用 Monorepo 多应用即可
2. Multi-Zone 原理与实践
2.1 架构
用户视角(同一个域名 app.example.com):
/ → 主应用(apps/web)
/dashboard → 主应用
/admin → 管理后台(apps/admin)
/admin/users → 管理后台
/docs → 文档站(apps/docs)
/docs/api → 文档站
/blog → 博客(apps/blog)
部署视角(独立应用、独立部署):
apps/web → web.vercel.app
apps/admin → admin.vercel.app
apps/docs → docs.vercel.app
apps/blog → blog.vercel.app
2.2 主应用 rewrites 配置
// apps/web/next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
async rewrites() {
return {
beforeFiles: [
// /admin/* 路由代理到 admin 应用
{
source: '/admin',
destination: `${process.env.ADMIN_URL}/admin`,
},
{
source: '/admin/:path*',
destination: `${process.env.ADMIN_URL}/admin/:path*`,
},
// /docs/* 路由代理到 docs 应用
{
source: '/docs',
destination: `${process.env.DOCS_URL}/docs`,
},
{
source: '/docs/:path*',
destination: `${process.env.DOCS_URL}/docs/:path*`,
},
// /blog/* 路由代理到 blog 应用
{
source: '/blog',
destination: `${process.env.BLOG_URL}/blog`,
},
{
source: '/blog/:path*',
destination: `${process.env.BLOG_URL}/blog/:path*`,
},
],
afterFiles: [],
fallback: [],
}
},
}
export default config2.3 子应用 basePath 配置
每个子应用设置自己的 basePath:
// apps/admin/next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
basePath: '/admin',
// admin 应用的所有路由自动带 /admin 前缀
// app/page.tsx → /admin
// app/users/page.tsx → /admin/users
}
export default config// apps/docs/next.config.ts
const config: NextConfig = {
basePath: '/docs',
}// apps/blog/next.config.ts
const config: NextConfig = {
basePath: '/blog',
}2.4 环境变量
# apps/web/.env.local
ADMIN_URL=http://localhost:3001
DOCS_URL=http://localhost:3002
BLOG_URL=http://localhost:3003
# 生产环境
ADMIN_URL=https://admin.vercel.app
DOCS_URL=https://docs.vercel.app
BLOG_URL=https://blog.vercel.app2.5 跨 Zone 导航
// Zone 内导航用 <Link>(客户端导航,快速)
import Link from 'next/link'
<Link href="/dashboard/projects">Projects</Link>
// 跨 Zone 导航用 <a>(完整页面加载)
<a href="/admin/users">Admin Users</a>
// 因为 /admin 是另一个 Next.js 应用,客户端导航不跨应用
// 或者封装一个智能 Link 组件
function AppLink({ href, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
const isExternal = href?.startsWith('/admin') ||
href?.startsWith('/docs') ||
href?.startsWith('/blog')
if (isExternal) {
return <a href={href} {...props} />
}
return <Link href={href!} {...props} />
}2.6 共享状态
Multi-Zone 的各应用运行在独立的 JavaScript 上下文中,不能直接共享状态。
共享方案:
1. Cookie(认证 token、用户偏好)
→ 同域名下自动共享
2. URL 参数(临时状态)
→ /admin?tenantId=xxx
3. localStorage(持久化状态)
→ 同域名下自动共享
4. 后端 API(业务数据)
→ 各应用调用相同的 API / 数据库
3. Module Federation
3.1 什么是 Module Federation
Module Federation 是 Webpack 5 / Rspack 的功能,允许一个应用在运行时加载另一个应用暴露的模块(组件、函数、类型)。
应用 A(Host):
import Button from 'remote_b/Button'
← 运行时从应用 B 加载 Button 组件
应用 B(Remote):
exposes: { './Button': './src/components/Button' }
← 声明暴露 Button 组件
3.2 Next.js + Module Federation
使用 @module-federation/nextjs-mf 插件:
// apps/shell/next.config.ts(Host 应用)
import { NextFederationPlugin } from '@module-federation/nextjs-mf'
const config: NextConfig = {
webpack(config, options) {
if (!options.isServer) {
config.plugins.push(
new NextFederationPlugin({
name: 'shell',
remotes: {
dashboard: `dashboard@${process.env.DASHBOARD_URL}/_next/static/${options.isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
})
)
}
return config
},
}// apps/dashboard/next.config.ts(Remote 应用)
import { NextFederationPlugin } from '@module-federation/nextjs-mf'
const config: NextConfig = {
webpack(config, options) {
config.plugins.push(
new NextFederationPlugin({
name: 'dashboard',
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./StatsCard': './components/stats-card.tsx',
'./ProjectList': './components/project-list.tsx',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
})
)
return config
},
}3.3 使用远程组件
// apps/shell/app/page.tsx
import dynamic from 'next/dynamic'
const RemoteStatsCard = dynamic(
() => import('dashboard/StatsCard').catch(() => () => <div>Loading failed</div>),
{ ssr: false, loading: () => <Skeleton className="h-32" /> }
)
export default function HomePage() {
return (
<div>
<h1>Shell App</h1>
<RemoteStatsCard /> {/* 运行时从 dashboard 应用加载 */}
</div>
)
}3.4 Module Federation 的代价
| 优点 | 缺点 |
|---|---|
| 运行时组件共享 | 配置复杂 |
| 框架无关 | 版本冲突风险 |
| 独立部署 | 运行时错误难调试 |
| 按需加载 | 不支持 Server Component |
| 渐进式迁移 | 性能开销(额外网络请求) |
结论:除非有明确的跨框架共享需求,否则优先用 Multi-Zone + Monorepo 共享包。
4. 独立部署
4.1 Vercel Multi-Zone 部署
1. 每个应用是 Vercel 上的独立项目
2. 主应用配置 rewrites 指向其他应用的 Vercel URL
3. 主应用绑定自定义域名(app.example.com)
4. 子应用无需绑定域名(通过 rewrites 访问)
# Vercel CLI 部署
cd apps/web && vercel deploy --prod
cd apps/admin && vercel deploy --prod
cd apps/docs && vercel deploy --prod4.2 Docker 部署
# docker-compose.yml
services:
web:
build:
context: .
dockerfile: apps/web/Dockerfile
ports: ["3000:3000"]
environment:
- ADMIN_URL=http://admin:3001
- DOCS_URL=http://docs:3002
admin:
build:
context: .
dockerfile: apps/admin/Dockerfile
ports: ["3001:3001"]
docs:
build:
context: .
dockerfile: apps/docs/Dockerfile
ports: ["3002:3002"]
nginx:
image: nginx:alpine
ports: ["80:80"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf# nginx.conf
upstream web { server web:3000; }
upstream admin { server admin:3001; }
upstream docs { server docs:3002; }
server {
listen 80;
location /admin { proxy_pass http://admin; }
location /docs { proxy_pass http://docs; }
location / { proxy_pass http://web; }
}4.3 独立部署 vs Monorepo 部署
| 方式 | 优点 | 缺点 |
|---|---|---|
| Monorepo + 统一部署 | 版本一致 | 部署慢 |
| Monorepo + 选择性部署 | 快速、一致 | 需要 Turborepo filter |
| 独立仓库 + 独立部署 | 完全解耦 | 共享代码需发 npm |
推荐:Monorepo + Turborepo 选择性部署——既享受代码共享,又有独立部署的速度。
5. 方案选择决策树
你需要多个独立的 Next.js 应用吗?
├── 否 → 用单一 Next.js 应用 + Route Groups
│
├── 是 → 它们需要在同一个域名下吗?
│ ├── 否 → 独立部署,各自绑域名
│ │
│ └── 是 → 用 Multi-Zone(rewrites + basePath)
│ │
│ └── 需要跨应用运行时共享组件吗?
│ ├── 否 → Multi-Zone 就够了
│ └── 是 → 加 Module Federation
本章小结
- Multi-Zone:多 Next.js 应用通过 rewrites + basePath 合并到一个域名,原生支持、配置简单
- 跨 Zone 导航:Zone 内用
<Link>,跨 Zone 用<a>(完整页面加载) - 共享状态:Cookie / localStorage / URL 参数 / 后端 API——不能直接共享 JS 内存状态
- Module Federation:运行时加载远程组件,适合跨框架场景,但配置复杂且不支持 Server Component
- 独立部署:Vercel 多项目 + Nginx 反代 / Docker Compose 都可实现
- 决策:大多数场景 Multi-Zone + Monorepo 共享包就够了,Module Federation 是最后手段