如何在Next.js中安全保护静态站点生成(SSG)路由?
你提到的客户端重定向确实有致命的安全漏洞——用户只要篡改本地状态就能绕开校验,直接看到预先生成的静态内容。完全没必要为了权限要求把所有页面改成SSR,这里有几个更安全的服务端层面解决方案:
1. 用Next.js Middleware(首推方案)
Next.js 12及以上版本提供的Middleware是处理这类权限校验的最佳实践,它会在请求到达页面之前,就在边缘层(Edge Runtime)执行拦截逻辑,完全绕开客户端操作,从根源上杜绝篡改本地状态的可能。
举个结合Auth0的实战例子,在项目根目录创建middleware.ts文件:
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import jwt from 'jsonwebtoken' export function middleware(request: NextRequest) { // 从请求Cookie中获取Auth0签发的JWT令牌 const token = request.cookies.get('auth0-token')?.value // 验证令牌的有效性(签名、过期时间等) const isAuthenticated = validateToken(token) // 拦截未认证用户访问受保护的SSG页面 if (!isAuthenticated && request.nextUrl.pathname.startsWith('/protected')) { const loginUrl = new URL('/login', request.url) // 保存原访问路径,登录后可自动跳转回来 loginUrl.searchParams.set('returnTo', request.nextUrl.pathname) return NextResponse.redirect(loginUrl) } // 认证通过,放行请求 return NextResponse.next() } // 配置需要触发中间件的路径规则 export const config = { matcher: '/protected/:path*', } // 实现实际的令牌验证逻辑 function validateToken(token: string | undefined): boolean { if (!token) return false try { // 替换成你的Auth0密钥和验证逻辑 jwt.verify(token, process.env.AUTH0_SECRET_KEY!, { audience: process.env.AUTH0_AUDIENCE, issuer: process.env.AUTH0_ISSUER, }) return true } catch (err) { return false } }
这个方案的核心优势:
- 拦截逻辑在服务端/边缘节点执行,客户端根本碰不到受保护的静态内容
- 统一配置路径规则,不用在每个页面重复写校验代码
- 基于Edge Runtime,性能开销极小,完全保留SSG的性能优势
2. 边缘函数(Edge Functions)
如果你的部署平台支持边缘函数(比如Vercel、Cloudflare Pages),可以直接用它实现类似Middleware的拦截逻辑。本质上Next.js Middleware就是基于Edge Runtime的,如果你需要和平台特定功能集成,边缘函数会更灵活。
比如在Cloudflare Pages中,创建functions/protected/[[path]].ts文件,处理所有/protected路径的请求:
import jwt from 'jsonwebtoken' export async function onRequest(context) { const { request, env } = context const token = request.cookies.get('auth0-token')?.value // 验证令牌 if (!token || !validateToken(token, env)) { return Response.redirect(new URL('/login', request.url)) } // 认证通过,返回静态内容 return await env.ASSETS.fetch(request) } function validateToken(token: string, env: any): boolean { try { jwt.verify(token, env.AUTH0_SECRET_KEY, { audience: env.AUTH0_AUDIENCE, issuer: env.AUTH0_ISSUER, }) return true } catch (err) { return false } }
3. 自定义服务器(不推荐)
如果你还在使用Next.js旧版本,或者有特殊需求,可以用自定义服务器(比如Express)处理权限校验,但这个方案会失去Next.js的自动优化能力(比如自动静态优化、增量静态再生),所以除非必要不建议使用。
示例Express服务器代码:
const express = require('express') const next = require('next') const jwt = require('jsonwebtoken') const cookieParser = require('cookie-parser') const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare().then(() => { const server = express() server.use(cookieParser()) // 给受保护路径添加权限校验中间件 server.use('/protected/*', (req, res, next) => { const token = req.cookies['auth0-token'] if (!token) { return res.redirect('/login') } try { jwt.verify(token, process.env.AUTH0_SECRET_KEY, { audience: process.env.AUTH0_AUDIENCE, issuer: process.env.AUTH0_ISSUER, }) next() } catch (err) { return res.redirect('/login') } }) server.all('*', (req, res) => { return handle(req, res) }) server.listen(3000, (err) => { if (err) throw err console.log('> Ready on http://localhost:3000') }) })
额外提醒
如果你的受保护页面包含用户专属的动态内容(比如个人资料页、定制化仪表盘),那SSG确实不太合适——因为静态页面是构建时生成的,没法针对每个用户动态渲染内容。这种情况下,你可以考虑:
- 用
getStaticProps结合增量静态再生(ISR),但仍然无法处理用户专属内容 - 改成SSR(
getServerSideProps),在服务端获取用户信息并渲染专属内容
但如果只是页面需要权限访问,内容是通用的(比如内部文档、后台框架页),上面的Middleware/边缘函数方案完全可以满足需求,既能保留SSG的性能优势,又能实现安全的权限控制。
内容的提问来源于stack exchange,提问作者Thor

