Server-Side Rendering (SSR)

Understand Server-Side Rendering in Next.js — when to use it, how it works with the App Router, and how to avoid the performance traps that catch most developers.

Last reviewed: January 2025

·Next.js 13Next.js 14Next.js 15
✅
Quick Understanding: SSR means HTML is generated on the server for every request. In the App Router, any component that reads request-specific data (cookies, headers, searchParams) automatically becomes dynamic (SSR). There is no getServerSideProps anymore — just async Server Components.

🔍 What Is It?

Server-Side Rendering (SSR) generates a fresh HTML response for every incoming request. In Next.js App Router:

  • →A route is dynamic (SSR) when it accesses request-specific data: cookies(), headers(), searchParams, or uses fetch with cache: 'no-store'
  • →A route is static (SSG) when it has no request-specific dependencies
  • →Next.js automatically detects which mode to use — no explicit configuration needed

SSR is the right choice when:

  • →Content is user-specific (logged-in user's dashboard)
  • →Content changes frequently and must be fresh on every request
  • →You need access to request cookies/headers (auth sessions)
  • →Real-time data that cannot be stale even for 1 second

🎯 Why Do We Need It?

Static pages are faster but cannot serve personalised content. SSR bridges the gap:

  • →Authentication — serve different content based on the user's session
  • →Personalisation — show user-specific data, preferences, recommendations
  • →Real-time accuracy — stock prices, live inventory, breaking news
  • →A/B testing — serve different variants based on cookies

Without SSR, you'd either serve the same page to everyone (wrong) or fetch personalised data client-side (slow, bad UX, bad SEO).

🏗️ What Happens in Real Projects?

SSR routes in a real enterprise app:

  • →/dashboard — reads session cookie, fetches user-specific metrics
  • →/checkout — reads cart from session, checks real-time inventory
  • →/search?q=... — reads searchParams, returns personalised results
  • →/account/orders — reads auth cookie, fetches user's orders

Static routes in the same app:

  • →/products — same for all users, revalidated every hour (ISR)
  • →/blog/[slug] — pre-built at deploy time (SSG)
  • →/about, /pricing — pure static HTML

⚙️ How It Works

SSR request lifecycle in App Router:

  1. Request hits the server with a cookie session=abc123
  2. Next.js identifies the route as dynamic (it reads cookies)
  3. The Server Component runs: const session = await getSession()
  4. Data is fetched based on the session: await db.query(userId)
  5. HTML is rendered and streamed to the browser
  6. The browser displays the HTML — no loading spinner for the initial content
  7. Client Components hydrate for interactivity

Opting into dynamic rendering:

import { cookies } from 'next/headers';

export default async function Page() {
  const cookieStore = await cookies(); // This makes the route dynamic
  const token = cookieStore.get('token');
  // ...
}

💻 Example

SSR Patterns in Next.js 14 — Dynamic routes that correctly handle authentication and personalisation.

tsx
// app/dashboard/page.tsx
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { verifySession } from '@/lib/auth';
import { db } from '@/lib/db';

export default async function DashboardPage() {
  // Reading cookies makes this route dynamic (SSR)
  const cookieStore = await cookies();
  const sessionToken = cookieStore.get('session')?.value;

  if (!sessionToken) {
    redirect('/login');
  }

  const session = await verifySession(sessionToken);
  if (!session) redirect('/login');

  // Fetch user-specific data
  const metrics = await db.metrics.findMany({
    where: { userId: session.userId },
  });

  return (
    <div>
      <h1>Welcome back, {session.user.name}</h1>
      <MetricsGrid metrics={metrics} />
    </div>
  );
}

🤔 Common Confusions

❓ How do I opt into SSR in the App Router?

You do not opt in explicitly — Next.js detects it automatically. Use cookies(), headers(), or searchParams, or set cache: "no-store" on a fetch call. The route becomes dynamic automatically. You can also set export const dynamic = "force-dynamic" at the top of a page file.

❓ Is SSR in the App Router the same as getServerSideProps?

Functionally yes — HTML is generated per request. But the implementation is completely different. Instead of a separate getServerSideProps function, you use async Server Components that read cookies/headers. The result is the same: fresh HTML per request.

❓ Does SSR hurt performance?

SSR adds server processing time per request, but it eliminates the client-side loading waterfall. For users, SSR often feels faster because they see content immediately instead of a loading spinner. The tradeoff: server CPU cost vs client loading time. For personalised content, SSR is almost always the right choice.

🚨 Common Production Issues

Accidentally making static routes dynamic — importing cookies() or headers() anywhere in the component tree makes the entire route dynamic, even if you do not use the value. Only import them when you actually need them.

SSR without caching under load — a high-traffic SSR route that queries the DB on every request will overwhelm your database. Add Redis caching or use unstable_cache to cache the DB results for a few seconds.

Slow TTFB (Time To First Byte) — if your SSR data fetching is slow, users wait for the first byte of HTML. Use streaming (Suspense) to send the page shell immediately and stream in the data-dependent parts.

✅ Best Practices

Use SSR only when content is genuinely request-specific — prefer ISR for content that changes infrequently

Cache DB queries in SSR routes with unstable_cache or Redis — do not hit the DB on every request

Use streaming to send the page shell immediately while data loads

Set explicit cache headers on SSR responses — even dynamic pages can be cached at the CDN for a few seconds

Profile your SSR routes with next build and server timing headers to find slow data fetches

🏛️ Architect's Note

Senior Architect's Perspective

A common architectural mistake is defaulting everything to SSR. SSR has a real cost: every request hits your server and often your database. The right architecture uses SSR only where necessary and pushes everything else to static (SSG/ISR).

The decision tree: Is the content the same for all users? → SSG/ISR. Does it change frequently? → ISR with short revalidation. Is it user-specific? → SSR. Can it be fetched client-side after initial load? → Static shell + client fetch.

💬 Frequently Asked Questions

Can I cache SSR responses at the CDN level?

Yes — even dynamic routes can be cached at the CDN if you set Cache-Control headers. For semi-personalised content, use stale-while-revalidate with a short TTL. For fully personalised content (user-specific), set private, no-cache to prevent CDN caching.

How do I handle errors in SSR routes?

Add an error.tsx file in the same directory as your page.tsx. It automatically catches errors thrown during rendering. For expected errors (not found, unauthorised), use notFound() or redirect() which are handled gracefully without error boundaries.

What is the performance difference between SSR and SSG?

SSG serves pre-built HTML from a CDN — response times of 10–50ms. SSR generates HTML per request — response times of 100–500ms+ depending on data fetching. For high-traffic pages, the difference in infrastructure cost and user experience is significant.

🎯 Key Takeaways

1SSR in App Router is automatic — reading cookies(), headers(), or using cache: "no-store" makes a route dynamic
2No more getServerSideProps — async Server Components replace it entirely
3SSR is for user-specific, real-time, or personalised content
4Cache DB queries in SSR routes — do not hit the database on every single request
5Use streaming to improve perceived performance of SSR routes
6Default to static (SSG/ISR) and only use SSR when you genuinely need per-request freshness

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Next.js references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Next.js learning path

Browse the full Next.js topic collection and learning order on SplashRide.

Next.js Interview Prep

Practice Next.js interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Static Site Generation (SSG)

Master Static Site Generation in Next.js — pre-building pages at deploy time for maximum performance, CDN delivery, and zero server costs.

Incremental Static Regeneration (ISR)

Master ISR — the Next.js feature that gives you static performance with dynamic freshness. Learn time-based and on-demand revalidation for real production systems.