返回博客
Next.jsSupabaseSSGSSRSEO

Next.js + Supabase:为什么你的 SSG 页面会悄悄变成 SSR?

RaytonX
1 min read

在使用 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/ssrcreateServerClient

这个客户端是为 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 表现。