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...`
}
]
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, '-')
}));
}
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!;
}
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} />;
}
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
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,
})),
];
}
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)