Back to blog
SSR, SSG and ISR: Complete Comparison of Next.js Rendering Strategies in 2026
Performance

SSR, SSG and ISR: Complete Comparison of Next.js Rendering Strategies in 2026

Bastien AllainMarch 6, 202622 min read
ssrssgisrnextjsrenderingperformance

Rendering strategy selection shapes every dimension of a web application, from the initial byte delivered to the browser to the freshness of the data displayed on screen. With Next.js 15 and the App Router, development teams now have access to a spectrum of rendering methods that extends far beyond the classic "static versus dynamic" binary. Server-Side Rendering, Static Site Generation, Incremental Static Regeneration, and the newer Partial Prerendering all coexist within the same framework, each addressing distinct constraints around performance, search engine optimization, and business logic.

Despite this wealth of options, many engineering teams struggle to identify the optimal rendering approach for each route in their application. A poorly chosen strategy can inflate Time to First Byte, impair search engine indexation, or generate disproportionate server load. This technical deep-dive methodically compares these rendering strategies, analyzes their impact on performance and SEO metrics, and provides an actionable decision framework for architects and front-end engineers building production applications in 2026.

Understanding rendering strategies in the App Router context

The Server Components paradigm

Before comparing rendering strategies, the discussion must be grounded in the architectural model defined by the Next.js 15 App Router. Unlike the legacy Pages Router, where every page was a client-side React component by default, the App Router is built natively on React Server Components (RSC). Every component executes on the server by default. It ships zero JavaScript to the client unless the developer explicitly adds the "use client" directive.

This paradigm shift profoundly changes how rendering is conceptualized. The server no longer simply generates raw HTML; it produces a serialized stream (the RSC Payload) containing both the pre-rendered HTML and the instructions required to hydrate interactive components on the client side. This duality is the foundation upon which every rendering strategy discussed in this article is built.

Static versus dynamic rendering: the fundamental distinction

Within the App Router, Next.js automatically determines whether a route should be rendered statically (at build time) or dynamically (at request time). This decision is based on the detection of dynamic functions within the route's code. Using cookies(), headers(), or searchParams in a server component signals to Next.js that the route cannot be pre-computed and must be generated dynamically.

This automatic detection simplifies the developer experience, but it can also produce unexpected behavior when the underlying mechanics are not well understood. A route assumed to be static can silently switch to dynamic rendering because a deeply nested component invokes a dynamic function. Mastering this detection logic is a prerequisite for effectively applying rendering strategies.

Static Site Generation (SSG): build-time rendering

How it works under the hood

Static Site Generation is the most performant rendering strategy in terms of raw response time. The concept is straightforward: HTML pages are fully generated during the build process and stored as static files. When a user or a search engine crawler makes a request, the server or CDN returns the pre-computed file directly, with no additional processing.

In the App Router, a route is statically generated by default if it contains no dynamic functions. For dynamic routes (those using parameters like [slug]), the developer provides the complete list of paths to pre-render via the generateStaticParams function.

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}
 
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  return <Article post={post} />;
}

During the build, Next.js invokes generateStaticParams, retrieves the full list of slugs, and generates an HTML page and RSC Payload for each one. The output is a collection of static files ready for CDN distribution.

Advantages of SSG

The primary strength of SSG is response speed. A static file served from a CDN achieves a Time to First Byte in the range of 20 to 50 milliseconds, regardless of traffic volume. There are no database queries and no server-side code execution at request time. The file is simply transferred.

The second advantage is native scalability. Because the files are static, they can be replicated across hundreds of CDN nodes worldwide. A sudden traffic spike generates zero additional load on the origin server. Infrastructure costs remain constant regardless of concurrent visitor count.

The third benefit is reliability. A static site cannot crash due to database overload or server-side execution errors. If the CDN is operational, the site is available.

Limitations and constraints

The trade-off of SSG lies in data freshness. Content is frozen at build time. If an article is edited or a new product is added, a full rebuild is required before the change becomes visible. For a blog with a few dozen articles, this is acceptable. For an e-commerce catalog with fifty thousand SKUs, build times become prohibitive.

The other limitation is the inability to personalize content. A static page is identical for every visitor. Displaying a personalized cart, a welcome message with the user's name, or recommendations based on browsing history requires client-side JavaScript.

Ideal use cases

SSG is the reference strategy for content with low update frequency. Corporate pages, blog articles, technical documentation, marketing landing pages, and product catalogs with scheduled updates are all ideal candidates. The common denominator: content that does not change between deployments.

Server-Side Rendering (SSR): on-demand rendering

Dynamic rendering in the App Router

Server-Side Rendering generates the page HTML on every incoming request. Unlike SSG, there is no pre-computed file. The server receives the request, executes the React component code, performs the necessary data fetches, produces the complete HTML, and sends it back to the client.

In the App Router, SSR is triggered automatically when Next.js detects dynamic function usage within the route. You can also explicitly force dynamic behavior:

// Force dynamic rendering for this route
export const dynamic = "force-dynamic";
 
export default async function DashboardPage() {
  const session = await getSession();
  const data = await fetchUserData(session.userId);
 
  return (
    <main>
      <h1>Dashboard</h1>
      <UserStats data={data} />
    </main>
  );
}

The dynamic = "force-dynamic" segment configuration tells Next.js that this route must always be rendered at request time, even if the code does not contain explicit dynamic functions.

Streaming and Suspense boundaries

One of the most significant advances in SSR within the App Router is streaming. Instead of waiting for the entire page to be generated before sending the response, the server begins delivering HTML as it is produced. Parts of the page that are ready immediately are sent first, while components requiring longer data fetches are streamed later.

This mechanism relies on React's Suspense boundaries. By wrapping a slow component in a <Suspense> element, the developer defines a split point in the rendering stream. The server sends the fallback first (a loading skeleton, for example), then injects the actual content once the data is available.

import { Suspense } from "react";
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await getProduct(id);
 
  return (
    <main>
      <ProductDetails product={product} />
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews productId={id} />
      </Suspense>
      <Suspense fallback={<RecommendationsSkeleton />}>
        <RelatedProducts productId={id} />
      </Suspense>
    </main>
  );
}

With this architecture, the user sees the core product information almost instantly. Customer reviews and recommendations appear progressively, without blocking the initial render. The Time to First Byte drops significantly because the browser begins receiving content within the first few milliseconds.

Advantages of SSR

SSR guarantees that every request receives up-to-date data. For applications where data freshness is non-negotiable (dashboards, search result pages, shopping carts, personalized content), this is a decisive advantage.

SSR also enables server-side personalization. By accessing cookies, HTTP headers, and request parameters, the server can adapt content based on the user: displaying prices in the correct currency, adapting content to geolocation, or pre-rendering an authenticated state.

Streaming significantly improves perceived performance metrics. Even if the full page takes two seconds to generate, the user sees a coherent initial render within a few hundred milliseconds thanks to the progressive stream.

Limitations and constraints

SSR imposes a server load proportional to traffic. Every request triggers code execution, data fetches, and React rendering. For high-traffic sites, this requires robust, properly sized server infrastructure. Hosting costs are inherently higher than with SSG.

The Time to First Byte is also higher than SSG because the server must perform processing before it can begin sending the response. Even with streaming, the first byte arrives later than a static file served from a CDN.

Incremental Static Regeneration (ISR): the static-dynamic compromise

The revalidation principle

ISR represents an elegant compromise between SSG performance and SSR data freshness. The concept works as follows: pages are statically generated at build time, exactly like SSG, but they can be regenerated in the background after a defined time interval, without requiring a full redeployment.

In the App Router, ISR is configured via the revalidate option on fetch calls or at the route segment level:

// Revalidation at the fetch level
async function getProducts() {
  const res = await fetch("https://api.example.com/products", {
    next: { revalidate: 3600 }, // Revalidate every hour
  });
  return res.json();
}
 
// Or at the route segment level
export const revalidate = 3600;

When a user requests an ISR page whose revalidation interval has expired, the server immediately returns the cached version (the "stale" version) while triggering a background regeneration. The next visitor receives the freshly regenerated version. This is the stale-while-revalidate pattern applied to page rendering.

On-demand revalidation

Beyond time-based revalidation, Next.js provides an on-demand revalidation mechanism. This system allows triggering the regeneration of a specific page in response to an external event, such as a content update in a headless CMS or a database record modification.

// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
 
export async function POST(request: NextRequest) {
  const { path, tag, secret } = await request.json();
 
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
 
  if (tag) {
    revalidateTag(tag);
  } else if (path) {
    revalidatePath(path);
  }
 
  return NextResponse.json({ revalidated: true, now: Date.now() });
}

This webhook can be called by a headless CMS (Sanity, Contentful, Strapi) on every content publication. The affected page is regenerated within seconds, without waiting for the revalidation interval to expire. This is the best available compromise between static performance and editorial freshness.

The cache tags system

The Next.js 15 App Router offers a granular cache tags system that significantly improves revalidation precision. Instead of revalidating an entire path, you can associate semantic tags with your data fetches and invalidate only the relevant caches:

// Associate a tag with a fetch
async function getPost(slug: string) {
  const res = await fetch(`https://api.cms.com/posts/${slug}`, {
    next: { tags: [`post-${slug}`, "posts"] },
  });
  return res.json();
}
 
// Invalidate all pages that use the "posts" tag
revalidateTag("posts");

This level of granularity enables sophisticated caching strategies where each data segment has its own lifecycle. Modifying a product listing revalidates only the pages displaying that product, without affecting the rest of the site.

Ideal use cases

ISR excels for sites with a large volume of pages and moderate update frequency. E-commerce catalogs where prices change daily, news portals where articles are published multiple times per day, and user-generated content sites are all natural fits. ISR delivers response times close to static while maintaining reasonable content freshness.

Partial Prerendering (PPR): the 2026 hybrid approach

A paradigm shift in rendering

Partial Prerendering represents the most significant evolution of rendering strategies in Next.js. While previous approaches force a binary choice per route (static or dynamic), PPR allows combining static and dynamic content within a single page.

The principle works as follows: during the build, Next.js generates a static shell containing all parts of the page that do not depend on dynamic data. Dynamic zones (personalized content, real-time data) are demarcated by Suspense boundaries and filled at request time via streaming.

import { Suspense } from "react";
 
export default function StorePage() {
  return (
    <main>
      {/* Static content: generated at build */}
      <Header />
      <HeroBanner />
      <CategoryNavigation />
 
      {/* Dynamic content: streamed at request */}
      <Suspense fallback={<CartSkeleton />}>
        <UserCart />
      </Suspense>
 
      {/* Static content */}
      <FeaturedProducts />
 
      {/* Dynamic content */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <PersonalizedRecommendations />
      </Suspense>
 
      {/* Static content */}
      <Footer />
    </main>
  );
}

How PPR works in practice

When a user requests this page, the CDN immediately returns the pre-generated static shell. The TTFB matches that of a static file: a few dozen milliseconds. The user instantly sees the header, the banner, the navigation, and the featured products. Simultaneously, the server executes the dynamic components (user cart, personalized recommendations) and injects them into the page via streaming.

The result is a page that combines the best of both worlds: the instantaneous speed of SSG for the structure and permanent content, and the real-time personalization of SSR for the zones that require it.

Impact on application architecture

PPR fundamentally changes how developers structure their applications. Instead of reasoning in terms of "this page is static" or "this page is dynamic," the analysis happens at the component level. Each component is evaluated individually: can it be pre-rendered at build time, or does it require dynamic data?

This granularity encourages an architecture where the boundaries between static and dynamic content are clearly defined in code via Suspense boundaries. Teams adopting PPR observe notable LCP improvements because the primary content is always served statically, while secondary elements load progressively.

SEO implications of each strategy

Crawlability and indexation

From a search engine perspective, the rendering strategy determines how quickly and reliably content is discovered and indexed. SSG provides the strongest indexation guarantee: the HTML is complete, pre-rendered, and immediately available. Crawlers do not need to execute JavaScript or wait for server-side rendering. Content is indexable from the crawler's first visit.

SSR provides equivalent indexation quality because the complete HTML is generated server-side before being sent to the crawler. The difference lies in latency: the crawler must wait for page generation, which consumes crawl budget. For large sites, this additional latency can slow the discovery of new pages.

ISR behaves like SSG for initial indexation, then benefits from revalidation to keep content current. Crawlers always see complete static HTML, with the assurance that the content reflects recent changes within a reasonable timeframe.

TTFB impact and performance signals

Google uses Core Web Vitals as ranking signals. While TTFB is not directly a Core Web Vital, it strongly influences LCP, which is. A rendering strategy that minimizes TTFB therefore has a direct impact on search result positioning.

SSG and PPR (for the static portion) offer the best possible TTFB because files are served from a CDN with no server processing. ISR presents a similar TTFB for cached pages, with slight degradation on the first request after revalidation. SSR consistently shows the highest TTFB, proportional to the complexity of the server operations required to generate the page.

SEO strategies by content type

For editorial content pages (articles, guides, documentation), SSG is the optimal SEO strategy. Content is immediately available, TTFB is minimal, and search engines index the page without friction.

For product catalog pages with frequent updates (pricing, availability), ISR with on-demand revalidation offers the best balance. Content is fast to serve and remains synchronized with backend data.

For internal search result pages or heavily personalized pages, SSR with streaming is the only viable option. However, these pages must be carefully configured with canonical tags and appropriate noindex directives to prevent duplicate content issues.

Performance benchmarks and metrics

TTFB comparison

Time to First Byte is the metric that most clearly differentiates rendering strategies. The following are representative ranges observed in production Next.js 15 applications deployed on edge infrastructure:

SSG: 20 to 60 ms (static file served from the nearest CDN node)

ISR (cached page): 25 to 70 ms (similar to SSG, with slight cache verification overhead)

ISR (first request after revalidation): 200 to 800 ms (background regeneration, stale cache served)

PPR (static shell): 20 to 60 ms (identical to SSG for the shell)

SSR (without streaming): 200 to 1500 ms (depending on server operation complexity)

SSR (with streaming): first byte at 50 to 150 ms, full page at 200 to 1500 ms

First Contentful Paint and Largest Contentful Paint

FCP and LCP are directly influenced by TTFB. With SSG, FCP typically falls under 500 ms and LCP under 1.2 seconds, including browser rendering time and visual resource loading.

SSR with streaming significantly improves FCP compared to classic SSR because the browser begins displaying content upon receiving the first HTML fragment. LCP can remain elevated, however, if the page's largest visual element (a hero image, for example) depends on a dynamic zone that is streamed late in the sequence.

PPR delivers the best overall results: an FCP equivalent to SSG (the static shell displays immediately) and an optimal LCP when the page's primary element is part of the static shell.

INP and interactivity

Interaction to Next Paint measures page responsiveness to user interactions. This metric is less directly influenced by rendering strategy than by the volume of client-side JavaScript. However, strategies that minimize JavaScript sent to the browser (SSG with pure Server Components, for example) naturally produce better INP scores.

SSR with heavy client-side hydration can degrade INP when many interactive components must be hydrated simultaneously. The judicious use of Server Components versus Client Components is more determinative for INP than the choice between SSG, SSR, or ISR.

Decision framework: choosing the right strategy

Selection criteria

Rendering strategy selection should be guided by four primary criteria:

Content update frequency. If content changes rarely (less than once per day), SSG is sufficient. If updates are frequent but predictable, ISR is appropriate. If content is generated in real time or personalized, SSR is required.

Personalization requirements. If the page is identical for all users, SSG or ISR are suitable. If the page must display user-specific data (cart, recommendations, dashboard), SSR or PPR are necessary.

Page volume. For a site with a few hundred pages, SSG builds remain fast. Beyond several thousand pages, pure SSG becomes constraining and ISR becomes the practical choice.

Infrastructure budget. SSG is the least expensive to operate (static hosting). SSR is the most expensive (server sized for traffic). ISR sits between the two.

Decision matrix by page type

Marketing and corporate pages (home, about, services) benefit from SSG or PPR if they contain a personalized element. Blog articles and documentation pages are ideal candidates for pure SSG. E-commerce product pages are natural fits for ISR with on-demand revalidation. User dashboards and account pages require SSR. Hybrid pages, such as an e-commerce homepage with permanent content and a personalized cart, are the ideal territory for PPR.

Implementation patterns in Next.js 15

SSG with generateStaticParams

SSG implementation in the App Router relies on exporting the generateStaticParams function for dynamic routes. Here is a complete pattern for a multilingual blog:

// app/[locale]/blog/[slug]/page.tsx
import { getPostSlugs, getPost } from "@/lib/blog";
import { locales } from "@/lib/i18n";
 
export async function generateStaticParams() {
  const params = [];
  for (const locale of locales) {
    const slugs = await getPostSlugs(locale);
    for (const slug of slugs) {
      params.push({ locale, slug });
    }
  }
  return params;
}
 
export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string; slug: string }>;
}) {
  const { locale, slug } = await params;
  const post = await getPost(locale, slug);
  return {
    title: post.title,
    description: post.description,
  };
}
 
export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ locale: string; slug: string }>;
}) {
  const { locale, slug } = await params;
  const post = await getPost(locale, slug);
 
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

ISR with configurable revalidation

Implementing ISR requires only adding a revalidation directive at the segment level or on individual fetch calls:

// app/products/[id]/page.tsx
 
// Revalidation every hour
export const revalidate = 3600;
 
export async function generateStaticParams() {
  const products = await getTopProducts(100);
  return products.map((p) => ({ id: p.id }));
}
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
 
  // This fetch inherits the segment revalidation
  const product = await getProduct(id);
 
  // This fetch has its own revalidation policy
  const reviews = await fetch(
    `https://api.example.com/reviews/${id}`,
    { next: { revalidate: 300, tags: [`reviews-${id}`] } }
  ).then((r) => r.json());
 
  return (
    <main>
      <ProductDetails product={product} />
      <ReviewsList reviews={reviews} />
    </main>
  );
}

SSR with streaming and Suspense

The SSR streaming pattern relies on Suspense boundaries to define split points in the rendering stream:

// app/dashboard/page.tsx
import { Suspense } from "react";
 
export const dynamic = "force-dynamic";
 
export default async function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1>
 
      <Suspense fallback={<StatsSkeleton />}>
        <DashboardStats />
      </Suspense>
 
      <div className="grid grid-cols-2 gap-6">
        <Suspense fallback={<ChartSkeleton />}>
          <RevenueChart />
        </Suspense>
        <Suspense fallback={<ChartSkeleton />}>
          <TrafficChart />
        </Suspense>
      </div>
 
      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />
      </Suspense>
    </main>
  );
}

Each component wrapped by <Suspense> is rendered and streamed independently. If RevenueChart is ready before TrafficChart, it is sent to the client first. The user sees page elements materialize progressively, without waiting for all data sources to respond.

Experimental PPR configuration

To enable Partial Prerendering, the project configuration must include the experimental flag:

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    ppr: true,
  },
};
 
export default nextConfig;

Once enabled, Next.js automatically analyzes each route to determine which parts can be statically pre-rendered and which parts require dynamic rendering. Suspense boundaries serve as the delimiter between the two modes.

Migration strategies between rendering modes

SSG to ISR: the most natural transition

Migrating from SSG to ISR is the simplest transition to execute. It requires no structural changes to component code. Adding a revalidate directive to the route segment or fetch calls is sufficient. Pages continue to be generated at build time, but they gain the ability to regenerate automatically.

This transition is recommended when content volume grows to the point where full builds become prohibitively slow, or when editorial teams need their modifications published without waiting for a deployment.

SSR to PPR: capturing the best of both worlds

If your application uses SSR for pages that contain both permanent content and personalized zones, PPR allows reclaiming SSG performance for the static portions. The migration involves identifying components that do not depend on dynamic data and removing them from Suspense boundaries, then wrapping the dynamic components in <Suspense> with explicit fallbacks.

The performance gain is immediate: the static shell is served from the CDN, and only the dynamic zones require server processing.

ISR to pure SSG: simplifying when possible

For certain pages whose content has stabilized (an "about" page that no longer changes, an older blog article), removing the revalidation directive and reverting to pure SSG can be worthwhile. This reduces cache system complexity and guarantees perfectly predictable behavior.

Adopting an incremental approach

The strength of Next.js lies in its ability to mix rendering strategies within a single application. It is neither necessary nor advisable to select a single strategy for the entire project. The recommended approach is to evaluate each route individually, applying the strategy best suited to its freshness, personalization, and performance constraints.

Start with high-traffic, high-SEO-impact pages. Optimize them with SSG or PPR. Move next to catalog pages with ISR. Reserve pure SSR for authenticated pages and administrative interfaces. This incremental approach minimizes risk and allows measuring gains at every step.

The rendering strategy landscape in Next.js has evolved profoundly with the App Router and React Server Components. The choice is no longer a binary between static and dynamic. Developers now have access to a full spectrum of options, from pure SSG to SSR with streaming, through ISR and PPR. Mastering these strategies and knowing when to apply each one has become a foundational competency for any front-end engineer aiming for excellence in both performance and search engine optimization.

Related posts