Caching Semantics in Next.js 15: What’s Changed? (Chapter 2)

πŸš€ Introduction

Next.js 15 introduces a significant change in how caching works, moving towards an explicit opt-in model. Fetch requests, GET Route Handlers, and client navigations are no longer cached by default, ensuring more predictable and dynamic data fetching.

In this article, we’ll dive deep into:

  • What changed in caching behavior
  • How to explicitly enable caching
  • When and why to use different caching strategies
  • Performance implications of this update

Let's break it down. πŸ”


⚑ What Changed in Next.js 15 Caching?

Previously, Next.js automatically cached fetch requests, API routes, and client-side navigations unless explicitly set to no-store . This could lead to stale data issues, where users received outdated responses unexpectedly.

Now, in Next.js 15:

  1. fetch requests default to no-store (previously force-cache).
  2. GET Route Handlers are no longer cached automatically.
  3. Client-side navigations always fetch fresh data unless caching is manually configured.

This shift gives developers more control over caching behavior, reducing unexpected results. However, it also requires explicit configuration to optimize performance.


πŸ”§ How to Enable Caching Explicitly

Since caching is no longer automatic, developers must opt-in using force-cache, next.revalidate, or HTTP headers.

1. Enabling Fetch Caching

By default, fetch requests always retrieve fresh data unless explicitly cached.

βœ… Example: Caching Fetch Requests

1
async function getProducts() {
2
const res = await fetch("https://api.example.com/products", {
3
cache: "force-cache", // Opt into caching
4
});
5
return res.json();
6
}
7

Alternatively, you can cache responses for a set period using next.revalidate:

1
async function getProducts() {
2
const res = await fetch("https://api.example.com/products", {
3
next: { revalidate: 60 }, // Cache response but refresh every 60 seconds
4
});
5
return res.json();
6
}
7

2. Caching GET Route Handlers (app/api/route.ts)

In earlier versions, GET API routes automatically cached responses. Now, to enable caching, you need to use explicit headers.

βœ… Example: Caching a GET API Route

1
import { NextResponse } from "next/server";
2
3
export async function GET() {
4
const data = await fetch("https://api.example.com/data", {
5
cache: "force-cache", // Opt into caching
6
});
7
8
return NextResponse.json(await data.json(), {
9
headers: {
10
"Cache-Control": "public, max-age=300", // Cache for 5 minutes
11
},
12
});
13
}
14

3. Enabling Client-Side Page Caching

If a page fetches data dynamically and you want to cache responses, use next.revalidate.

βœ… Example: Opting Into Page Caching

1
export async function getServerSideProps() {
2
const data = await fetch("https://api.example.com/data", {
3
next: { revalidate: 120 }, // Cache for 2 minutes
4
});
5
6
return {
7
props: { data: await data.json() },
8
};
9
}
10

πŸ“‰ Performance Implications

Since caching is opt-in, applications that rely on frequent API calls may experience increased request volume. Here’s how this change affects performance:

πŸ”΄ Potential Drawbacks

  • Higher API request volume since fresh data is always fetched unless explicitly cached.
  • Slower client-side transitions due to missing automatic caching.
  • Unexpected behavior in legacy applications that relied on implicit caching.

βœ… When This Change is Beneficial

  • Ensures data consistency by always fetching fresh content.
  • Prevents stale data issues, especially in dynamic applications.
  • Gives developers control over caching, reducing unexpected side effects.

πŸš€ Best Practices

  • Use next.revalidate for periodic caching instead of full persistence.
  • Cache only non-critical or rarely changing data.
  • Apply Cache-Control headers in API responses where needed.

πŸ“Œ Conclusion

Next.js 15’s explicit caching model improves data consistency but requires developers to opt into caching manually. While this may increase API load, it ensures predictable behavior across dynamic applications.

By using strategies like force-cache, next.revalidate, and explicit cache headers, you can optimize performance while maintaining fresh data.

For further insights, check out:

Would you like me to add a performance benchmark comparison for different caching strategies? πŸš€