Lessons in this course (8)

4. Data fetching, caching and Server Actions

Intermediate★ Must knowLesson 4 of 8
tsx
// Fetch in a Server Component (Next 15+: not cached unless you opt in)
const res = await fetch("https://api.example.com/rates", { next: { revalidate: 300, tags: ["rates"] } });

// Next 16 Cache Components: cache a function's result
async function getCategories() {
  "use cache";
  return db.query("SELECT Id, Name FROM dbo.Categories");
}

Server Actions — functions that run on the server, callable from forms or client code (no API route needed):

tsx
// app/products/new/actions.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { createProduct } from "@/lib/products";

const Schema = z.object({ name: z.string().min(2), price: z.coerce.number().positive() });

export async function addProduct(formData: FormData) {
  const parsed = Schema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: "Invalid input" };
  await createProduct(parsed.data);           // auth check here too — actions are public endpoints!
  revalidatePath("/products");
  redirect("/products");
}

// app/products/new/page.tsx
import { addProduct } from "./actions";
export default function NewProduct() {
  return (
    <form action={addProduct}>
      <input name="name" placeholder="Name" required />
      <input name="price" type="number" step="0.01" required />
      <button type="submit">Save</button>
    </form>
  );
}

Forms work even before JavaScript loads (progressive enhancement); useActionState and useFormStatus give pending/error states in Client Components.