在 Next.js 的 App Router 架构中,静态渲染(Static Rendering)是默认行为。这意味着组件会在构建时或按需预渲染(On-demand Revalidation)阶段生成 HTML,以获得极致性能和缓存效率。
但有时,我们需要组件必须在用户请求时动态执行,例如:
- 使用
Math.random()
生成随机数 - 获取当前时间
new Date()
- 调用外部实时 API(但又不想用
fetch
触发动态) - 基于用户 IP 或请求上下文做个性化渲染
过去我们可能用 unstable_noStore()
,但从 Next.js v15.0.0 起,官方推荐使用 connection()
来显式声明"此组件需等待用户连接后才渲染"。
一、什么是 connection()
?
connection()
是 Next.js 提供的一个函数,用于强制组件在用户请求到达时才执行渲染,从而跳过静态/预渲染阶段。
基本用法
tsx
// app/random/page.tsx
import { connection } from 'next/server'
export default async function RandomPage() {
await connection() // ⚠️ 关键:等待用户连接
const rand = Math.random() // 每次请求都不同
return <h1>随机数:{rand}</h1>
}
✅ 效果:每次刷新页面,都会看到不同的随机数。
❌ 若不加
await connection()
,该页面会被静态生成,所有用户看到同一个"构建时"的随机值。
二、为什么需要 connection()
?
Next.js 的渲染策略基于"是否使用动态 API"自动判断:
使用了以下 API? | 渲染模式 |
---|---|
cookies() , headers() , searchParams , POST 等 |
动态渲染 |
未使用任何动态 API | 静态渲染(即使逻辑依赖运行时) |
但像 Math.random()
、new Date()
这类纯 JavaScript 代码,不会被 Next.js 自动识别为"动态",导致错误地静态化。
connection()
就是为这类场景设计的"手动开关"。
三、实战案例
案例 1:实时时间显示(非缓存)
tsx
// app/time/page.tsx
import { connection } from 'next/server'
export default async function TimePage() {
await connection()
const now = new Date().toLocaleString('zh-CN')
return (
<div>
<h1>当前服务器时间</h1>
<p>{now}</p>
<small>每次刷新都会更新</small>
</div>
)
}
💡 适用于调试、演示或需要真实请求时间的场景。
案例 2:基于用户 IP 的个性化欢迎语(不使用 headers)
假设你通过自定义中间件将 IP 注入到环境变量(或通过其他方式),但不想在组件中直接调用 headers()
:
tsx
// app/welcome/page.tsx
import { connection } from 'next/server'
export default async function WelcomePage() {
await connection()
// 假设 IP 已通过某种方式获取(如自定义上下文)
const userIP = process.env.USER_IP || 'unknown'
return <p>你好,来自 {userIP} 的访客!</p>
}
⚠️ 注意:若直接使用
headers().get('x-forwarded-for')
,则无需connection()
,因为headers()
本身已是动态 API。
案例 3:防止静态缓存的 A/B 测试
tsx
// app/ab-test/page.tsx
import { connection } from 'next/server'
function getVariant() {
return Math.random() > 0.5 ? 'A' : 'B'
}
export default async function ABTestPage() {
await connection()
const variant = getVariant()
return (
<div>
<h2>你看到的是版本 {variant}</h2>
{variant === 'A' ? <button>按钮 A</button> : <button>按钮 B</button>}
</div>
)
}
✅ 确保每个用户独立分配版本,而非所有人看到同一个静态版本。
四、与 unstable_noStore()
的区别
特性 | unstable_noStore() (旧) |
connection() (新) |
---|---|---|
作用 | 禁用缓存 | 等待用户连接后渲染 |
语义 | "不要缓存这个请求" | "这是一个动态请求,需实时执行" |
推荐度 | ❌ 已废弃 | ✅ 官方推荐(v15+) |
渲染时机 | 仍可能预渲染,但不缓存 | 完全跳过预渲染 |
📌 官方明确表示:
connection()
更符合未来渲染模型(如 Partial Prerendering)。
五、注意事项
- 不要滥用:只有真正需要"每次请求都不同"的场景才用,否则会丧失静态性能优势。
- 不能用于客户端组件 :
connection()
只能在 Server Components、Route Handlers 等服务端上下文中使用。 - 不影响 SEO:动态页面仍可被爬虫抓取,但无法享受 CDN 缓存加速。
- 与 ISR 不冲突 :你仍可结合
revalidate
实现混合策略(但connection()
会覆盖静态行为)。
六、总结
connection()
是 Next.js v15+ 中强制动态渲染的标准方式。- 适用于
Math.random()
、new Date()
等无法被自动识别为动态的场景。 - 替代了旧的
unstable_noStore()
,语义更清晰,未来兼容性更好。 - 使用时需权衡性能与动态性,避免过度使用。
合理使用 connection()
,让你的 Next.js 应用在"静态优先"与"动态必要"之间取得完美平衡。