DEV Community

Yunhan
Yunhan

Posted on

Static Site Generation for 1,200+ Pages: Lessons from Building a Baby Name Database

When I started building BabyNamePick, I knew I wanted every single name to have its own page. With 1,200+ names, that meant 1,200+ statically generated pages — plus category pages, letter pages, and blog posts.

Here's what I learned about scaling static generation.

The Numbers

Our current build generates:

  • 1,269 name pages (/name/[slug])
  • 50+ category pages (/[category])
  • 26 letter pages (/letter/[letter])
  • 85+ blog posts (/blog/[slug])
  • Total: ~3,400+ static HTML pages

All from a single names.json file and a TypeScript blog module.

Lesson 1: Keep Your Data Source Simple

We tried a few approaches:

  • SQLite → too much overhead for static generation
  • Markdown files → too many files to manage
  • JSON file → ✅ simple, fast, version-controlled
// data/names.json - 1,269 entries
[
  {
    "name": "Elara",
    "meaning": "Shining light",
    "gender": "girl",
    "origin": "greek",
    "style": ["celestial", "mythological"],
    "popularity": "rising",
    "startLetter": "E"
  }
]
Enter fullscreen mode Exit fullscreen mode

A single JSON file loads in milliseconds, filters in microseconds, and diffs cleanly in git. For our scale (thousands, not millions), it's perfect.

Lesson 2: generateStaticParams Is Your Friend

Next.js App Router's generateStaticParams makes programmatic page generation trivial:

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

The key insight: slugification must be deterministic. We normalize all names to lowercase with hyphens, and we do it the same way everywhere — in generateStaticParams, in internal links, and in the sitemap.

Lesson 3: Category Pages Are Filtered Views

Instead of maintaining separate data for each category, our categories are just filter functions:

// categoryData.ts
export const categories = [
  {
    slug: "celtic-names",
    title: "Celtic Baby Names",
    filterFn: (n) => ["irish","scottish","welsh"].includes(n.origin),
    // ...
  },
  {
    slug: "nature-names", 
    title: "Nature-Inspired Baby Names",
    filterFn: (n) => n.style.includes("nature"),
    // ...
  }
];
Enter fullscreen mode Exit fullscreen mode

A single name like "Rowan" (origin: irish, style: nature) appears on both the Celtic names page AND the Nature names page. No data duplication.

Lesson 4: Build Times Stay Manageable

With ~3,400 pages, our build takes about 2-3 minutes on Vercel. The secret:

  1. No external API calls during build — everything reads from local files
  2. Simple page components — no complex data transformations at build time
  3. Shared data loading — names load once, not per-page

If we grew to 10,000+ pages, we'd consider Incremental Static Regeneration. But at our scale, full rebuilds are fine.

Lesson 5: Sitemap Generation Matters

With 3,400+ URLs, the sitemap is critical for SEO:

// app/sitemap.ts
export default function sitemap(): MetadataRoute.Sitemap {
  const namePages = names.map(n => ({
    url: `https://babynamepick.com/name/${slugify(n.name)}`,
    lastModified: new Date(),
    priority: 0.6,
  }));

  const categoryPages = categories.map(c => ({
    url: `https://babynamepick.com/${c.slug}`,
    priority: 0.8,
  }));

  return [...staticPages, ...categoryPages, ...namePages, ...blogPages];
}
Enter fullscreen mode Exit fullscreen mode

We submit this to Google Search Console and let Google crawl systematically.

Lesson 6: Internal Linking Creates a Web

Every name page links to:

  • Its origin category (/irish-names)
  • Its style categories (/nature-names, /mythological-names)
  • Related names (same origin or style)
  • Relevant blog posts

Every category page links to:

  • All names in that category
  • Related categories
  • Relevant blog posts

This creates a dense web of internal links that helps Google understand topical relationships and distributes page authority throughout the site.

Performance Results

Because everything is static HTML served from a CDN:

  • Time to First Byte: <100ms globally
  • Largest Contentful Paint: <1.5s
  • Cumulative Layout Shift: 0
  • Lighthouse Score: 95+

No server to manage. No database to maintain. No caching to configure.

When NOT to Use This Approach

This pattern works great for:

  • Content that changes infrequently (we add names weekly, not hourly)
  • Pages that can be fully rendered at build time
  • Sites where SEO matters (static HTML is Google's favorite)

It doesn't work for:

  • Real-time data (stock prices, live scores)
  • User-generated content at scale
  • Sites that need per-user personalization

The Bottom Line

Static site generation at scale isn't scary. With a clean data source, deterministic slugification, and smart internal linking, you can build thousands of fast, SEO-friendly pages with minimal infrastructure.

Our entire stack is:

  • Data: One JSON file
  • Framework: Next.js App Router
  • Hosting: Vercel free tier
  • Monthly cost: $0

Check it out at BabyNamePick — browse 1,200+ names, explore 50+ categories, or read our blog.


Questions about scaling static generation? Drop them in the comments.

Top comments (0)