React Server Components Data Fetching Patterns
Master async/await patterns in Server Components for efficient data loading and caching.
Overview
React Server Components introduce a new mental model for data fetching where components themselves are async functions that can await data directly. This represents a fundamental shift from the useEffect + API call pattern to co-located data fetching where the component that needs data is responsible for fetching it. The async/await pattern in Server Components eliminates the waterfall problem where nested components each fetch their own data sequentially. By using Promise.all or simply awaiting multiple independent data sources in parallel at the component level, you can fetch all required data in a single round trip time. This pattern naturally encourages optimizing data fetching at the component boundary rather than in effects. Caching strategies become critical with Server Components. The fetch Web API is extended in Next.js to support automatic request deduplication and multiple cache options. Using fetch with cache: 'force-cache' (default) caches responses indefinitely, while cache: 'no-store' bypasses all caching for fresh data. For database queries without fetch, Next.js provides unstable_cache for caching arbitrary function results. Streaming with Suspense enables progressive loading where parts of the UI render as their data becomes available rather than waiting for all data. This dramatically improves Time to First Byte and allows showing skeleton loaders or fallback content immediately while slower data sources complete. The combination of streaming and selective hydration ensures the application feels responsive even when some data is slow.
Code Example
import { db } from '@/lib/db';
import { cache } from 'next/cache';
import { Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleton';
// Cached data function
const getProjects = cache(async () => {
return db.project.findMany({
orderBy: { updatedAt: 'desc' },
take: 20,
include: { owner: true, _count: { select: { tasks: true } } },
});
});
const getCategories = cache(async () => {
return db.category.findMany({
include: { _count: { select: { projects: true } } },
});
});
// Parallel data fetching
async function DashboardData() {
const [projects, categories] = await Promise.all([
getProjects(),
getCategories(),
]);
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<ProjectList projects={projects} />
<CategoryList categories={categories} />
</div>
);
}
// Streaming with Suspense
export default function DashboardPage() {
return (
<main className="container py-8">
<h1 className="text-3xl font-bold mb-8">Dashboard</h1>
<Suspense fallback={<DashboardSkeleton />}>
<DashboardData />
</Suspense>
</main>
);
}
function DashboardSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Skeleton className="h-64" />
<Skeleton className="h-64" />
</div>
);
}
// Individual data fetching components
async function ProjectList({ projects }: { projects: any[] }) {
return (
<section>
<h2 className="text-xl font-semibold mb-4">Recent Projects</h2>
<ul className="space-y-3">
{projects.map((project) => (
<li key={project.id} className="border rounded-lg p-4">
<h3 className="font-medium">{project.name}</h3>
<p className="text-sm text-muted">
{project._count.tasks} tasks • Updated {project.updatedAt}
</p>
</li>
))}
</ul>
</section>
);
}More React Rules
React Component Testing with Testing Library
Test behavior over implementation. Use Testing Library queries to interact with components as users would.
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm }...React Performance Optimization Patterns
Use React.memo, useMemo, and useCallback correctly to prevent unnecessary re-renders.
import React, { useState, useMemo, useCallback, memo, useEffect } from 'react';
// Expensive calculation helper
function calculateExpensiveValue(item...React Form Handling with React Hook Form
Build performant forms with React Hook Form using uncontrolled components and validation.
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import DatePick...