When your baby name website grows to 3,000+ names, page performance becomes a real challenge. Here's how we solved it.
The Problem
Our browse page loads names grouped by origin — Irish, French, Japanese, Hebrew, and 40+ more categories. With 2,800+ names, the page was shipping 3.6MB of HTML on first load. Category pages like Popular Names had similar issues.
Users on mobile were waiting 4-6 seconds for the page to become interactive. Not great for a tool people use while excitedly planning their baby's name.
The Solution: Progressive Disclosure
Instead of loading all names at once, we show the first 30 names per section and let users expand with a "Show all X names" button.
Architecture
We kept our pages as server components for SEO (search engines see all names), but wrapped the display logic in client components:
// BrowseNameList.tsx - Client Component
"use client";
import { useState } from "react";
export default function BrowseNameList({ groupedNames }) {
return (
<>
{Object.entries(groupedNames).map(([origin, names]) => (
<OriginSection key={origin} origin={origin} names={names} />
))}
</>
);
}
function OriginSection({ origin, names }) {
const [expanded, setExpanded] = useState(false);
const visible = expanded ? names : names.slice(0, 30);
return (
<section>
<h2>{origin} Names ({names.length})</h2>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{visible.map(name => <NameCard key={name.slug} {...name} />)}
</div>
{names.length > 30 && !expanded && (
<button onClick={() => setExpanded(true)}>
Show all {names.length} names
</button>
)}
</section>
);
}
Results
| Page | Before | After | Reduction |
|---|---|---|---|
| /browse | 3.6MB | 820KB | 77% |
| /popular-names | 1.3MB | 220KB | 83% |
| /modern-names | 3.1MB | ~400KB | 87% |
Key Decisions
1. Server components stay server components. All SEO metadata, JSON-LD schema, and the full name list are still rendered server-side. Search engines see everything.
2. Client components only handle display logic. The useState for expand/collapse is the only client-side state. No data fetching, no side effects.
3. Each origin section manages its own state. Users expanding "Irish Names" doesn't affect "French Names." Independent, predictable behavior.
Why Not Pagination?
We considered traditional pagination but rejected it:
- SEO penalty: Multiple paginated pages split link equity
- UX friction: Users hate clicking "Page 2, 3, 4..."
- Search intent mismatch: People browsing baby names want to scan quickly, not navigate pages
Progressive disclosure gives us the best of both worlds — fast initial load AND all content accessible without navigation.
What's Next
We're exploring:
- Virtual scrolling for the expanded view (react-window)
- Intersection Observer to lazy-load sections as users scroll
- Image lazy loading for name cards with famous people photos
Check out the live implementation at babynamepick.com/browse, or explore specific categories like Irish Names, Rare Names, or French Baby Names.
We've also been writing about the journey of building this site:
Building BabyNamePick — a free baby name search engine with 3,000+ names, meanings, and origins. Open to feedback!
Top comments (0)