attiliov
attiliov

Reputation: 61

Setting Cache-Control Header in NextJS App Router

I've been banging my head over this. I'm using NextJS 13's new App Router. I've tried setting the response Cache-Control header via middleware.ts but it doesn't change. I also tried in next.config.js and this also hasn't worked. I have no problems setting custom headers, but NextJS always automatically sets Cache-Control to 'no-store, must-revalidate'. What am I missing? How do I change this header?

Upvotes: 5

Views: 14916

Answers (5)

fadomire
fadomire

Reputation: 1975

In Next v14.2.10

You can override the cache header for dynamicly rendered page in next.config.ts

even based on a cookie

const nextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        has: [
          {
            type: 'cookie',
            key: 'shouldCache',
            value: 'true',
          },
        ],
        headers: [
          {
            key: 'Cache-Control',
            value: 'max-age=60',
          },
        ],
      },
    ];
  },
};

is it a good idea ? not sure seeing all the efforts nextjs makes to determine the correct Cache-Control headers for you based on your usage

source: https://nextjs.org/docs/app/api-reference/next-config-js/headers

Change in nextjs that allowed this: https://github.com/vercel/next.js/pull/69802

Upvotes: 2

Atliac
Atliac

Reputation: 106

In a nutshell: To create dynamic Next.js pages that load quickly (by taking advantage of caching) and still show up-to-date content, we can combine static generation with on-demand regeneration. In this setup, Next.js generates a static HTML version of our dynamic page but provides a mechanism to regenerate that HTML when the underlying data changes.

Here's a breakdown:

  1. Enable Static Generation: Inside our dynamic page component (e.g., pages/my-dynamic-page.js), we'll add this line:

    export const dynamic = 'force-static'; 
    

    Important: This line behaves differently in development and production. During development (next dev), it acts as if the page is still fully dynamic. However, in our production build (next build), this line tells Next.js to generate a static HTML file for this route.

  2. Manage Revalidation Strategy: Now, we'll manage how often Next.js regenerates (or revalidates) the page:

    • Default Behavior (Usually Best): By default, Next.js won't regenerate a statically generated page automatically. This is usually the desired behavior and means we don't need to add any extra code! It's as if we have export const revalidate = false; in our component.

    • Time-Based Revalidation (Use Sparingly): We can set export const revalidate = n (where 'n' is the number of seconds) to make Next.js regenerate the page every 'n' seconds. This effectively sets the s-maxage property of the Cache-Control header to 'n' seconds. However, this approach can be less efficient because the page might be regenerated even if the data hasn't changed.

  3. On-Demand Revalidation (Most Efficient): For optimal performance, we'll regenerate the static page only when our data is updated. We'll use the revalidatePath() function to achieve this:

    // Example: After updating data somewhere in our application
    import { revalidatePath } from 'next/cache';
    
    async function updateData() {
      // ... Logic to update the data source
    
      // Tell Next.js to regenerate the page:
      await revalidatePath('/my-dynamic-page');
    }
    

By strategically employing static generation with on-demand revalidation, we strike an optimal balance between delivering fast initial page loads (due to caching) and ensuring our users see fresh, up-to-date content.

Upvotes: 1

Daniel De León
Daniel De León

Reputation: 13679

This questions is tricky... because NextJs use many resources to be a super SSR, so you must take a serious time to understand how it's really work to hacked as you wish, but may you do not need to do that because many times NextJs has already a way to deal with your case.

But I will give you my case scenario:

Cache a page.tsx forever until a external signal arribes to rendered again in the next call.

page.tsx

export const dynamic = 'force-static'
export const revalidate = false;

and to send the signal call revalidatePath('/path/to/revalidate');

FYI:

Upvotes: 2

Tomáš Hodek
Tomáš Hodek

Reputation: 113

I'm not sure if it solves your issue, but I might help others, I've spent almost a few days trying to find that piece of docs.

As mentioned, you can't rewrite the Cache-control headers, NextJS will always overwrite them (unless you create your middleware on the server level).

But, for static pages, like /posts you can add page segment config revalidate. It takes a number of seconds and that will be the number in the Cache-control header then.

If you're dealing with a dynamic page, as I did, post/[id], NextJS propose to use fetch or other cacheable fetch and caches the data on the server, but won't change the headers even if the continent is static.

And there you have the option to use segment config again, dynamic, which you can use to force static pages to be dynamic, or a dynamic page to behave as static.

So if you add this code to your dynamic page:

export const dynamic = 'force-static';
export const revalidate = 60;

Your headers will look like this: Cache-control: s-maxage=60, stale-while-revalidate

That way, you're able to play around with Cache-control headers forcing dynamic pages to be static, but I'd suggest always using the revalidate option so your pages get updated.

I'm still looking into the revalidation possibilities by converting the page to static, so I'll be glad if someone can add some more hints/caveats to this approach.

Upvotes: 6

Matthew Rideout
Matthew Rideout

Reputation: 8546

NextJS with App Router handles the cache-control header automatically. If you attempt to set it in next.config.js it will be overwritten in production by the build-in caching mechanism

In development mode npm run dev the cache is no-store, must-revalidate.

In production, NextJS 13 uses the SWR strategy (s-maxage=31536000, stale-while-revalidate). There are only cache invalidation mechanisms for data fetching and dynamically generated page content.

The initially statically rendered page currently has no mechanism to force a fresh fetch for the end-user at initial load outside of Vercel's hosting ecosystem, which handles this.

If you do not need server-side rendering, I would suggest not using Next, and using Vite. If you do need server-side rendering and don't want to host on Vercel, I would suggest a less black-box framework opinionated for Vercel's ecosystem (like Remix).

Upvotes: 7

Related Questions