Next.js rendering strategies

Next.js offers several strategies for generating a page's HTML. Each strategy determines when the HTML is produced: at build time, on every request, or incrementally. The choice of strategy directly impacts load time, content freshness, and hosting costs.

Static Site Generation (SSG)

Static generation produces the HTML at build time (next build). The resulting HTML files are deployed to a CDN and served directly to the browser without any server-side processing on each request.

How it works

  1. At build time, Next.js runs the data-loading functions
  2. The data is injected into the React components
  3. The HTML is generated and stored as static files
  4. On each request, the CDN serves the pre-generated HTML file

Implementation with the App Router

In the App Router, a page is static by default if it doesn't depend on any dynamic data. For a page that consumes the WordPress API:

// app/blog/page.tsx — Static page generated at build time
export default async function BlogPage() {
  const res = await fetch(
    `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-json/wp/v2/posts?per_page=20&_embed`,
    { cache: 'force-cache' } // Explicit static behavior
  );
  const posts = await res.json();

  return (
    <main>
      <h1>Articles</h1>
      {posts.map((post: any) => (
        <article key={post.id}>
          <h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
        </article>
      ))}
    </main>
  );
}

For dynamic routes, generateStaticParams tells Next.js which parameters to pre-generate:

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const res = await fetch(
    `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-json/wp/v2/posts?per_page=100`
  );
  const posts = await res.json();
  return posts.map((post: any) => ({ slug: post.slug }));
}

When to use SSG

  • Pages whose content rarely changes (legal notices, About, FAQ)
  • Blog posts published without frequent edits
  • Category and archive pages

< 50ms

TTFB (Time to First Byte)

The HTML file is served directly from the CDN, with no server processing

0

Server cost per request

No server execution — static files are served by the CDN

Build

Content freshness

Content is up to date as of the latest build only

Server-Side Rendering (SSR)

Server-side rendering generates HTML on every request. The Next.js server queries the WordPress API, runs the React components, and returns the HTML to the browser.

Implementation

To force SSR, use the cache: 'no-store' option on fetch calls, or export dynamic = 'force-dynamic':

// app/search/page.tsx — Page rendered on every request
export const dynamic = 'force-dynamic';

export default async function SearchPage({
  searchParams,
}: {
  searchParams: { q?: string };
}) {
  const query = searchParams.q ?? '';
  const res = await fetch(
    `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-json/wp/v2/posts?search=${query}&_embed`,
    { cache: 'no-store' }
  );
  const posts = await res.json();

  return (
    <main>
      <h1>Results for "{query}"</h1>
      {posts.map((post: any) => (
        <article key={post.id}>
          <h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
        </article>
      ))}
    </main>
  );
}

When to use SSR

  • Search pages whose content depends on query parameters
  • Pages personalized for the logged-in user
  • Pages displaying real-time data (stock, prices, availability)

Incremental Static Regeneration (ISR)

ISR combines static performance with dynamic freshness. Pages are generated at build time like in SSG, then regenerated in the background after a configured interval.

Detailed mechanics

Initial build

At build time, Next.js generates static pages and deploys them to the CDN. These pages are served to visitors.

First request after expiration

When a visitor accesses a page whose revalidation interval has expired, they receive the cached version (stale). In the background, Next.js triggers regeneration.

Background regeneration

Next.js queries the WordPress API, generates the new HTML, and replaces the cached version. This process is invisible to the visitor.

Subsequent requests

Following visitors receive the regenerated version. The cycle restarts at the next interval expiration.

This mechanism is called stale-while-revalidate: the visitor always receives an immediate response (the cached version), and the update happens in the background.

Implementation

// app/blog/[slug]/page.tsx — Static page with ISR revalidation
export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const res = await fetch(
    `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-json/wp/v2/posts?slug=${params.slug}&_embed`,
    { next: { revalidate: 3600 } } // Regeneration every hour
  );
  const posts = await res.json();
  const post = posts[0];

  return (
    <article>
      <h1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
      <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
    </article>
  );
}

On-demand revalidation

Rather than waiting for the interval to expire, a WordPress webhook can trigger regeneration immediately:

// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.nextUrl.searchParams.get('secret');
  if (secret !== process.env.REVALIDATION_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }

  const body = await request.json();
  const slug = body.post_name;

  revalidatePath(`/blog/${slug}`);
  revalidatePath('/blog'); // Also regenerate the listing page

  return Response.json({ revalidated: true, slug });
}

On the WordPress side, a plugin or publish_post hook sends a POST request to this route on each publication.

Client-side data loading

For data that changes frequently and doesn't need SEO indexing (comments, view count, login status), client-side loading is preferable.

'use client';
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then((res) => res.json());

export function CommentSection({ postId }: { postId: number }) {
  const { data, error, isLoading } = useSWR(
    `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-json/wp/v2/comments?post=${postId}`,
    fetcher,
    { refreshInterval: 30000 } // Refresh every 30 seconds
  );

  if (isLoading) return <p>Loading comments...</p>;
  if (error) return <p>Loading error</p>;

  return (
    <section>
      <h3>{data.length} comment(s)</h3>
      {data.map((comment: any) => (
        <div key={comment.id}>
          <strong>{comment.author_name}</strong>
          <div dangerouslySetInnerHTML={{ __html: comment.content.rendered }} />
        </div>
      ))}
    </section>
  );
}

Decision matrix

Hybrid approach: combining strategies

One of the advantages of Next.js is the ability to combine multiple strategies in a single project. Each route can use its own strategy.

app/
  page.tsx              → SSG (static home page)
  blog/
    page.tsx            → ISR revalidate: 3600 (post listing)
    [slug]/
      page.tsx          → ISR revalidate: 3600 (post detail)
  search/
    page.tsx            → SSR (dynamic search results)
  account/
    page.tsx            → SSR (personalized content)

Practical rule for strategy selection

For a standard headless WordPress site, ISR with on-demand revalidation is the recommended default choice. It covers most use cases (posts, pages, categories) by combining static performance with content freshness. Reserve SSR for pages that depend on query parameters or the logged-in user, and pure SSG for pages whose content only changes between deployments.

Performance impact

50ms

TTFB with SSG/ISR

Pre-generated pages are served from the CDN closest to the visitor

200-500ms

TTFB with SSR

The server generates the HTML on every request — time depends on WordPress API latency

1-3s

TTFB with CSR

The browser loads the JavaScript, then makes API calls before displaying content

Key takeaways

Key points

  1. SSG generates HTML at build time. Maximum performance, but content is only updated after a new build.

  2. SSR generates HTML on every request. Always up-to-date content, but higher response time and server cost proportional to traffic.

  3. ISR combines SSG and periodic revalidation. The recommended default strategy for a headless WordPress site.

  4. The hybrid approach lets you use the strategy best suited to each route within a single project.

  5. On-demand revalidation via webhook is the most efficient method to sync WordPress content with the Next.js frontend.