· By John Kavanagh

Optimising Next.js Performance with Incremental Static Regeneration (ISR)

Abstract image used to represent Optimising Next.js Performance with ISR
Image by Heidi Fin.

Incremental Static Regeneration is one of the most practical performance features Next.js offers. It helps us avoid a false choice between fully static pages and fully dynamic rendering by letting us regenerate static content after deployment.

That middle ground is powerful. Many pages aren't truly real time, but they aren't truly immutable either. Articles, product pages, documentation, and category listings often sit in that space, which is exactly where ISR shines.


What ISR Actually Gives Us

With ISR, a page is generated statically and can be regenerated after deployment. In the Pages Router, a timebased policy makes it eligible for background regeneration after an interval; a later request triggers the work. Ondemand revalidation provides a separate explicit trigger.

For large sites, that matters a great deal. A full rebuild every time one item changes is often wasteful. ISR lets us regenerate only what needs updating.


A Practical ISR Example

In the Pages Router, ISR is usually introduced through getStaticProps:

import type { GetStaticPaths, GetStaticProps } from 'next';

type Article = {
  slug: string;
  title: string;
  body: string;
};

type Props = {
  article: Article;
};

export const getStaticPaths: GetStaticPaths = async () => {
  return {
    paths: [],
    fallback: 'blocking',
  };
};

export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
  const slug = params?.slug;
  if (typeof slug !== 'string') {
    return { notFound: true };
  }

  const response = await fetch(
    `https://example.com/api/articles/${encodeURIComponent(slug)}`,
  );
  if (response.status === 404) {
    return { notFound: true, revalidate: 300 };
  }
  if (!response.ok) {
    throw new Error(`Article request failed: HTTP ${response.status}`);
  }

  const article: unknown = await response.json();
  if (
    !article ||
    typeof article !== 'object' ||
    !('slug' in article) || typeof article.slug !== 'string' ||
    !('title' in article) || typeof article.title !== 'string' ||
    !('body' in article) || typeof article.body !== 'string'
  ) {
    throw new Error('Invalid article response');
  }

  return {
    props: { article: article as Article },
    revalidate: 300,
  };
};

Here, revalidate: 300 makes the cached page eligible for regeneration after 300 seconds. The next request can still receive the old page whilst Next.js regenerates it in the background. That is not a fiveminute maximum age: a quiet page may wait longer for a request, and regeneration takes time. The example returns notFound for a missing article and throws on other failed or malformed responses instead of caching them as article data.


When ISR is a Strong Fit

ISR tends to work especially well when:

  • the content changes, but not every second
  • the number of pages is large enough that full rebuilds hurt
  • the page is mostly public and cachefriendly
  • we want predictable performance across repeat traffic

This is why ISR appears so often in contentheavy and commerceheavy projects. It gives us scalability without forcing every request through the server path.


When ISR is the Wrong Tool

ISR isn't ideal for everything. Highly personalised pages, realtime dashboards, and securitysensitive data should usually remain dynamic. If two users must see meaningfully different output, static regeneration is often the wrong abstraction.

That's an important distinction. Performance work isn't about reaching for the fastest feature first, it is about matching the rendering strategy to the behaviour of the page.


On‑Demand Revalidation

Timebased regeneration is helpful, but sometimes we know exactly when content changed. In that case, ondemand revalidation is even better because it reduces unnecessary regeneration and keeps freshness aligned with actual edits.

This pattern is especially useful for CMSdriven websites. A publish webhook can trigger revalidation immediately, which creates a smoother operational flow for editors and developers alike.


Operational Benefits Beyond Speed

ISR isn't only about raw load time. It also improves maintainability in a few subtle ways:

  • fewer full rebuilds reduce deployment pressure
  • large content collections become easier to manage
  • performance stays predictable as the site grows
  • frontend code can remain largely staticfirst

Those benefits are often overlooked, but they matter in longrunning projects where scalability isn't just technical, but also organisational.


A Couple of ISR Pitfalls

Timebased ISR does not promise that every visitor sees an update within the interval. The next request triggers regeneration, and a failed attempt leaves the last successful page available for a later retry. Choose this policy only when that behaviour meets the product's freshness requirements; monitor regeneration failures and use an explicit invalidation workflow where necessary.

A related assumption is that ISR replaces all other caching concerns. It doesn't. Browser caching, CDN behaviour, API caching, and image optimisation still matter around it.

For the frameworkspecific details behind these patterns, the official documentation below is the best place to go deeper:


Wrapping Up

ISR is effective because it matches the way many real websites behave. Content changes occasionally, visitors expect speed immediately, and rebuilds should not grow linearly with the size of the site. When we use ISR on the right pages, we get a performance strategy that remains fast, maintainable, and scalable long after the first release.

Key Takeaways

  • ISR combines static delivery with controlled content freshness.
  • It works best for large collections of mostly public content.
  • It should be chosen deliberately, not as a blanket default.

Used thoughtfully, ISR gives us one of the clearest paths to a fast Next.js site that can still evolve continuously.


Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.