Back to Blog
Next.jsSupabaseSSGSSRSEO

Next.js + Supabase: Why Your SSG Pages Quietly Turn into SSR

RaytonX
3 min read

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