Skip to content

Static Generation at Scale: Why FH Builds Ship 800+ Pages Without a Headless CMS

Headless CMS is the wrong answer for most marketing sites. Static generation from TypeScript data is faster, cheaper, and easier to maintain.

John Cravey with AIFounder4 min readUpdated Jul 6, 2026

Every FH client site has 50–800 generated pages — location pages, service pages, blog posts, neighborhood pages, programmatic landing pages. None of them come from a headless CMS. They come from TypeScript data files compiled at build time. The result: zero database calls at request time, zero CMS subscription fees, full version control over content, and pages that serve in under 80ms from the CDN.

Free estimate · 2 minutes

You read the mechanism. Now see it for your site.

The estimate sketches the SEO system we would build for your business, at your scale. About a minute, no opt-in.

Why most SMB sites don’t need a CMS

The headless CMS pitch is editorial autonomy. The reality for SMBs: editorial autonomy is the engineering team adding content via a different UI. Most FH clients don’t have a non-technical content team. The owner sends us a doc, we paste the content into TypeScript, we deploy. Total time: 20 minutes. A CMS adds infrastructure, monthly cost, schema migration friction, and a permanent dependency we don’t need.

When a CMS does make sense: a client publishing weekly content with a non-technical team, or a content surface that needs frequent updates from multiple authors. For everything else, TypeScript data files win.

The pattern: typed data + generateStaticParams

A single TypeScript file exports an array of typed content. The route uses `generateStaticParams` to read that array and tell Next to pre-render one page per entry at build time. Each page reads its content by slug at build time and serves the resulting static HTML.

// lib/locations.ts
export interface Location {
  slug: string;
  city: string;
  state: string;
  neighborhoods: string[];
  serviceBlurb: string;
}

export const LOCATIONS: Location[] = [
  { slug: "dallas", city: "Dallas", state: "TX", neighborhoods: ["Highland Park", "Lakewood"], serviceBlurb: "…" },
  { slug: "fort-worth", city: "Fort Worth", state: "TX", neighborhoods: ["TCU", "Sundance Square"], serviceBlurb: "…" },
  // … 50+ entries …
];
// app/locations/[slug]/page.tsx
import { LOCATIONS } from "@/lib/locations";

export function generateStaticParams() {
  return LOCATIONS.map((l) => ({ slug: l.slug }));
}

export default async function LocationPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const location = LOCATIONS.find((l) => l.slug === slug);
  if (!location) return notFound();
  return <main>{/* … */}</main>;
}

Why this scales further than people expect

Next builds pages in parallel. 200 location pages, 50 service pages, 100 blog posts — the build still finishes in under 2 minutes on a 4GB VPS. Pre-rendered HTML is cached by the CDN and never re-rendered until the next deploy. The runtime cost per page is zero.

How to keep TypeScript-as-CMS sane

  • Type the content interface tightly. Optional fields are temptations — keep them rare.
  • Split content by topic into separate files, merged via a barrel module (we do this on FH’s blog).
  • Use slug-derived helpers (slugify, related, postsByTag) in the barrel so consumers don’t reinvent them.
  • Run `tsc` and `eslint` in CI — TypeScript catches the structural errors a CMS schema would catch later.
  • Lint for typos: a quick regex check on common words in content, or a copy-edit pass before deploy.

What about previewing changes?

Two patterns. For small edits, deploy preview environments on every PR — Coolify supports this, Vercel does it by default. The reviewer sees the change before it lands. For larger content drops, we sometimes build a `/admin/preview/[slug]` route gated by an auth cookie that renders the page from a draft branch. Either way, we don’t need a CMS preview surface.

Bulk-generating content: AI-assisted but human-edited

When a client needs 50 location pages in a sprint, we don’t hand-write each one. We use the Anthropic API to draft each page from a template and the location’s data, then a human reviewer edits each draft for voice, accuracy, and local detail. The draft cuts the writing time per page from 90 minutes to 15. The human review keeps the content from sounding generic.

Updating content without redeploying

If content changes are frequent, pair static generation with ISR. Set `revalidate = 3600` on the route, write content updates to a JSON file in storage, and have the page read from storage at revalidate time. We rarely need this for marketing pages but it’s the bridge if a client outgrows the pure-static pattern.

SEO benefits over CMS-backed sites

Pre-rendered HTML serves to crawlers without JavaScript execution. Googlebot indexes the full content immediately. Search Console picks up new pages within a day of deploy. CMS-backed sites that render content client-side often have indexing delays — we’ve audited two prospective clients whose CMS was the reason 40% of their pages were unindexed.

When CMSs do make sense

  • Multi-author teams publishing 5+ times per week (rare in SMB).
  • Content surfaces where non-technical reviewers approve drafts (also rare).
  • Localized content with complex translation workflows (some retail clients).
  • Anything where the content team is bigger than the engineering team.

If those apply, we route clients to Payload CMS or Sanity rather than rebuilding a CMS in-house. Both work cleanly with Next.js and both can still pre-render statically. The TypeScript-as-CMS pattern just covers more cases than people assume.

Pulling it together

If your SMB site is on a CMS you don’t need, you’re paying a tax for editorial autonomy you’re not using. Migrating to a static-generated Next.js site cuts your hosting bill, your page-load time, and your CMS subscription. Schedule a consultation — we’ll audit whether the CMS is earning its keep and quote the migration if it isn’t.

Answers

Frequently asked questions

Why do most marketing sites not need a CMS?

Because a CMS solves a problem most small sites do not have: many non-technical editors changing content constantly. When content changes rarely and one or two people own it, a CMS adds a service, a schema, an API, and a failure mode, in exchange for an editing interface nobody is waiting for.

What does typed data as a CMS look like?

Content as typed data in the repository, with routes generated from it at build time. The type system validates the shape, code review covers the change, and there is no runtime dependency at all. For a marketing site with a handful of editors this is faster to work with, not merely cheaper.

How far does this pattern actually scale?

Further than people expect. Hundreds of pages generated from typed data build quickly and serve as static files. The limit is not page count; it is how many people need to edit concurrently and whether they can work in a repository.

How do you keep TypeScript-as-CMS maintainable?

One module per content cluster rather than one enormous file, a barrel that merges them, and tests asserting the contract each cluster must meet. Without those the file becomes unreviewable and every content change carries merge-conflict risk.

How do you preview changes without a CMS?

Through the normal development and preview-deploy flow, which shows the real site rather than an editor's approximation. It is a worse experience for someone who does not use a repository and a better one for anyone who does, because what you see is the actual page.

Can content be generated in bulk this way?

Yes, and it should still be human-edited before it ships. Generating structured entries into typed data is mechanical; deciding what is worth publishing is not. The type system catches shape errors, which is exactly the class of mistake bulk generation produces most.

How do you update content without a redeploy?

With an overlay: a small database layer that can override or add entries at runtime, read through the same interface as the typed data. That keeps the default path static and fast while giving urgent edits a route that does not require a build.

What are the SEO advantages over a CMS-backed site?

Mostly performance and control. Static pages serve fast without a database in the request path, and metadata, structured data, and internal linking are code rather than plugin configuration. The gains are not magic; they come from removing layers between the request and the HTML.

When does a CMS genuinely make sense?

Many concurrent non-technical editors, editorial workflow with approvals, scheduled publishing at volume, or content managed by people who will never touch a repository. Those are real needs and a CMS answers them well. Most small business sites have none of them.

What is the risk of this approach?

That content becomes developer-gated. If a typo fix needs a pull request and a deploy, small corrections stop happening. The overlay path exists to prevent exactly that, and a project without one eventually accumulates a list of tiny wrong things nobody wants to redeploy for.

How does build time behave at hundreds of pages?

It grows, and it is manageable with partial pre-rendering: build the pages that matter most and render the tail on demand with regeneration. That caps build time and memory regardless of how large the corpus grows, which is the constraint that actually bites first.

Can this coexist with a database-backed CMS later?

Yes, and that is the usual path: typed data as the base, a database overlay added when editing needs outgrow the repository. Reading both through one interface means the pages do not care which source answered, and the migration happens gradually rather than as a rewrite.

Question we did not answer? Ask us directly and we will answer it here.

John Cravey, Founder
Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
Why Isn’t My Page Indexed? 8 Causes, Fixed
Older post
Supabase Performance: Indexing, Connection Pooling, and the Postgres Settings That Matter
Keep reading

More from the blog

Next.js·10 min

Structured Data in Next.js: How JSON-LD Gets You Cited by Google and AI

Structured data is how you tell Google and AI what your page means, not just what it says. Here’s the Next.js way to ship it.

Next.js·11 min

Titles, Meta Descriptions, and Social Cards: The Next.js Metadata Playbook for Every Business Size

Your title tag is the ad you never wrote. Here’s how to make Next.js render one that gets clicked, whatever size you are.

Next.js·10 min

Redirects in Next.js: How Not to Torch Your Rankings in a Redesign

Every redesign is a chance to lose the rankings you spent years earning. Redirects are the seatbelt. Here’s how to wear it.