在使用 Next.js(App Router)开发内容型网站时,我们通常会为动态路由配置 generateStaticParams,让页面在构建阶段完成静态生成(SSG)。例如:
/courses/[courseSlug]/courses/[courseSlug]/lessons/[lessonSlug]
这类页面结构稳定、内容更新频率低,非常适合 SSG。
但在实际项目中,我们踩过一个非常隐蔽的坑:页面看起来是 SSG,实际上却已经退化成了 SSR。,而且这个问题不会报错,很容易被忽略。
问题表现
最早的异常,是在页面跳转体验上发现的:页面 prefetch 没有生效, 返回 404
进一步排查发现:动态路由页面并没有在构建阶段生成静态文件。
为了验证,我们临时加了一行配置:export const dynamic = "force-static"; 页面立刻恢复正常。
这说明:问题并不在 generateStaticParams,而是在页面中的某些逻辑,让 Next.js 判断该页面必须走动态渲染。
真正原因:误用了 Supabase 的 SSR Client
最终定位问题,发现在 SSG 页面中使用了 @supabase/ssr 的 createServerClient。
这个客户端是为 SSR 场景设计的,它依赖:cookies(用户登录态)和 request 上下文
但在构建阶段,没有 cookies, 没有用户信息
因此,一旦页面中引入了这类逻辑,Next.js 会直接判定:这个页面必须是动态渲染(SSR)。
正确做法:区分 SSG 与 SSR 的数据访问方式
在 Next.js + Supabase 项目中,可以明确划分两种客户端:
1. 用于 SSG 的静态客户端
创建 /lib/supabase/static.ts 之后在SSG页面中使用静态客户端
import { createClient } from "@supabase/supabase-js";
export const supabaseStaticClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
2. 用于 SSR 的服务端客户端
用于登录态、用户信息等场景:
import { createServerClient } from "@supabase/ssr";
验证是否生效
1. 关注构建输出
如果页面是 SSG,你应该能看到类似输出:
pnpm build
...
● /[courseSlug]
├ /raytonx-learn-from-zero
└ /supabase-fundamentals
● /[courseSlug]/lessons/[lessonSlug]
├ /...
└ /...
...
● (SSG) prerendered as static HTML (uses generateStaticParams)
ƒ (Dynamic) server-rendered on demand
如果没有这些路径,很可能页面已经变成 SSR。
2. 检查 prefetch 行为
如果出现:prefetch 404, 或者页面跳转卡顿, 就需要警惕渲染模式是否发生变化。
总结
Next.js 会根据代码结构自动决定页面的渲染方式。错误的依赖(如 cookies / request)会让页面失去 SSG 能力,进而影响抓取效率和 SEO 表现。