Data Fetching and Caching
Examples
This guide will walk you through the basics of data fetching and caching in Next.js, providing practical examples and best practices.
Here's a minimal example of data fetching in Next.js:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
This example demonstrates a basic server-side data fetch using the fetch
API in an asynchronous React Server Component.
Reference
fetch
- React
cache
- Next.js
unstable_cache
Caching data with an ORM or Database
You can use the unstable_cache
API to cache the response when running next build
.
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const allPosts = await getPosts()
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
This example caches the result of the database query for 1 hour (3600 seconds). It also adds the cache tag posts
which can then be invalidated with Incremental Static Regeneration.
Reusing data across multiple functions
Next.js uses APIs like generateMetadata
and generateStaticParams
where you will need to use the same data fetched in the page
.
If you are using fetch
, requests can be memoized by adding cache: 'force-cache'
. This means you can safely call the same URL with the same options, and only one request will be made.
Good to know:
- In previous versions of Next.js, using
fetch
would have a defaultcache
value offorce-cache
. This changed in version 15, to a default ofcache: no-store
.
import { notFound } from 'next/navigation'
interface Post {
id: string
title: string
content: string
}
async function getPost(id: string) {
const res = await fetch(`https://api.vercel.app/blog/${id}`, {
cache: 'force-cache',
})
const post: Post = await res.json()
if (!post) notFound()
return post
}
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog', {
cache: 'force-cache',
}).then((res) => res.json())
return posts.map((post: Post) => ({
id: String(post.id),
}))
}
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post = await getPost(id)
return {
title: post.title,
}
}
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post = await getPost(id)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
If you are not using fetch
, and instead using an ORM or database directly, you can wrap your data fetch with the React cache
function. This will de-duplicate and only make one query.
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db' // Example with Drizzle ORM
import { notFound } from 'next/navigation'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
if (!post) notFound()
return post
})
Revalidating cached data
Learn more about revalidating cached data with Incremental Static Regeneration.
Was this helpful?