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:
Let's break it down. 🔍
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:
fetch
requests default to no-store
(previously force-cache
).This shift gives developers more control over caching behavior, reducing unexpected results. However, it also requires explicit configuration to optimize performance.
Since caching is no longer automatic, developers must opt-in using force-cache
, next.revalidate
, or HTTP headers.
By default, fetch requests always retrieve fresh data unless explicitly cached.
Alternatively, you can cache responses for a set period using next.revalidate
:
app/api/route.ts
)In earlier versions, GET API routes automatically cached responses. Now, to enable caching, you need to use explicit headers.
If a page fetches data dynamically and you want to cache responses, use next.revalidate
.
Since caching is opt-in, applications that rely on frequent API calls may experience increased request volume. Here’s how this change affects performance:
next.revalidate
for periodic caching instead of full persistence.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? 🚀
1async function getProducts() {2const res = await fetch("https://api.example.com/products", {3cache: "force-cache", // Opt into caching4});5return res.json();6}7
1import { NextResponse } from "next/server";23export async function GET() {4const data = await fetch("https://api.example.com/data", {5cache: "force-cache", // Opt into caching6});78return NextResponse.json(await data.json(), {9headers: {10"Cache-Control": "public, max-age=300", // Cache for 5 minutes11},12});13}14
1export async function getServerSideProps() {2const data = await fetch("https://api.example.com/data", {3next: { revalidate: 120 }, // Cache for 2 minutes4});56return {7props: { data: await data.json() },8};9}10
1async function getProducts() {2const res = await fetch("https://api.example.com/products", {3next: { revalidate: 60 }, // Cache response but refresh every 60 seconds4});5return res.json();6}7