Next.js
04 / 06

Data Fetching Patterns

Next.js Data Fetching Patterns

Next.js provides multiple patterns for fetching data: Server Components, Route Handlers, Server Actions, and client-side fetching.

Server Component Data Fetching

// app/posts/page.tsx
export default async function PostsPage() {
  // Fetch directly in Server Component
  const response = await fetch('https://api.example.com/posts', {
    cache: 'force-cache'  // Default: cache
  });
  const posts = await response.json();
  
  return (
    <div>
      {posts.map((post: any) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </div>
      ))}
    </div>
  );
}

// With database
import { db } from '@/lib/database';

export default async function UsersPage() {
  const users = await db.user.findMany();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Caching & Revalidation

// Cache strategies

// 1. Force cache (static generation)
fetch(url, { cache: 'force-cache' });

// 2. No cache (always fresh)
fetch(url, { cache: 'no-store' });

// 3. Revalidate every N seconds
fetch(url, { next: { revalidate: 60 } });  // ISR

// 4. Tag-based revalidation
fetch(url, { next: { tags: ['posts'] } });

// Revalidate by tag
import { revalidateTag } from 'next/cache';
await revalidateTag('posts');

// Revalidate by path
import { revalidatePath } from 'next/cache';
await revalidatePath('/blog');

// Route segment config
export const revalidate = 60;  // Revalidate every 60 seconds
export const dynamic = 'force-dynamic';  // Always dynamic
export const dynamic = 'force-static';   // Always static
export const runtime = 'edge';  // Edge runtime

Server Actions

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/database';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;
  
  // Validate
  if (!title || !content) {
    return { error: 'Title and content required' };
  }
  
  // Save to database
  await db.post.create({
    data: { title, content }
  });
  
  // Revalidate
  revalidatePath('/posts');
  
  return { success: true };
}

export async function deletePost(id: string) {
  await db.post.delete({ where: { id } });
  revalidatePath('/posts');
}

// app/posts/new/page.tsx
import { createPost } from '@/app/actions';

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

// With Client Component for enhanced UX
'use client';

import { useFormStatus, useFormState } from 'react-dom';
import { createPost } from '@/app/actions';

export default function NewPostForm() {
  const [state, formAction] = useFormState(createPost, null);
  
  return (
    <form action={formAction}>
      <input name="title" />
      <textarea name="content" />
      {state?.error && <p className="error">{state.error}</p>}
      <SubmitButton />
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Creating...' : 'Create Post'}
    </button>
  );
}

Parallel Data Fetching

// Sequential (slower)
export default async function Page() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);  // Waits for user
  
  return <div>{/* render */}</div>;
}

// Parallel (faster)
export default async function Page() {
  const [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts()
  ]);
  
  return <div>{/* render */}</div>;
}

// Streaming with Suspense
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <Suspense fallback={<UserSkeleton />}>
        <UserInfo />
      </Suspense>
      <Suspense fallback={<PostsSkeleton />}>
        <PostsList />
      </Suspense>
    </div>
  );
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free