All articles
SEO & GrowthSeptember 3, 2026 7 min read

Rendering Strategy for Programmatic SEO: When SSR, ISR, and Static Actually Matter

Programmatic SEO lives and dies on how you render 50k pages. Here's how we pick between SSR, ISR, and full static — and where teams burn crawl budget by choosing wrong.

Rendering Strategy for Programmatic SEO: When SSR, ISR, and Static Actually Matter

Pick the wrong rendering strategy and a programmatic SEO site quietly bleeds money — either through a serverless bill that scales with Googlebot's mood, or through stale pages that lose to a competitor who redeployed yesterday. This is one of those decisions that looks like a framework config flag and is actually an architecture call.

We've shipped programmatic sites in the 5k–500k URL range across Next.js, Astro, and a couple of custom Node stacks. The rendering choice was almost always the biggest lever on cost, TTFB, and how quickly Google would re-crawl updates. Here's the decision framework we actually use.

The three modes, stripped of marketing

Before comparing, let's agree on what each mode does at request time. Framework docs love to blur these lines.

  • Static (SSG): HTML is generated at build time. The origin serves a file (or a CDN does). Zero compute per request. Updates require a rebuild or a targeted revalidation.
  • ISR (Incremental Static Regeneration): First request after a TTL expires triggers a background regeneration. Subsequent requests get the fresh HTML. Between regenerations, it behaves like static.
  • SSR: HTML is generated on every request. Full flexibility, full latency, full bill.

There's a fourth mode people forget: on-demand ISR (or revalidateTag in Next 14+), where a webhook or CMS event invalidates a specific path or tag. This is what you actually want for most programmatic sites, and we'll come back to it.

The variables that actually decide this

Every framework blog post frames this as "static is faster, SSR is more dynamic". That's true and useless. The real inputs are:

  1. URL count. 2,000 pages is not the same problem as 200,000.
  2. Update frequency per URL. A jobs board changes hourly. A "best X in Y" page changes monthly.
  3. Personalization. If the HTML differs by geo, session, or A/B bucket, you cannot cache it as one artifact.
  4. Crawl budget pressure. How often does Googlebot actually hit each URL? (Check your logs — see our log analysis piece.)
  5. Build time. If your full build is over 20 minutes, static is already lying to you.

Write these down for your project before touching config. Everything below flows from them.

Static: the default that stops working at scale

Static is the correct choice when you have fewer than roughly 10,000 URLs, updates are batched (daily or less often), and content is identical for every visitor. Marketing sites, docs, most affiliate content, and small programmatic sets fit here.

The cost model is unbeatable: you're paying for CDN egress and nothing else. TTFB is whatever your CDN's edge latency is — typically 30–80ms globally. Googlebot loves this. Core Web Vitals are easy to win because there's no server variance.

Where it breaks:

Build times explode past ~10k pages

A Next.js build generating 50,000 pages with data fetching per page routinely takes 30–90 minutes in our experience, depending on how well you parallelize generateStaticParams. Every content edit means another full build, or a careful partial redeploy pipeline you now have to maintain. At 200k pages, full static is essentially off the table unless your data is trivially local.

You can't react to freshness signals

If your "last updated" date is baked into HTML at build time and you build weekly, every page is up to seven days stale from Googlebot's perspective. That matters for query intents where recency is a ranking factor.

ISR: the right default for real programmatic sites

ISR is what most 10k–500k URL programmatic sites should be running. The mental model: treat your CDN as a lazy cache of a server-rendered site.

You set a revalidation window (say, 24 hours). The first crawler or user to hit a page after that window triggers a background regeneration. Everyone else — including Googlebot on its next visit — gets a warm, cached HTML response with edge-level TTFB.

Here's the shape in Next.js App Router:

// app/[category]/[slug]/page.tsx
export const revalidate = 86400; // 24h

export async function generateStaticParams() {
  // Only pre-build the top ~2k pages by traffic
  const top = await db.pages.findTopByTraffic(2000);
  return top.map(p => ({ category: p.category, slug: p.slug }));
}

export default async function Page({ params }) {
  const data = await getPageData(params.category, params.slug);
  if (!data) notFound();
  return <ArticleTemplate data={data} />;
}

Notice we pre-build only the top 2,000. The other 98,000 pages get generated on first hit and cached. This is the pattern that keeps builds under 5 minutes while still serving 100k+ URLs.

On-demand revalidation is the real unlock

Time-based ISR is a blunt tool. On-demand revalidation via revalidatePath or revalidateTag lets your CMS or ETL pipeline invalidate exactly the pages that changed:

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';

export async function POST(req: Request) {
  const { secret, tag } = await req.json();
  if (secret !== process.env.REVALIDATE_SECRET) {
    return Response.json({ ok: false }, { status: 401 });
  }
  revalidateTag(tag);
  return Response.json({ ok: true });
}

Tag your data fetches (fetch(url, { next: { tags: ['page:' + slug] } })) and now a CMS webhook can flip a single page fresh in seconds. This is how you handle a jobs board or product catalog without SSR-ing every request.

The gotchas

  • Stale-while-revalidate confuses new devs. The first visitor after expiry gets the stale copy. If that visitor is Googlebot and freshness matters, you may need on-demand invalidation instead.
  • Cache storage costs money. Vercel, Netlify, and Cloudflare all charge for ISR cache. At 500k URLs, model this before committing.
  • 404s cache too. If a page 404s during a bad deploy or DB blip, that 404 is now cached for revalidate seconds. Add explicit checks and short revalidate on notFound() paths.

SSR: use it when you must, not by default

SSR earns its place in three scenarios:

  1. Genuine per-request personalization. Logged-in dashboards, geo-priced product pages where the price is authoritative and legal exposure exists if it's stale.
  2. True real-time content. Live scores, auction prices, availability that changes minute-to-minute and where showing stale data breaks trust.
  3. URLs Googlebot barely visits. If a page gets crawled once a quarter, caching it is pointless overhead.

For programmatic SEO specifically, SSR is almost always the wrong call. You're generating templated content from a database. The database changed 30 minutes ago; nobody, including Google, cares if the HTML lags by an hour. Paying compute per crawl to render identical HTML is just lighting money on fire.

The one exception we've seen work: SSR with an aggressive CDN edge cache (say, s-maxage=3600) that behaves like ISR but with more control over cache keys. This is useful when you need to vary cache by geography or A/B bucket without ISR's rigid path model.

A decision table we actually use

ScenarioURLsUpdate cadenceRecommended
Marketing site< 500Weekly+Static
Docs / blog< 5kDaily+Static or ISR
Programmatic (catalog, directory)5k–500kHourly–weeklyISR + on-demand
Jobs board, marketplace10k+Minute-levelISR with 60–300s TTL + webhook
Personalized dashboardAnyPer requestSSR
Live prices / auctionsAnyReal-timeSSR + short edge cache

The measurement loop

Whatever you pick, instrument it. The three metrics that matter:

  • p75 TTFB per template, split by cache HIT vs MISS. If MISS TTFB is over 800ms, your regeneration is too slow and Googlebot will notice.
  • Cache hit rate by URL bucket. Top 1k pages should be near 100%. Long-tail pages will be lower — that's fine, but if the long tail is under 30% hit rate you're basically running SSR with extra steps.
  • Regeneration duration. If a single page takes over 3 seconds to regenerate, your data fetching is the bottleneck, not the framework.

All three are trivially available in Vercel/Cloudflare logs or via a custom middleware that stamps x-cache and x-render-ms headers.

Where we'd start

If you're greenfielding a programmatic site today: default to ISR in Next.js App Router with a 24-hour revalidate, pre-build only your top 1–2k pages by projected traffic, and wire on-demand revalidation to your content pipeline from day one. Add a x-render-source response header so you can distinguish static, ISR-warm, and ISR-cold in logs.

If you're inheriting a fully SSR programmatic site with a scary bill: pick the ten highest-traffic templates, move them to ISR with a 1-hour TTL, and measure the cost delta over a week before touching anything else. It's almost always an 80% reduction, and the freshness cost is nearly zero for templated content.

If you want a second pair of eyes on the architecture, our team does this work as part of our web engineering services.

#Programmatic SEO#Next.js#Rendering#Core Web Vitals#Crawl Budget

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project