When building a content-driven site with Next.js (App Router), we typically use generateStaticParams for dynamic routes to enable Static Site Generation (SSG) at build time. For example:
/courses/[courseSlug]/courses/[courseSlug]/lessons/[lessonSlug]
These pages have stable structures and low update frequency, making them ideal for SSG.
However, in a real project, we ran into a subtle issue: the page looked like SSG, but had already fallen back to SSR.
Worse, there were no errors — making it easy to overlook.
Symptoms
The first sign appeared in navigation behavior: Prefetch eturned 404
Further investigation revealed that: the dynamic routes were not actually generated as static pages during build time.
To verify, we temporarily added: export const dynamic = "force-static"; The page immediately behaved as expected.
This indicated that: the issue was not with generateStaticParams, but with logic inside the page that caused Next.js to treat it as dynamic.
Root Cause: Misusing Supabase SSR Client
The root cause turned out to be: using createServerClient from @supabase/ssr inside an SSG page.
This client is designed for SSR and depends on cookies (user session) and request context
But during build time, there is no request, so there are no cookies, and there is no user context
As a result, once such dependencies are introduced, Next.js will classify the page as dynamic (SSR).
The Fix: Separate SSG and SSR Data Access
In a Next.js + Supabase setup, it's important to clearly separate two types of clients.
1. Static Client for SSG
Used for content pages such as courses or blogs.
Create /lib/supabase/static.ts and use it in SSG pages:
import { createClient } from "@supabase/supabase-js";
export const supabaseStaticClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
2. Server Client for SSR
Used for authentication and user-related data:
import { createServerClient } from "@supabase/ssr";
How to Verify It Works
1. Check Build Output
If the page is truly SSG, you should see something like:
pnpm build
...
● /[courseSlug]
├ /raytonx-learn-from-zero
└ /supabase-fundamentals
● /[courseSlug]/lessons/[lessonSlug]
├ /...
└ /...
...
● (SSG) prerendered as static HTML (uses generateStaticParams)
ƒ (Dynamic) server-rendered on demand
If these paths are missing, the page is likely SSR.
2. Observe Prefetch Behavior
If you see, prefetch returning 404, delayed page transitions, then the rendering mode may have changed unexpectedly.
Conclusion
Next.js determines the rendering mode based on your code dependencies. If you introduce request-based logic (such as cookies), a page that should be SSG may be treated as SSR.
This can lead to:
- unstable content output
- reduced crawl efficiency
- degraded SEO performance