Skip to content

Next Rendering Strategies

SSR , SSG , ISR , CSR - four ways to render the same React component with dramatically different performance , freshness , and cost profiles

Next.js lets you pick per page or even per component. One misconfigured revalidation interval and you're either serving stale data for hours or hammering your database on every request. Know the difference before you pick

the four strategies

SSR - Server-Side Rendering

HTML generated on every request. Fresh data always. Higher latency and server cost

// src/app/profile/page.tsx - SSR by default (no generateStaticParams)
export default async function ProfilePage() {
  // This fetch runs on every request
  const data = await fetch('https://api.example.com/user', {
    cache: 'no-store' // forces SSR behavior
  })
  const user = await data.json()

  return <div>{user.name}</div>
}

SSR is the default when you use cache: 'no-store' or when the page has dynamic functions like cookies() , headers() , or searchParams

Use SSR for: * Pages with user-specific data (dashboard , settings , profile) * Pages that need real-time data (scores , prices , status) * Pages behind auth where caching doesn't help

SSG - Static Site Generation

HTML generated at build time. Blazing fast. Stale until next build

// src/app/about/page.tsx - SSG by default
export default async function AboutPage() {
  // This fetch runs at build time
  const data = await fetch('https://api.example.com/about')
  const content = await data.json()

  return <div>{content.body}</div>
}

Without cache: 'no-store' or generateStaticParams , Next.js assumes the page can be statically generated. The HTML is generated during next build and served as a flat file

Use SSG for: * Marketing pages , landing pages , documentation * Public content that changes rarely * Pages where build-time data is acceptable

ISR - Incremental Static Regeneration

HTML generated at build time , revalidated in the background after a timeout

// src/app/blog/[slug]/page.tsx - ISR with revalidation
export default async function BlogPost({
  params
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const data = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 3600 } // revalidate every hour
  })
  const post = await data.json()

  return <article>{post.title}</article>
}

ISR behavior: 1. First request after build serves the statically generated page 2. After revalidate seconds , the first request triggers a background regeneration 3. Subsequent requests see the new page 4. If regeneration fails , the old page is served (stale data but no downtime)

// on-demand revalidation - trigger from webhook or admin action
// src/app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'

export async function POST(request: Request) {
  const { path } = await request.json()
  revalidatePath(path)
  return Response.json({ revalidated: true })
}

Use ISR for: * Blog posts , news articles , product pages * Content that updates periodically but doesn't need real-time freshness * Pages where build-time generation is too slow but SSR is overkill

CSR - Client-Side Rendering

Empty HTML shell , React renders everything in the browser

'use client'

import { useEffect, useState } from 'react'

export default function Dashboard() {
  const [data, setData] = useState(null)

  useEffect(() => {
    fetch('/api/dashboard-data')
      .then(res => res.json())
      .then(setData)
  }, [])

  if (!data) return <div>Loading...</div>
  return <div>{data.metrics}</div>
}

Use CSR for: * Highly interactive dashboards with real-time updates * Pages behind auth where SEO doesn't matter * Components that need browser APIs

streaming with Suspense

Next.js supports streaming Server Components using React Suspense. Parts of the page load progressively instead of waiting for everything

// src/app/dashboard/page.tsx
import { Suspense } from 'react'
import SlowComponent from './SlowComponent'
import FastComponent from './FastComponent'

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <FastComponent /> {/* renders immediately */}
      <Suspense fallback={<div>Loading slow data...</div>}>
        <SlowComponent /> {/* streams in when ready */}
      </Suspense>
    </div>
  )
}
// src/app/dashboard/SlowComponent.tsx
export default async function SlowComponent() {
  const data = await fetch('https://api.example.com/slow-data', {
    cache: 'no-store'
  })
  await new Promise(resolve => setTimeout(resolve, 3000)) // simulate slow response
  const result = await data.json()

  return <div>{result.value}</div>
}

Streaming improves perceived performance. The user sees the fast parts immediately while slow data loads in the background

picking the right strategy

Strategy Freshness Speed Server Cost SEO
SSG Build-time only Fastest Lowest Best
ISR Revalidate interval Near-instant after first load Low Best
SSR Every request Depends on data source Higher Best
CSR Every request Fast (client-side) None (client renders) Worst

The rule of thumb: default to SSG or ISR for public content , use SSR for user-specific pages , use CSR only for components that need browser APIs

prerequisites

next_04_navigation.md - you should understand client-side navigation and prefetching behavior


next → next_06_data_fetching.md