DEV Community

Yunhan
Yunhan

Posted on

How I Generate 3,400 Static Pages Without Losing My Mind

BabyNamePick generates over 3,400 static pages at build time. Here's how I keep that manageable.

The Page Breakdown

  • ~2,000 individual name pages (/name/[slug])
  • ~130 blog posts (/blog/[slug])
  • ~50 category pages (/[category])
  • 26 letter index pages (/letter/[letter])
  • ~20 static pages (home, about, privacy, etc.)

All generated from a single JSON data file and a TypeScript posts file.

The Data Architecture

Everything flows from two sources:

// names.json — 2,000+ entries
[
  {
    "name": "Sakura",
    "meaning": "Cherry blossom",
    "gender": "girl",
    "origin": "japanese",
    "style": ["nature", "elegant"],
    "popularity": "popular",
    "startLetter": "S"
  }
]

// posts.ts — 130+ blog entries
export const posts: BlogPost[] = [
  {
    slug: "autumn-baby-names",
    title: "Autumn Baby Names...",
    content: `...markdown content...`
  }
]
Enter fullscreen mode Exit fullscreen mode

generateStaticParams — The Heart of It

Next.js's generateStaticParams is what makes this work:

// app/name/[slug]/page.tsx
export async function generateStaticParams() {
  const names = await getNames();
  return names.map(name => ({
    slug: name.name.toLowerCase().replace(/\s+/g, '-')
  }));
}
Enter fullscreen mode Exit fullscreen mode

At build time, Next.js calls this function once, gets 2,000+ slugs, and generates a static HTML page for each one.

Build Time Optimization

1. Minimize Data Reads

The naive approach reads names.json in every page component. Instead, I read it once and cache:

let cachedNames: Name[] | null = null;

export async function getNames(): Promise<Name[]> {
  if (cachedNames) return cachedNames;
  cachedNames = JSON.parse(
    await fs.readFile('data/names.json', 'utf-8')
  );
  return cachedNames!;
}
Enter fullscreen mode Exit fullscreen mode

2. Parallel Page Generation

Next.js handles this automatically with output: "export", but I make sure my data functions are pure and don't have side effects that could cause race conditions.

3. Keep Components Simple

Each name page is essentially:

export default async function NamePage({ params }) {
  const name = await getNameBySlug(params.slug);
  return <NameDetail name={name} />;
}
Enter fullscreen mode Exit fullscreen mode

No client-side data fetching. No API calls. Just props → HTML.

The Gotchas

Dynamic Routes Need All Params

With output: "export", every dynamic route must have a corresponding generateStaticParams. Miss one and the build fails silently — the page just doesn't exist.

Build Memory

3,400 pages can eat RAM. I've hit Node.js heap limits on smaller machines. The fix:

NODE_OPTIONS="--max-old-space-size=4096" next build
Enter fullscreen mode Exit fullscreen mode

Incremental Updates Are Manual

Adding a new name means rebuilding all 3,400+ pages. At ~3 minutes per build, this is fine for now. At 10,000 pages, I'll need to rethink.

The Sitemap Challenge

Google needs to know about all 3,400 pages. I generate the sitemap at build time:

export default async function sitemap() {
  const names = await getNames();
  const posts = getAllPosts();

  return [
    ...names.map(n => ({
      url: `https://babynamepick.com/name/${slugify(n.name)}`,
      lastModified: new Date(),
      priority: 0.6,
    })),
    ...posts.map(p => ({
      url: `https://babynamepick.com/blog/${p.slug}`,
      lastModified: new Date(p.date),
      priority: 0.8,
    })),
  ];
}
Enter fullscreen mode Exit fullscreen mode

When to Move Beyond Static

I'm approaching the limits of this approach. Signs it's time to add a database:

  • Build times exceed 10 minutes
  • You need real-time search (not just client-side filtering)
  • User-generated content (reviews, favorites)
  • Personalization that can't be done client-side

For now, static works beautifully. Zero hosting cost, sub-50ms TTFB, and 100% uptime.


BabyNamePick — 2,000+ baby names, 46 cultures, 3,400 static pages. All free.

Top comments (0)