Ver en Español
Rendering Modes in Next.js
Apr 11, 2023
Updated: Jun 25, 2026

Rendering Modes in Next.js

One of Next.js's great strengths is that it can render each part of your application with the most suitable strategy. As we saw in the previous post, Fetching data with Next.js, the way you fetch data determines how each page is pre rendered, rendered, and displayed. These are the main strategies:

  • Static Generation (SSG)
  • Server Side Rendering (SSR)
  • Client Side Rendering (CSR)

Valid as of Next.js 16: These three concepts are still valid as mental models, but the way you choose them changed. In the App Router (recommended since Next.js 13) you no longer use page level functions like getStaticProps or getServerSideProps: rendering is driven by Server Components, the `fetch` cache, and route segment config. Below I map each classic concept to its modern equivalent.

Static Generation (SSG)

These are pages generated completely as HTML at build time, very easy to cache and serve from a CDN. Ideal for content that doesn't change on every request (landing pages, blog posts, documentation).

  • App Router (today): an async Server Component that does a cached fetch (the default behavior) is statically prerendered. For dynamic routes, you use generateStaticParams to declare which routes to generate at build time.
  • Pages Router (legacy): this was getStaticProps (and getStaticPaths for dynamic routes).
// app/blog/[slug]/page.js — SSG in the App Router
export async function generateStaticParams() {
  const posts = await fetch('https://api.mydomain.com/posts').then((r) => r.json());
  return posts.map((post) => ({ slug: post.slug }));
}

export default async function Page({ params }) {
  const { slug } = await params;
  // cached fetch by default => this page is prerendered at build time
  const post = await fetch(`https://api.mydomain.com/posts/${slug}`).then((r) => r.json());
  return <article>{post.title}</article>;
}

Server Side Rendering (SSR)

These are pages that are not fully generated at build time, because they need server data on every request (for example, content personalized per user). The HTML is built on the server when the user requests the page.

  • App Router (today): you mark data fetching as dynamic with fetch(url, { cache: 'no-store' }), or configure the segment with export const dynamic = 'force-dynamic'.
  • Pages Router (legacy): this was getServerSideProps. Avoid getInitialProps: it's been discouraged since Next.js 9 (it disables Automatic Static Optimization) and has no place in the App Router.
// app/dashboard/page.js — SSR in the App Router
export default async function Page() {
  // 'no-store' => runs on every request (dynamic)
  const data = await fetch('https://api.mydomain.com/me', { cache: 'no-store' }).then((r) => r.json());
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Client Side Rendering (CSR)

Sometimes the initial HTML is served the same for everyone, but once loaded, the component needs to request data from the browser (for example, data that depends on something that only exists on the client).

A note on terminology: CSR (Client Side Rendering) is a rendering strategy; SPA (Single Page Application) is an application architecture. They aren't synonyms, even though they're often mentioned together.

  • App Router / React 19 (today): you do this in a Client Component ('use client') with the useEffect hook, the use() hook, or a data library like SWR or TanStack Query.
  • Legacy: this used to be done with componentDidMount in class components, now obsolete in a React 19 codebase.
'use client';
import { useEffect, useState } from 'react';

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

  useEffect(() => {
    fetch('/api/widget').then((r) => r.json()).then(setData);
  }, []);

  return <div>{data ? data.value : 'Loading...'}</div>;
}

Mixed and default rendering

The most common pattern in the App Router is to combine strategies within a single route: a Server Component that prerenders the static content, with a Client Component inside it that fetches data in the browser. You don't have to pick one strategy for the whole page.

And two modern capabilities take this further:

  • Streaming + `<Suspense>`: Next.js can send the ready part of the page first and stream the rest as it resolves, showing loading states without blocking everything.
  • PPR (Partial Prerendering): combines a prerendered static shell with dynamic "holes" filled in at request time, the best of SSG and SSR in the same route.

Conclusion

I hope this post is useful to you and that you can apply it to a project you have in mind, or that it simply helped you understand the mechanisms Next.js offers for rendering pages, now through the lens of the App Router.

Practice exercises

  1. Create a Next.js 16 app and experiment with each strategy: a static page (SSG with a cached fetch), a dynamic one (SSR with cache: 'no-store'), and a Client Component that fetches data in the browser (CSR).
  2. Implement a route that combines a Server Component (static content) with a Client Component (client data).
  3. Try <Suspense> with streaming and compare performance and user experience against a fully dynamic page.

3-point summary

  1. SSG, SSR, and CSR are still valid as mental models; what changed is how you choose them.
  2. In the App Router, rendering is driven by Server Components, the `fetch` cache, and segment config (dynamic/revalidate), not getStaticProps/getServerSideProps (that's Pages Router, legacy), and never getInitialProps.
  3. You can combine strategies in a single route, and use streaming and PPR to serve the static instantly and the dynamic on demand.

Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias