Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply Next.js best practices for RSC boundaries, async APIs, routing, metadata, and optimization.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
data-patterns.md
1# Data Patterns23Choose the right data fetching pattern for each use case.45## Decision Tree67```8Need to fetch data?9├── From a Server Component?10│ └── Use: Fetch directly (no API needed)11│12├── From a Client Component?13│ ├── Is it a mutation (POST/PUT/DELETE)?14│ │ └── Use: Server Action15│ └── Is it a read (GET)?16│ └── Use: Route Handler OR pass from Server Component17│18├── Need external API access (webhooks, third parties)?19│ └── Use: Route Handler20│21└── Need REST API for mobile app / external clients?22└── Use: Route Handler23```2425## Pattern 1: Server Components (Preferred for Reads)2627Fetch data directly in Server Components - no API layer needed.2829```tsx30// app/users/page.tsx31async function UsersPage() {32// Direct database access - no API round-trip33const users = await db.user.findMany();3435// Or fetch from external API36const posts = await fetch('https://api.example.com/posts').then(r => r.json());3738return (39<ul>40{users.map(user => <li key={user.id}>{user.name}</li>)}41</ul>42);43}44```4546**Benefits**:47- No API to maintain48- No client-server waterfall49- Secrets stay on server50- Direct database access5152## Pattern 2: Server Actions (Preferred for Mutations)5354Server Actions are the recommended way to handle mutations.5556```tsx57// app/actions.ts58'use server';5960import { revalidatePath } from 'next/cache';6162export async function createPost(formData: FormData) {63const title = formData.get('title') as string;6465await db.post.create({ data: { title } });6667revalidatePath('/posts');68}6970export async function deletePost(id: string) {71await db.post.delete({ where: { id } });7273revalidateTag('posts');74}75```7677```tsx78// app/posts/new/page.tsx79import { createPost } from '@/app/actions';8081export default function NewPost() {82return (83<form action={createPost}>84<input name="title" required />85<button type="submit">Create</button>86</form>87);88}89```9091**Benefits**:92- End-to-end type safety93- Progressive enhancement (works without JS)94- Automatic request handling95- Integrated with React transitions9697**Constraints**:98- POST only (no GET caching semantics)99- Internal use only (no external access)100- Cannot return non-serializable data101102## Pattern 3: Route Handlers (APIs)103104Use Route Handlers when you need a REST API.105106```tsx107// app/api/posts/route.ts108import { NextRequest, NextResponse } from 'next/server';109110// GET is cacheable111export async function GET(request: NextRequest) {112const posts = await db.post.findMany();113return NextResponse.json(posts);114}115116// POST for mutations117export async function POST(request: NextRequest) {118const body = await request.json();119const post = await db.post.create({ data: body });120return NextResponse.json(post, { status: 201 });121}122```123124**When to use**:125- External API access (mobile apps, third parties)126- Webhooks from external services127- GET endpoints that need HTTP caching128- OpenAPI/Swagger documentation needed129130**When NOT to use**:131- Internal data fetching (use Server Components)132- Mutations from your UI (use Server Actions)133134## Avoiding Data Waterfalls135136### Problem: Sequential Fetches137138```tsx139// Bad: Sequential waterfalls140async function Dashboard() {141const user = await getUser(); // Wait...142const posts = await getPosts(); // Then wait...143const comments = await getComments(); // Then wait...144145return <div>...</div>;146}147```148149### Solution 1: Parallel Fetching with Promise.all150151```tsx152// Good: Parallel fetching153async function Dashboard() {154const [user, posts, comments] = await Promise.all([155getUser(),156getPosts(),157getComments(),158]);159160return <div>...</div>;161}162```163164### Solution 2: Streaming with Suspense165166```tsx167// Good: Show content progressively168import { Suspense } from 'react';169170async function Dashboard() {171return (172<div>173<Suspense fallback={<UserSkeleton />}>174<UserSection />175</Suspense>176<Suspense fallback={<PostsSkeleton />}>177<PostsSection />178</Suspense>179</div>180);181}182183async function UserSection() {184const user = await getUser(); // Fetches independently185return <div>{user.name}</div>;186}187188async function PostsSection() {189const posts = await getPosts(); // Fetches independently190return <PostList posts={posts} />;191}192```193194### Solution 3: Preload Pattern195196```tsx197// lib/data.ts198import { cache } from 'react';199200export const getUser = cache(async (id: string) => {201return db.user.findUnique({ where: { id } });202});203204export const preloadUser = (id: string) => {205void getUser(id); // Fire and forget206};207```208209```tsx210// app/user/[id]/page.tsx211import { getUser, preloadUser } from '@/lib/data';212213export default async function UserPage({ params }) {214const { id } = await params;215216// Start fetching early217preloadUser(id);218219// Do other work...220221// Data likely ready by now222const user = await getUser(id);223return <div>{user.name}</div>;224}225```226227## Client Component Data Fetching228229When Client Components need data:230231### Option 1: Pass from Server Component (Preferred)232233```tsx234// Server Component235async function Page() {236const data = await fetchData();237return <ClientComponent initialData={data} />;238}239240// Client Component241'use client';242function ClientComponent({ initialData }) {243const [data, setData] = useState(initialData);244// ...245}246```247248### Option 2: Fetch on Mount (When Necessary)249250```tsx251'use client';252import { useEffect, useState } from 'react';253254function ClientComponent() {255const [data, setData] = useState(null);256257useEffect(() => {258fetch('/api/data')259.then(r => r.json())260.then(setData);261}, []);262263if (!data) return <Loading />;264return <div>{data.value}</div>;265}266```267268### Option 3: Server Action for Reads (Works But Not Ideal)269270Server Actions can be called from Client Components for reads, but this is not their intended purpose:271272```tsx273'use client';274import { getData } from './actions';275import { useEffect, useState } from 'react';276277function ClientComponent() {278const [data, setData] = useState(null);279280useEffect(() => {281getData().then(setData);282}, []);283284return <div>{data?.value}</div>;285}286```287288**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.289290## Quick Reference291292| Pattern | Use Case | HTTP Method | Caching |293|---------|----------|-------------|---------|294| Server Component fetch | Internal reads | Any | Full Next.js caching |295| Server Action | Mutations, form submissions | POST only | No |296| Route Handler | External APIs, webhooks | Any | GET can be cached |297| Client fetch to API | Client-side reads | Any | HTTP cache headers |298