ShieldThemes Web Development
+1 (415) 555-0142 Get a quote →
← Journal/Engineering

Next.js rendering choices: static, server or cached

Next.js offers several ways to render a page, and the default is not always right. How we decide per route between static, server and cached rendering.

Maya Okafor
Maya Okafor
Head of Engineering · Aug 26, 2026 · 5 min read
Next.js rendering choices: static, server or cached

Next.js gives developers an unusual amount of control over where and when each page is rendered. A single application can serve some routes as prebuilt static files, render others on every request, cache others for a set period and fetch some data in the browser. That flexibility is one of the framework's biggest strengths, and also one of the most common sources of performance and cost problems we are asked to fix. Teams tend to pick one approach for the whole application, usually by accident, when the right answer is to decide route by route.

The four options, briefly

Stripped of terminology, there are four ways a page can get its content:

  • Static at build time. The page is rendered once when the application is built and served as a file. Fastest and cheapest, but content only changes on the next build.
  • Cached with revalidation. The page is rendered once, cached, and re-rendered in the background after a set interval or when explicitly invalidated. Nearly as fast as static, with content that stays reasonably fresh.
  • Dynamic on each request. The page is rendered on the server for every visitor. Always fresh and able to personalize, but every request costs server time.
  • Client-side fetching. The server sends a shell, and the browser fetches data after load. Useful for highly interactive or private data, but slower to show meaningful content.

Recent versions of the framework default to caching aggressively and let individual data fetches opt out. That default is sensible, but it means a single uncached call, such as reading cookies or fetching without cache options, can silently make a whole route dynamic.

A decision per route

For each route, we ask three questions: how often does the content change, does it differ per visitor, and how much traffic does it get? Those answers map fairly directly to a strategy.

  1. Marketing pages, documentation, blog posts. Change rarely, same for everyone, often high traffic. Cached with on-demand revalidation triggered by the CMS when an editor publishes.
  2. Product and category pages. Change several times a day, mostly the same for everyone, very high traffic. Cached with short time-based revalidation plus on-demand invalidation for price and stock changes, with any personalized fragments loaded separately.
  3. Search results and filtered listings. Near-infinite combinations of parameters. Dynamic rendering with a data-level cache for the underlying queries.
  4. Account pages, dashboards, checkout. Private and personalized. Dynamic on each request, never cached at the page level.
// app/products/[slug]/page.tsx
export const revalidate = 300;

export default async function ProductPage({ params }) {
  const product = await getProduct(params.slug, { tags: [`product:${params.slug}`] });
  return <ProductView product={product} />;
}

When the product changes, the back end calls the revalidation endpoint for that product's tag, and the next request gets a fresh render. Visitors never wait for the rebuild.

The question is never static or dynamic for the whole site. It is which parts of each page genuinely need to be computed for this visitor, right now.

Where teams usually go wrong

The same handful of mistakes account for most of the problems we see in audits:

  • Accidentally dynamic routes. A header component reads a cookie to show a cart count, and every page that includes the header becomes dynamic. Moving that one component to client-side fetching or a separate streamed boundary can turn a whole site back to cached.
  • Personalizing the shell. Rendering the visitor's name or region into the main HTML prevents caching. Keep the shell generic and personalize small islands.
  • Over-fetching in client components. Fetching large payloads in the browser that could have been rendered on the server, hurting Largest Contentful Paint on mobile.
  • No invalidation plan. Long cache times with no on-demand revalidation, so editors publish and nothing changes, and someone eventually sets every route to dynamic in frustration.
  • Build-time generation of huge catalogs. Prebuilding 80,000 product pages at deploy time makes builds take an hour. Generate the most popular pages at build and the rest on first request.

Cost and hosting implications

Rendering choices show up directly on the hosting bill. Dynamic rendering on serverless platforms is billed per invocation and duration, and a busy site that renders every page on every request can cost many times more than the same site served mostly from cache. For one content-heavy client, fixing two accidentally dynamic layout components moved about 90 percent of page views to cached responses, cut their monthly hosting bill by roughly 60 percent and improved median time to first byte from 480 to 70 milliseconds.

Self-hosting Next.js is entirely viable, but the cache needs somewhere shared to live when you run more than one instance, and on-demand revalidation must reach every instance. That is solvable with a shared cache handler, and it is worth planning for before launch rather than discovering it in production.

How we build it

On every Next.js build, we produce a short rendering map listing each route, its strategy, its revalidation triggers and the reasoning. It is reviewed alongside the design and kept up to date as routes are added. For existing applications, a rendering audit is often the quickest performance win available, and it pairs naturally with broader Core Web Vitals optimization and, where the content comes from WordPress, headless WordPress revalidation hooks.

Get a rendering plan for your app

If your Next.js site is slower or more expensive than it should be, we can usually tell you why within a day. Share the URL and a little context, and we will reply with a fixed-price proposal. Request a quote.

Maya Okafor
WRITTEN BY
Maya Okafor
Maya leads engineering at ShieldThemes. She has shipped more than 120 WordPress and Laravel platforms and writes about architecture that survives its second year.
All articles by Maya Okafor →
Want this on your project?
Get a fixed-price quote from a senior lead within 24 hours.
Request a quote →

Keep reading

How we shipped a support agent that resolves 62% of tickets
AI · 5 min
How we shipped a support agent that resolves 62% of tickets
What to learn in the two weeks before a website redesign
Design · 5 min
What to learn in the two weeks before a website redesign
Migrating to Shopify Plus without losing a single ranking
Shopify · 5 min
Migrating to Shopify Plus without losing a single ranking