Next.js
Data Fetching and Caching
by Tomas Trescak
t.trescak@westernsydney.edu.au
Next.js
/
Why Data Fetching?
- Dynamic Content
- User Interaction
- Fresh Information
Next.js
/
Why Caching?!
- Performance
- Efficiency
- Offline Access
Next.js
/
Server-Side Data Fetching
- Faster Load Times
- Better SEO
- Secure Data Handling
Next.js
/
Server-Side Data Fetching
export default async function Page() {
const res = await fetch('https://api.vercel.app/blog');
const posts = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
/app/page.tsx
Next.js
/
Server-Side Data Fetching
import { db } from '@/lib/db';
type Params = { params: { id: string } }
export default async function Page({ params }: Params) {
const { id } = await params;
const post = await db.select().from(posts).where({ id });
return (
<main>
<h1>{post.title}</h1>
<p>{post.description}</p>
</main>
);
}
/app/post/[id]/page.tsx
Next.js
/
Client-Side Data Fetching
- Dynamic Updates
- User-Specific Data
- Client-Side Caching
https://tutorialsight.com/next-js-vs-react/
Next.js
/
Client-Side Data Fetching
'use client';
import { useState, useEffect } from 'react';
export function Posts() {
const [posts, setPosts] = useState(null);
useEffect(() => {
async function fetchPosts() {
const res = await fetch('https://api.vercel.app/blog');
const data = await res.json();
setPosts(data);
}
fetchPosts();
}, []);
if (!posts) return <div>Loading...</div>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
/app/page.tsx
Next.js
/
Tanstack Query
'use client';
import { useQuery } from '@tanstack/react-query';
import { useParams } from "next/navigation";
export function Posts() {
const { id } = useParams();
const { data: posts, isLoading } = useQuery({
queryKey: ['post', id],
queryFn: async () => {
const res = await fetch(`https://blog.app/post/${id}`);
return res.json();
},
});
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
/app/post/[id]/page.tsx
Next.js
/
Caching
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>
);
}
/app/post/[id]/page.tsx
Next.js
/
Cache Invalidation
import { revalidateTag } from "next/cache";
import { unstable_cache } from "next/cache";
export async function invalidateUserData() {
await revalidateTag("posts");
// OR
await unstable_cache.clear(["posts"]);
}
/app/utils/cache.ts
Next.js
/
Reusing Data
type Post = {}
// ... (getPost function and other code)
export async function generateStaticParams() {
const posts: Post[] = await fetch(
'https://our-blog-api.com/posts', {
cache: 'force-cache',
}
).then((res) => res.json());
return posts.map((post) => ({
slug: post.slug,
}));
}
/
app/blog/[slug]/page.tsx
Next.js
/
Parallel Fetching
export default async function ParallelHome() {
const latestPosts = await getLatestPosts();
const popularPosts = await getPopularPosts(),
//... render latestPosts and popularPosts...
}
export default async function ParallelHome() {
const [latestPosts, popularPosts] = await Promise.all([
getLatestPosts(),
getPopularPosts(),
]);
//... render latestPosts and popularPosts...
}
/
app/blog/[slug]/page.tsx
Next.js
/
Preloading
// components/PostLink.tsx
import { preload } from './Post';
export default function PostLink({ post }) {
return (
<Link
href={`/blog/${post.slug}`}
onMouseEnter={() => preload(post.slug)}>
{post.title}
</Link>
);
}
// components/Post.tsx
import { cache } from 'react';
export const preload = (slug) => {
void getPost(slug);
};
const getPost = cache(async (slug) => {
const res = await fetch(`https://our-blog-api.com/posts/${slug}`);
return res.json();
});
export default async function Post({ slug }) {
const post = await getPost(slug);
//... render post...
}
/
app/blog/[slug]/page.tsx
Next.js
/
Caching and Server-Only
import { cache } from 'react';
import 'server-only';
export const getPost = cache(async (slug) => {
const res = await fetch(`https://our-blog-api.com/posts/${slug}`);
<narration>Fetches the post data.</narration>
return res.json();
});
/utils/blog.ts
Next.js
/
Tainting Information
import {
experimental_taintObjectReference,
experimental_taintUniqueValue,
} from 'react';
export async function getPost(slug) {
const res = await fetch(`https://our-blog-api.com/posts/${slug}`);
const post = await res.json();
experimental_taintObjectReference('Do not send', post);
experimental_taintUniqueValue(
'Do not send email',
post,
post.author.email
);
return post;
}
/utils/blog.ts
Next.js
/
Key Takeaways
- Server-side fetching for initial load and SEO.
- Client-side fetching for dynamic updates.
- Caching for performance and efficiency.
- TanStack Query simplifies client-side data fetching.
- Protect sensitive data with Taint API.