skyg profile picture

skyg

Software developer


March 14, 2023

Faster Next.js sites with stale-while-revalidate

Using the stale-while-revalidate Cache-Control header in a Next.js application can be a boon for page load times, especially when combined with a CDN like Cloudflare. A cached "stale" page is served while the CDN calls your server in the background. As a result, pages that make server-side API calls or read from a database can see a considerable performance boost.

Cache-Control Header Directives

The max-age and stale-while-revalidate directives are part of the Cache-Control header and play a significant role in web performance optimization by defining caching behavior. In combination, you can use these directives to reduce server load while keeping content as fresh as possible.

  • max-age: Specifies the duration, in seconds, for which a cached resource is considered fresh. Once this duration expires, the resource is considered stale, and a cache revalidation is required before serving the content.
  • stale-while-revalidate: Defines the period, in seconds, during which a stale resource can be served while a background revalidation process is initiated.

Why stale-while-revalidate is Useful

  • Faster response times: By serving stale content, we can ensure that users receive a response instantly from the CDN rather than waiting for a cache update. As a result, you'll likely see better user experience, increased conversion rates, and higher PageSpeed scores.
  • Reduced server load: Since stale content is served without waiting for an update, the server's load is reduced as it doesn't need to process requests for updated content as often. Fewer requests can save money and increase server stability during traffic spikes.

When to Use Aggressive Caching

The stale-while-revalidate header is best suited for use cases where:

  • Content updates are not critical: If your application's content stays mostly the same and it's acceptable for users to see slightly outdated information, stale-while-revalidate can be a great fit.
  • Latency is a concern: When latency is critical, serving stale content is often better than making users wait for an update, especially if the update frequency is low.

Websites that benefit most from stale-while-revalidate

  • News and blog sites: These websites typically have content that changes frequently but does not require real-time updates. Serving stale content momentarily while updating in the background usually benefits the user experience.
  • E-commerce platforms: While product inventory and prices should be real-time, other content such as product descriptions, images, and reviews can benefit from stale-while-revalidate, providing a faster browsing experience.
  • Documentation and knowledge base sites: Such websites are often updated regularly, but most users won't be negatively affected if they receive slightly outdated content for a short time.
  • Social media platforms: User-generated content, such as posts and comments, can benefit from stale-while-revalidate. However, it's essential to balance content freshness and caching efficiency.

Implementing stale-while-revalidate for dynamic (SSR) Next.js pages

In a Next.js application, you can optimize page performance by using the stale-while-revalidate Cache-Control header within the getServerSideProps function. Here's a step-by-step guide on how to implement it:

  1. Create or open your Next.js page: If you don't already have a Next.js page, create one in the pages directory of your project. If you already have a page, open it to add the getServerSideProps function.
  2. Implement the getServerSideProps function: Add the getServerSideProps function to your page if you haven't already, and fetch the data required for your page as usual. Here's an example:
import { fetchData } from "../lib/data";

export async function getServerSideProps() {
  const data = await fetchData();

  return {
    props: {
      data,
    },
  };
}
  1. Add the Cache-Control header: Modify the getServerSideProps function to include the stale-while-revalidate directive in the Cache-Control header. To do this, use the setHeader method on the res object, available through the context parameter. For example, if you want to serve stale content for up to 5 minutes while revalidating, you can use the following header:
export async function getServerSideProps(context) {
  const data = await fetchData();

  // Set the Cache-Control header
  context.res.setHeader(
    "Cache-Control",
    "public, max-age=60, stale-while-revalidate=300"
  );

  return {
    props: {
      data,
    },
  };
}

Implementing stale-while-revalidate for Next.js static (SSG) pages

To set the stale-while-revalidate directive of the Cache-Control header for static or dynamic Next.js pages, you can also use the headers key in the next.config.js file:

module.exports = {
  async headers() {
    return [
      {
        source: '/your-page',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, stale-while-revalidate=60',
          },
        ],
      },
    ];
  },
};

Test your Cache-Control header implementation

To verify that the stale-while-revalidate header is working correctly, use curl or similar to inspect the headers returned by your Next.js page:

curl -I http://localhost:3000/your-page

You should see the Cache-Control header you set earlier, indicating that the stale-while-revalidate directive was applied.

By using the stale-while-revalidate Cache-Control header, you can improve your web application's performance and user experience. Remember that this approach is best when your content updates are not critical and reduced latency is a priority.


-- skyg