在现代化前端开发中,Next.js 因其服务端渲染(SSR)和静态生成(SSG)能力成为 React 生态的明星框架。但正是这种混合渲染模式,使得鉴权(Authentication)方案的选择变得复杂。不同于传统 SPA 应用只需在客户端处理 token,Next.js 需要在服务端、客户端和边缘环境(如 Vercel Edge Functions)中统一处理身份验证。
我经历过多个 Next.js 项目的鉴权实现,发现开发者常陷入以下困境:
接下来将剖析 5 种经过生产验证的方案,包含代码片段和性能对比数据。所有示例基于 Next.js 14 App Router,但原理同样适用于 Pages Router。
typescript复制// app/api/login/route.ts
import { cookies } from 'next/headers'
export async function POST(request: Request) {
const { email, password } = await request.json()
const user = await authenticateUser(email, password)
cookies().set('session_token', user.token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7 // 1周
})
return Response.json({ user })
}
关键安全措施:
httpOnly 防止 XSS 攻击读取 Cookiesecure 确保 HTTPS 传输SameSite=Lax 防御 CSRF(Next.js 默认设置)typescript复制// app/dashboard/page.tsx
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = cookies()
const session = cookieStore.get('session_token')
if (!session) redirect('/login')
const user = await fetchUser(session.value)
return <Dashboard user={user} />
}
性能优化点:
unstable_cache 缓存用户查询typescript复制// lib/auth.ts
export function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.ACCESS_TOKEN_SECRET!,
{ expiresIn: '15m' }
)
const refreshToken = jwt.sign(
{ userId },
process.env.REFRESH_TOKEN_SECRET!,
{ expiresIn: '7d' }
)
return { accessToken, refreshToken }
}
令牌管理策略:
typescript复制// app/api/refresh/route.ts
export async function POST() {
const refreshToken = cookies().get('refresh_token')?.value
if (!refreshToken) {
return new Response('Unauthorized', { status: 401 })
}
try {
const decoded = verifyRefreshToken(refreshToken)
const { accessToken } = generateTokens(decoded.userId)
return Response.json({ accessToken })
} catch (err) {
cookies().delete('refresh_token')
return new Response('Invalid token', { status: 403 })
}
}
typescript复制// auth.config.ts
import GitHub from "next-auth/providers/github"
import type { NextAuthConfig } from "next-auth"
export default {
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role // 添加自定义声明
}
return token
}
}
} satisfies NextAuthConfig
typescript复制// middleware.ts
import { auth } from "@/auth"
export default auth((req) => {
const { pathname } = req.nextUrl
const isLoggedIn = !!req.auth
if (pathname.startsWith('/admin') && req.auth?.user.role !== 'admin') {
return Response.redirect(new URL('/denied', req.url))
}
})
typescript复制// app/api/auth/[...nextauth]/route.ts
import { NextAuth } from "next-auth"
import GitHub from "next-auth/providers/github"
const handler = NextAuth({
providers: [GitHub],
adapter: VercelKVAdapter(process.env.KV_URL) // 使用 Vercel KV
})
export { handler as GET, handler as POST }
export const runtime = 'edge'
| 方案 | 冷启动时间 | 内存占用 | 适合场景 |
|---|---|---|---|
| 传统 Node.js 运行时 | 1200ms | 180MB | 复杂业务逻辑 |
| Edge Runtime | 200ms | 32MB | 高并发认证请求 |
@typescript-eslint/strict 避免类型漏洞Rate Limitingtypescript复制// lib/monitoring.ts
export function trackAuthEvent(event: {
type: 'login' | 'logout' | 'token_refresh'
latency: number
error?: string
}) {
fetch('https://monitoring.example.com', {
method: 'POST',
body: JSON.stringify({
...event,
app: 'nextjs-auth',
env: process.env.NODE_ENV
})
})
}
关键监控项:
next.config.js 的 rewrites 是否干扰vercel.app)javascript复制cookies().set('test', 'value', {
sameSite: process.env.NODE_ENV === 'development' ? 'lax' : 'none'
})
解决方案:
typescript复制// components/auth-provider.tsx
'use client'
import { createContext, useEffect } from 'react'
import { useRouter } from 'next/navigation'
export const AuthContext = createContext()
export function AuthProvider({ user, children }) {
const [clientUser, setClientUser] = useState(null)
const router = useRouter()
useEffect(() => {
if (user && !clientUser) {
setClientUser(user) // 同步服务端状态
} else if (!user && clientUser) {
router.refresh() // 触发服务端重新验证
}
}, [user])
return (
<AuthContext.Provider value={{ user: clientUser }}>
{children}
</AuthContext.Provider>
)
}
typescript复制// app/layout.tsx
const AuthModal = dynamic(
() => import('@/components/auth-modal'),
{
ssr: false,
loading: () => <Skeleton className="h-10 w-20" />
}
)
typescript复制// app/dashboard/loading.tsx
export default function Loading() {
const { data: session } = useSession()
return session ? (
<DashboardSkeleton />
) : (
<LoginRedirect />
)
}
在最近的项目中,采用边缘缓存策略将会验证延迟从 300ms 降至 80ms。关键在于将 JWT 验证结果缓存在 Vercel Edge Config 中,设置 5 秒的短过期时间平衡安全性与性能。