Veröffentlicht · wird verbessert
Next.js-Anleitung · 4/6
Dieses Kapitel ist vorerst nur auf Englisch verfügbar.
In Next.js, reads usually happen in Server Components, writes in Server Actions or Route Handlers, and browser-only UI state lives in Client Components. This chapter covers fetching, caching, Route Handlers, forms and client state.
Server Components can be async, so you await data right inside them, with fetch or an ORM or database client. The code runs only on the server, so secrets never reach the browser.
// app/products/page.tsx
import { db } from "@/lib/db";
export default async function ProductsPage() {
const [products, categories] = await Promise.all([
db.product.findMany({ take: 20 }),
fetch("https://api.example.com/categories").then((res) => res.json()),
]);
return (
<main>
<p>{categories.length} categories</p>
<ul>
{products.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</main>
);
}Start independent requests together with Promise.all; awaiting them one by one creates a waterfall. Keeping data access in lib/ functions makes reuse and authorization checks easier.
Caching is the part of Next.js that has changed most between major versions. In recent releases fetch results are not cached by default; you opt in where it makes sense. The commonly used fetch options are:
// cache the result and reuse it
await fetch("https://api.example.com/menu", { cache: "force-cache" });
// time-based revalidation: refresh at most once an hour
await fetch("https://api.example.com/news", { next: { revalidate: 3600 } });
// tag the data so you can invalidate just this tag later
await fetch("https://api.example.com/posts", { next: { tags: ["posts"] } });After a mutation, invalidate the cache so users see fresh content. revalidatePath from next/cache invalidates a route, and revalidateTag invalidates tagged data (recent releases take a second argument that controls how revalidation behaves).
The newest releases also offer an opt-in model: enable the cacheComponents option and put a "use cache" directive at the top of a function or component. In that model, cacheLife sets how long an entry lives and cacheTag attaches tags. Check which model your project uses and follow the docs for that version.
When you need an HTTP response such as JSON rather than UI, export functions named after HTTP methods from a route.ts file. They use the standard Web Request and Response APIs and suit public APIs and webhooks. A folder cannot contain both and .
page.tsxroute.ts// app/api/posts/route.ts
import { db } from "@/lib/db";
export async function GET() {
const posts = await db.post.findMany();
return Response.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.post.create({ data: { title: body.title } });
return Response.json(post, { status: 201 });
}Server Components should call data functions directly rather than fetch your own Route Handlers.
Server Actions are a natural fit for form submissions and mutations. Put them in a separate file and Client Components can import them too. React's useActionState hook gives you both the value the action returns (such as an error message) and a pending flag.
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { db } from "@/lib/db";
export async function createPost(prev: { error?: string }, formData: FormData) {
const title = String(formData.get("title") ?? "").trim();
if (!title) return { error: "Please enter a title." };
await db.post.create({ data: { title } });
revalidatePath("/posts");
redirect("/posts");
}// app/posts/new/form.tsx
"use client";
import { useActionState } from "react";
import { createPost } from "@/app/actions";
export function NewPostForm() {
const [state, formAction, pending] = useActionState(createPost, {});
return (
<form action={formAction}>
<input name="title" />
{state.error && <p role="alert">{state.error}</p>}
<button disabled={pending}>{pending ? "Saving..." : "Save"}</button>
</form>
);
}Libraries such as Zod are popular for validation, which, like permission checks, must run inside the action on the server.
UI-only state, such as an open menu or the selected tab, belongs in useState or useReducer in Client Components. When several components share it, create a Context provider as a Client Component and wrap it around the tree in the root layout. The layout itself stays a Server Component.
// app/providers.tsx
"use client";
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext<{ dark: boolean; toggle: () => void } | null>(null);
export function Providers({ children }: { children: React.ReactNode }) {
const [dark, setDark] = useState(false);
return <ThemeContext value={{ dark, toggle: () => setDark(!dark) }}>{children}</ThemeContext>;
}
export const useTheme = () => useContext(ThemeContext);State worth sharing or bookmarking, such as a search query or sort order, is best kept in URL search params. Pages read it through the searchParams prop and Client Components through useSearchParams. For server data that updates frequently in the browser, SWR or TanStack Query work well, and libraries such as Zustand help with complex global state.
async Server Components and start independent requests together with Promise.all.fetch options or "use cache").
0 Kommentare
Anmelden · Melde dich an, um einen Kommentar zu schreiben.
Schreib den ersten Kommentar.