正在加载,请稍候…

Next.js App Router: Server Components, Data Fetching, and Caching

Master Next.js 14 App Router. Learn React Server Components, Server Actions, data fetching patterns, caching strategies, and performance optimization.

Next.js App Router: Server Components, Data Fetching, and Caching

Server Components vs Client Components

// app/users/page.tsx - Server Component (default)
// Runs on server, no client JavaScript
async function UsersPage() {
  // Direct database access!
  const users = await db.query('SELECT * FROM users');

  return (
    <div>
      <h1>Users</h1>
      {users.map(user => <UserCard key={user.id} user={user} />)}
    </div>
  );
}

// app/users/SearchBar.tsx - Client Component
'use client';  // Required for interactivity

import { useState } from 'react';

export function SearchBar({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('');
  // ... interactive code
}

Data Fetching Patterns

// Parallel data fetching
async function DashboardPage() {
  // Both requests start simultaneously
  const [user, orders] = await Promise.all([
    fetchUser(),
    fetchOrders(),
  ]);

  return <Dashboard user={user} orders={orders} />;
}

// Sequential when data depends on previous
async function UserOrdersPage({ params }: { params: { userId: string } }) {
  const user = await fetchUser(params.userId);
  const orders = await fetchOrders(user.id);  // needs user.id first

  return <OrderList user={user} orders={orders} />;
}

Caching

// Default: cached indefinitely (static)
const data = await fetch('https://api.example.com/products');

// Revalidate every 60 seconds
const data = await fetch('https://api.example.com/products', {
  next: { revalidate: 60 }
});

// No cache (dynamic)
const data = await fetch('https://api.example.com/user', {
  cache: 'no-store'
});

// Route segment config
// app/dashboard/layout.tsx
export const revalidate = 300; // revalidate every 5 minutes
export const dynamic = 'force-dynamic'; // always dynamic

Server Actions

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

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createUser(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  // Server-side validation
  if (!email.includes('@')) {
    return { error: 'Invalid email' };
  }

  await db.insert('users', { name, email });
  revalidatePath('/users');  // Invalidate cache
  redirect('/users');         // Redirect after success
}

// app/users/new/page.tsx
import { createUser } from '../actions';

export default function NewUserPage() {
  return (
    <form action={createUser}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit">Create User</button>
    </form>
  );
}

Streaming with Suspense

import { Suspense } from 'react';

async function SlowComponent() {
  await new Promise(r => setTimeout(r, 2000)); // Simulate slow data fetch
  const data = await fetchExpensiveData();
  return <DataDisplay data={data} />;
}

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* Shows immediately */}
      <QuickStats />

      {/* Streams in when ready - shows skeleton while loading */}
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

Middleware

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Auth check
  const token = request.cookies.get('auth-token');
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Add headers
  const response = NextResponse.next();
  response.headers.set('x-pathname', request.nextUrl.pathname);
  return response;
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

Next.js App Router with React Server Components represents a fundamental shift toward server-centric frontend development.