Understanding CDN Cache in Next.js
Next.js has emerged as a popular framework for building fast, user-friendly web applications. The framework claims to automatically and proactively handle caching within applications. However, is this really the case?
Recently, I encountered a challenge while developing my book review website, refanswer.com. To reduce the load on the server when accessing the Firestore database, I needed to implement caching between requests.
Next.js indeed implements a caching mechanism for server-side request data. This can be achieved using the optimized fetch method to access API URLs for data retrieval or by using the cache method provided by React to wrap data-fetching methods from third-party libraries.
You can find detailed instructions for the former here, and for the latter here.
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
import { cache } from 'react';
import db from '@/lib/db';
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id });
return item;
});
These methods address the issue of repeatedly requesting the same data within a single request. For example, the generateMetadata
method and the server component might both use the same ID to fetch data from the database or API.
However, the lifecycle of these caching methods is limited to individual requests. In other words, the cached data is only available within the same request and cannot be shared across different requests. While this is useful and significantly reduces the number of requests between the server and the data source, I had a higher requirement: I wanted to share data across multiple user requests to the server.
This concept is similar to that of a CDN (Content Delivery Network). However, the Next.js documentation does not emphasize this aspect. Given Vercel's reputation for providing edge network services, Vercel seems to be the default solution to this problem. Yet, without further configuration, Vercel will not proactively resolve these issues.
To achieve this functionality, you need to configure the Cache-Control
headers in next.config.mjs, vercel.json, or middleware.ts.
Key Cache-Control Directives
max-age:
Specifies the maximum time a resource is considered fresh in both browser and shared caches. It has a lower priority compared to s-maxage
and is commonly used to control the overall caching duration for a resource across all caching layers.
s-maxage:
Specifies the maximum time a resource is considered fresh in shared caches only. It takes precedence over max-age
for these caches and is typically used to extend or reduce the caching duration specifically for proxy servers to optimize content delivery.
In summary, max-age
has a lower priority and is typically used to set cache durations for both browser and shared caches, while s-maxage
has a higher priority and is specifically used to set cache durations for shared caches in the network.
Configuring Cache-Control
To configure cache-control, set the values of max-age
and s-maxage
according to your requirements. A typical configuration in middleware might look like this:
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Set Cache-Control headers
response.headers.set('Cache-Control', 'public, max-age=3600, s-maxage=7200, stale-while-revalidate=59');
return response;
}
The stale-while-revalidate
directive specifies a time window during which the cache can continue to use a resource even if it has expired, while revalidating and updating the resource in the background. I won’t go into too much detail about it here.
In addition to setting Cache-Control
headers in middleware, you can also set them in next.config.mjs and vercel.json (only valid for the Vercel network). For example:
In next.config.mjs:
headers: async () => {
return [
{
source: "/(.*)",
headers: [
{
key: "Cache-Control",
value: "public, max-age=60, s-maxage=3600, stale-while-revalidate=60",
},
],
},
];
},
In vercel.json:
{
"headers": [
{
"source": "/about.js",
"headers": [
{
"key": "Cache-Control",
"value": "s-maxage=1, stale-while-revalidate=59"
}
]
}
]
}
For more details, please visit: Vercel Caching Documentation.
About Dynamic Routing
In a dynamic route page, such as app/[slug]/page.tsx
, setting Cache-Control
headers alone is not sufficient. You must also simulate the dynamic route page as a static page. A simple way to achieve this is by using the following syntax:
export const dynamic = "force-static";
export const revalidate = 86400;
This makes the page a static page, allowing the Cache-Control
headers in the response to take effect.
Top comments (0)