Course
Cache
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Default Caching
By default, GET requests using the
Response
object (including NextResponse
) are cached in Next.js. Let’s delve into how this works with a practical example.
Create
app/api/time/route.ts
with the following code:export async function GET() { console.log('GET /api/time'); return Response.json({ data: new Date().toLocaleTimeString() });}
Note: In development mode, the response is not cached, so the time changes with each refresh.
In production, run
npm run build && npm run start
:
You will observe that regardless of how many times you refresh, the time remains constant. This indicates that the response is being cached.
Why Is This Happening?
Next.js implements this by pre-rendering the content as static during the build process.
npm run build && npm run startnext-template@0.1.0 build
next build▲ Next.js 14.1.4Environments: .envCreating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types✓ Collecting page dataGenerating static pages (0/10) [ ]GET /api/time
✓ Generating static pages (10/10)
✓ Collecting build traces✓ Finalizing page optimizationRoute (app) Size First Load JS
┌ ○ / 178 B 91.3 kB
├ ○ /_not-found 885 B 85.2 kB
├ λ /api/blog 0 B 0 B
├ λ /api/blog/[blogId] 0 B 0 B
├ ○ /api/time 0 B 0 B
├ ○ /blog 178 B 91.3 kB
├ λ /blog/[blogId] 145 B 84.5 kB
├ ○ /dashboard 145 B 84.5 kB
└ ○ /dashboard/settings 145 B 84.5 kBFirst Load JS shared by all 84.3 kB
├ chunks/672-c944b3b6ab6fb493.js 29 kB
├ chunks/90234aad-7488aa0737955c30.js 53.4 kB
└ other shared chunks (total) 1.9 kB○ (Static) prerendered as static content
λ (Dynamic) server-rendered on demand using Node.jsnext-template@0.1.0 start
next start▲ Next.js 14.1.4Local: http://localhost:3000
This means the response for
/api/time
is determined at build time, not on the first request.Opting Out of Caching
However, you don't need to worry about the default caching affecting your application. The conditions for default caching are strict, and there are several scenarios where caching is not applied.
GET Request
Modify
app/api/time/route.ts
:import { type NextRequest } from 'next/server';
export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams return Response.json({ data: new Date().toLocaleTimeString(), params: searchParams.toString() })}
In production, run
npm run build && npm run start
. The response will now be dynamically rendered, showing a different time on each request.npm run build && npm run startnext-template@0.1.0 build
next build▲ Next.js 14.1.4Environments: .envCreating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types✓ Collecting page data✓ Generating static pages (10/10)
✓ Collecting build traces✓ Finalizing page optimizationRoute (app) Size First Load JS
┌ ○ / 178 B 91.3 kB
├ ○ /_not-found 885 B 85.2 kB
├ λ /api/blog 0 B 0 B
├ λ /api/blog/[blogId] 0 B 0 B
├ λ /api/time 0 B 0 B
├ ○ /blog 178 B 91.3 kB
├ λ /blog/[blogId] 145 B 84.5 kB
├ ○ /dashboard 145 B 84.5 kB
└ ○ /dashboard/settings 145 B 84.5 kBFirst Load JS shared by all 84.3 kB
├ chunks/672-c944b3b6ab6fb493.js 29 kB
├ chunks/90234aad-7488aa0737955c30.js 53.4 kB
└ other shared chunks (total) 1.9 kB○ (Static) prerendered as static content
λ (Dynamic) server-rendered on demand using Node.jsnext-template@0.1.0 start
next start▲ Next.js 14.1.4Local: http://localhost:3000✓ Ready in 223ms
The running effect is as follows:
Dynamic rendering will happen when requested, meaning that server-side request will be executed and the time will update.
POST Request
Modify
app/api/time/route.ts
:export async function POST() { console.log('POST /api/time') return Response.json({ data: new Date().toLocaleTimeString() })}
The presence of a POST request causes dynamic rendering because POST requests typically modify data, indicating that caching may not be appropriate.
- Using Dynamic Functions Like Cookies or Headers
Modify
app/api/time/route.ts
:export async function GET(request: NextRequest) { const token = request.cookies.get('token') return Response.json({ data: new Date().toLocaleTimeString() })}
Using cookies or headers results in dynamic rendering, as these values are only known at request time.
- Manually Declaring Dynamic Mode
Modify
app/api/time/route.ts
:export const dynamic = 'force-dynamic'
export async function GET() { return Response.json({ data: new Date().toLocaleTimeString() })}
This explicitly sets the rendering mode to dynamic.
Revalidation
Instead of opting out of caching, you can set cache revalidation times for less critical or less frequently updated pages.
There are two common methods for revalidation:
- Route Segment Configurations
Modify
app/api/time/route.ts
:export const revalidate = 10
export async function GET() { return Response.json({ data: new Date().toLocaleTimeString() })}
Setting
revalidate = 10
means the cache is revalidated at least every 10 seconds. However, this does not mean the server automatically updates the cache every 10 seconds. Instead, it ensures a revalidation happens at least every 10 seconds.
For instance, if you are currently accessing
/api/time
and the time is on 0s, continuing to access this interface within the next 10 seconds will always return the previously cached result. After 10 seconds have passed, accessing /api/time
again on the 12s will still return the cached result but trigger a server cache update. It's only when you access it on the 13s that you will receive the updated result.
next.revalidate
Option
To demonstrate this, we need an API that returns random data, such as an image API.
Create
app/api/image/route.ts
:export async function GET() { const res = await fetch('https://api.thecatapi.com/v1/images/search') const data = await res.json() console.log(data) return Response.json(data)}
In development mode, the data changes with each refresh.Next.js extends the native fetch method to automatically cache the results.
Use
next.revalidate
to set the revalidation time for the fetch request. Modify
app/api/image/route.ts
:export async function GET() { const res = await fetch('https://api.thecatapi.com/v1/images/search', { next: { revalidate: 5 }, }) const data = await res.json() console.log(data) return Response.json(data)}
Refreshing the page multiple times locally will show updated data every 5 seconds.
In production, even though
/api/image
shows static rendering during the build, the data updates as per the revalidation rule.
Summary
Congratulations on completing this section! 👏👏
In this section, we covered dynamic routes, route groups, parallel routes, and intercepting routes. Each has unique filename conventions. Dynamic routes handle dynamic links, route groups organize code, and parallel and intercepting routes address common development scenarios. While parallel and intercepting routes may seem challenging at first, following the demos in this article should help you quickly understand and master these concepts.