Arquitetura web moderna: SSG, SSR, ISR explicados
When someone visits your website, something has to turn your code into the HTML their browser displays. How and when that transformation happens is the most consequential architectural decision you'll make — it determines your load speed, your server costs, your SEO performance, and how fresh your content can be.
Three rendering strategies dominate modern web development: Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR). Each makes a different tradeoff between performance, freshness, and complexity. Most production sites use a combination of all three. Understanding when to apply each one is the difference between a site that scores 95+ on Lighthouse and one that struggles to break 60.
The Rendering Spectrum
Every web page exists somewhere on a spectrum from fully static to fully dynamic:
Client-Side Rendering (CSR): The server sends an empty HTML shell with a JavaScript bundle. The browser downloads the JS, executes it, fetches data from APIs, and renders the page. The user sees a blank screen (or a spinner) until JavaScript finishes. This is how React apps work by default — and it's terrible for SEO and performance.
Server-Side Rendering (SSR): The server generates complete HTML for every request. The browser receives a fully rendered page immediately. JavaScript then "hydrates" the page to make it interactive. Each request hits the server, which means server load scales linearly with traffic.
Static Site Generation (SSG): All pages are pre-rendered to HTML at build time. The server (or CDN) serves these pre-built files directly. No server-side computation per request. The fastest possible delivery — but content is frozen at build time.
Incremental Static Regeneration (ISR): Pages are statically generated but can be regenerated in the background after a specified interval. Visitors always get a cached static page (fast), while the server quietly rebuilds stale pages behind the scenes (fresh).
| Strategy | When HTML is generated | Server load per request | Content freshness | Performance |
|---|---|---|---|---|
| CSR | In the browser | None (API calls only) | Real-time | Poor (blank screen) |
| SSR | On every request | High | Real-time | Good (depends on server) |
| SSG | At build time | Zero | Frozen at build | Excellent |
| ISR | At build + background | Minimal | Near-real-time | Excellent |
SSG: Build Once, Serve Forever
Static Site Generation is the simplest and fastest approach. During the build process, your framework (Next.js, Astro, Hugo, etc.) generates an HTML file for every page. These files are uploaded to a CDN. When a visitor requests a page, the CDN serves the pre-built HTML file directly — no database query, no server computation, no waiting.
The performance ceiling of SSG is essentially the speed of your CDN. A statically generated page served from a nearby edge node typically achieves:
- TTFB: 20-50ms (compared to 200-800ms for SSR)
- LCP: 0.5-1.2s (compared to 1.5-4.0s for SSR)
- Lighthouse score: 95-100
For marketing websites, blogs, documentation, and landing pages, SSG is almost always the right choice. The content doesn't change between visitor requests, so there's no reason to regenerate it.
Where SSG breaks down:
Scale. A site with 50 pages builds in seconds. A site with 50,000 pages builds in 20-40 minutes. An e-commerce site with 500,000 product pages might take hours to build. Every content change triggers a full rebuild.
Dynamic content. Prices that change hourly, inventory counts, user-specific recommendations, or content that depends on the visitor's location can't be pre-rendered. By the time the visitor sees the page, the data is already stale.
Preview and editing workflows. Content teams want to see changes before publishing. With pure SSG, previewing a draft means triggering a build and waiting. Headless CMS platforms have workarounds (preview modes), but they add complexity.
In Next.js, SSG is the default for pages without dynamic data fetching:
// This page is statically generated at build time
export default async function AboutPage() {
const team = await getTeamMembers() // fetched at build time
return <TeamGrid members={team} />
}
SSR: Fresh on Every Request
Server-Side Rendering generates HTML on the server for each incoming request. The visitor's browser receives a complete page — no blank screen, no waiting for JavaScript to render content. The server does the work once, and the browser just displays the result.
SSR is necessary when the page content depends on the request itself: the user's session, their location, a search query, real-time data from a database, or personalized content. You can't pre-render a page that's different for every visitor.
The tradeoffs:
TTFB increases. Every request waits for the server to fetch data, render HTML, and respond. A well-optimized SSR page serves in 150-400ms. A poorly optimized one (complex database queries, slow external APIs) can take 2-3 seconds. This TTFB hit directly impacts LCP and user experience.
Server costs scale with traffic. Unlike SSG (where the CDN handles everything), SSR requires compute capacity proportional to your traffic. A traffic spike that 10x your normal volume means 10x the server load. Without proper infrastructure, this means downtime at exactly the moment you can least afford it.
Caching partially solves both problems. You can cache SSR output at the CDN level for short periods (30-300 seconds). This gives you near-SSG performance for most visitors while still regenerating frequently enough to keep content fresh. The implementation is subtle — you need to handle cache invalidation, vary headers for personalized content, and stale-while-revalidate semantics.
In Next.js, SSR is triggered by making pages dynamic:
// This page is server-rendered on every request
export const dynamic = 'force-dynamic'
export default async function DashboardPage() {
const user = await getCurrentUser()
const stats = await getUserStats(user.id)
return <Dashboard user={user} stats={stats} />
}
ISR: The Best of Both Worlds
Incremental Static Regeneration, introduced by Next.js and now supported by other frameworks, combines the performance of SSG with the freshness of SSR. The concept is elegantly simple: serve static pages, but regenerate them in the background when they get stale.
Here's how it works:
- At build time, pages are statically generated (just like SSG).
- You specify a
revalidateinterval — say, 60 seconds. - When a visitor requests a page, they get the cached static version (fast).
- If the page is older than 60 seconds, the server regenerates it in the background.
- The next visitor gets the freshly regenerated page.
This is "stale-while-revalidate" semantics. No visitor ever waits for a page to generate. The worst case is seeing content that's up to 60 seconds old — which for most business websites is indistinguishable from real-time.
// ISR: regenerates every 60 seconds
export const revalidate = 60
export default async function BlogPost({ params }) {
const post = await getPost(params.slug)
return <Article post={post} />
}
On-demand revalidation is even more powerful. Instead of time-based regeneration, you trigger a rebuild when content actually changes. Your CMS publishes an article → sends a webhook → your API route calls revalidatePath('/blog/article-slug') → only that page regenerates. No rebuild of the entire site. No stale content window. Instant freshness with static performance.
// API route for on-demand revalidation
export async function POST(request) {
const { slug } = await request.json()
revalidatePath(`/blog/${slug}`)
return Response.json({ revalidated: true })
}
ISR is our default rendering strategy at Empirium for content-driven sites. Blog posts, service pages, and case studies are served statically with on-demand revalidation triggered by CMS webhooks. The result: sub-second load times with content that's always up-to-date.
Choosing the Right Pattern for Your Site
Most production sites don't use a single strategy. The right approach is to choose per page based on content characteristics:
| Page Type | Recommended Strategy | Why |
|---|---|---|
| Homepage | ISR (60-300s) | Content changes occasionally, must be fast |
| Blog posts | SSG or ISR (on-demand) | Content is stable after publish |
| Service pages | SSG | Rarely changes, performance critical |
| Pricing page | ISR (60s) | May change frequently |
| Search results | SSR | Depends on user query |
| User dashboard | SSR | Personalized per user |
| Product listings | ISR (60-300s) | Many pages, moderate freshness needs |
| Legal/policy pages | SSG | Changes quarterly at most |
The decision framework:
- Does the content change between visitors? → SSR
- Does the content change, but infrequently? → ISR with on-demand revalidation
- Is the content stable after publish? → SSG
- Are there too many pages to build at once? → ISR with
dynamicParams: true
For a typical B2B marketing site — homepage, services, blog, case studies, contact — the right approach is: SSG for service and legal pages, ISR with on-demand revalidation for blog and case studies, and SSR only for search or personalized features. This gives you 95+ Lighthouse scores across the site with near-zero server costs.
FAQ
Does rendering strategy affect SEO? Significantly. Google can crawl and index SSG, SSR, and ISR pages immediately because they serve complete HTML. CSR (client-side rendering) is problematic — while Google claims to execute JavaScript during crawling, in practice there are delays and rendering budget limits. If SEO matters (and for B2B, it does), never rely on CSR for important pages.
What does ISR hosting cost compared to SSR? ISR is dramatically cheaper. Since most requests are served from cache (like SSG), your server only handles regeneration requests — a fraction of total traffic. A site serving 100,000 monthly visitors with SSR might need a $50-100/month server. The same site with ISR runs on Vercel's free tier or a $20/month VPS because the cache handles 99%+ of requests.
Can I use ISR without Next.js? Astro supports similar patterns with their hybrid rendering mode. Nuxt has its own ISR equivalent. SvelteKit supports it through adapter configuration. The concept isn't Next.js-specific, though Next.js has the most mature implementation. Static-first architecture principles apply regardless of framework.
What about cold starts with ISR? When a page hasn't been requested recently and the cache is empty, the first visitor triggers a build. On serverless platforms (Vercel, Netlify), this includes a cold start penalty of 200-500ms. Subsequent visitors get the cached result. For high-traffic pages, cold starts are rare. For long-tail pages with infrequent traffic, consider pre-rendering them at build time to avoid the first-visit penalty.
How do I handle dynamic content within static pages? Use a pattern called "static shell with dynamic islands." The page structure, navigation, and primary content are statically rendered. Small dynamic sections (like a live chat widget, personalized recommendations, or real-time stock data) load client-side after the static content is visible. The visitor sees the page instantly, and dynamic elements appear a moment later. This is the approach frameworks like Astro call "islands architecture."