For years, Static Site Generation (SSG) felt like the obvious choice for SEO-focused websites.
Pre-render everything, serve static HTML, and enjoy fast page loads.
That works well until your site grows beyond a few hundred pages.
While building a software comparison platform, I ran into a problem: every new record in the database could generate dozens of new pages. What started as a few hundred URLs quickly became thousands.
At that point, traditional static generation started becoming a bottleneck rather than an advantage.
The Scaling Problem
Imagine a site with:
- 150 software products
- Individual product pages
- Alternatives pages
- Category pages
- Comparison pages
The total URL count grows surprisingly fast.
The challenge isn't serving the pages.
The challenge is rebuilding them.
If every deployment requires regenerating thousands of pages, build times eventually become unacceptable.
A deployment that takes 30 seconds at 100 pages can become several minutes once the site reaches tens of thousands of URLs.
My Initial Approach
My original implementation relied heavily on static generation.
The benefits were obvious:
- Excellent Lighthouse scores
- Fast initial loads
- Predictable SEO behavior
- Simple hosting requirements
However, every time I added or updated records, I needed to consider regeneration costs.
The larger the dataset became, the more painful deployments became.
Moving to Dynamic Rendering
Instead of generating every page at build time, I moved most pages to dynamic rendering backed by a PostgreSQL database.
The application uses:
- Next.js App Router
- Route Handlers
- Server Components
- PostgreSQL via Supabase
A route simply receives a slug and retrieves the relevant record:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const { data } = await supabase
.from("tools")
.select("*")
.eq("slug", slug)
.single()
return <ToolPage tool={data} />
}
The page template remains the same.
The data changes.
Why Performance Wasn't the Problem I Expected
Many developers assume dynamic rendering automatically means slow rendering.
In practice, database query optimization mattered far more than rendering strategy.
The biggest improvements came from:
- Proper indexing
- Eliminating unnecessary queries
- Caching common lookups
- Reducing over-fetching
Once those issues were addressed, page response times remained consistently fast.
Metadata at Scale
One advantage of dynamic rendering is metadata generation.
Every page can generate unique metadata directly from database fields:
export async function generateMetadata() {
return {
title: tool.name,
description: tool.description,
}
}
This allows thousands of pages to maintain unique titles and descriptions without manual intervention.
What I Learned
1. Build Time Is a Cost
Developers often optimize runtime performance while ignoring deployment performance.
At scale, build times become part of the user experience for the development team.
2. Data Models Matter More Than Templates
The quality of a large content platform depends far more on the structure of the underlying data than on the page templates.
Well-designed schemas create flexibility.
Poor schemas create technical debt.
3. Dynamic Doesn't Mean Slow
Modern server rendering is extremely capable when paired with efficient database queries.
4. Start With Simplicity
Many scaling problems don't appear until later.
It's often better to start with a straightforward implementation and optimize once real bottlenecks emerge.
A Real-World Example
One project where I applied this approach is SoftwareDuel, a software comparison platform that relies heavily on dynamic routes and structured data rather than manually managed content.
The architecture would have been significantly more difficult to maintain using a traditional static-generation-first approach.
Final Thoughts
Static Site Generation remains an excellent tool.
But once a project begins generating thousands of URLs from structured data, it's worth reconsidering whether every page truly needs to exist at build time.
In my experience, moving to a dynamic architecture simplified deployments, reduced maintenance overhead, and made future growth far easier to manage.
The best solution wasn't eliminating complexity.
It was moving that complexity from the build process into the application itself.
Top comments (0)