CoachnestCoachnest
Sign InGet Started
Back to course

Next.js 16: The Complete Developer Guide

…
—
Contents
1

What's New in Next.js 16

Reading14mFree
2

Installation, CLI & Your First Project

Reading12mFree
3

Project Structure & Conventions Deep Dive

Reading16m
4

Turbopack — The New Default Bundler

Video18m
5

Configuring TypeScript, ESLint & next.config.ts

Reading14m
6

Chapter 1 — Quiz

Quiz10m
7

App Router Fundamentals

Reading16m
8

Dynamic Routes, Catch-Alls & Type-Safe Params

Reading14m
9

Route Groups & Parallel Routes

Reading16m
10

Intercepting Routes & Modal Patterns

Reading12m
11

Loading, Error & Not-Found UI

Reading12m
12

Chapter 2 — Routing Quiz

Quiz12m
13

Understanding the Server/Client Boundary

Reading18m
14

Choosing When to use "use client"

Reading14m
15

Composing Server & Client Components

Reading14m
16

server-only, client-only & Code Splitting

Reading12m
17

Chapter 3 — Quiz

Quiz10m
18

Fetching Data in Server Components

Reading14m
19

The Next.js 16 Cache Model

Reading16m
20

Revalidation: revalidateTag, revalidatePath & On-Demand

Reading12m
21

Cache Components — Building Reusable Cached Functions

Reading14m
22

Search Params, Cookies & Dynamic APIs

Reading12m
23

Chapter 4 — Quiz

Quiz12m
24

Server Actions From First Principles

Reading16m

Forms with useActionState & Progressive Enhancement

Reading14m
26

Optimistic UI with useOptimistic

Reading12m
27

Validation with Zod + Server Actions

Reading12m
28

Chapter 5 — Quiz

Quiz10m
29

Static vs Dynamic vs Streaming

Reading14m
30

generateStaticParams & Pre-Rendering Dynamic Routes

Reading12m
31

Incremental Static Regeneration (ISR)

Reading12m
32

Partial Prerendering (PPR)

Reading16m
33

Edge Runtime vs Node.js Runtime

Reading12m
34

Rendering Strategies Deep Dive

Video22m
35

Chapter 6 — Quiz

Quiz12m
36

Tailwind CSS v4 with Next.js 16

Reading14m
37

CSS Modules, Global Styles & Scoped CSS

Reading10m
38

next/image — Smart, Fast Images

Reading14m
39

Fonts, Icons & Metadata

Reading12m
40

Chapter 7 — Quiz

Quiz10m
41

Middleware Fundamentals

Reading14m
42

Sessions, JWTs & Cookies

Reading16m
43

Protecting Server Components & Server Actions

Reading12m
44

Chapter 8 — Quiz

Quiz10m
45

Prisma with Next.js — The Production Setup

Reading14m
46

Mutations: Server Actions + Database Writes

Reading12m
47

Route Handlers & REST APIs

Reading12m
48

Chapter 9 — Quiz

Quiz10m
49

Unit & Component Testing with Vitest

Reading12m
50

End-to-End Testing with Playwright

Reading14m
51

Deploying to Vercel

Reading12m
52

Self-Hosting with Docker

Reading14m
53

Production Performance Checklist

Reading12m
54

Final Assessment — Next.js 16 Mastery

Quiz20m
←→navigate lessons
Chapter 5 of 10·Chapter 5 — Server Actions & Forms
Lesson 25 of 54Reading14 min

Forms with useActionState & Progressive Enhancement

#Forms with useActionState & Progressive Enhancement¶

useActionState (formerly useFormState) is the canonical way to wire a form to a Server Action and get back state — validation errors, success messages, the new entity.

Shape of the Action¶

ts
24 lines
1// app/contact/actions.ts
2"use server";
3import { z } from "zod";
4
5type State = { ok: boolean; error?: string; data?: { id: string } };
6
7const schema = z.object({
8  email: z.string().email(),
9  message: z.string().min(10).max(2000),
10});
11
12export async function submitContact(prev: State, formData: FormData): Promise<State> {
13  const parsed = schema.safeParse({
14    email: formData.get("email"),
15    message: formData.get("message"),
16  });
17
18  if (!parsed.success) {
19    return { ok: false, error: parsed.error.issues[0].message };
20  }
21
22  const msg = await db.contactMessage.create({ data: parsed.data });
23  return { ok: true, data: { id: msg.id } };
24}

Note the signature: (previousState, formData) → newState.

The Client Component¶

tsx
25 lines
1// app/contact/ContactForm.tsx
2"use client";
3import { useActionState } from "react";
4import { submitContact } from "./actions";
5
6const initial = { ok: false } as const;
7
8export function ContactForm() {
9  const [state, action, pending] = useActionState(submitContact, initial);
10
11  if (state.ok) {
12    return <p className="text-green-600">Thanks — message #{state.data!.id} received.</p>;
13  }
14
15  return (
16    <form action={action} className="space-y-3">
17      <input name="email" type="email" required placeholder="you@example.com" />
18      <textarea name="message" required minLength={10} placeholder="What's up?" />
19      {state.error && <p className="text-red-600 text-sm">{state.error}</p>}
20      <button disabled={pending} className="rounded bg-black px-4 py-2 text-white">
21        {pending ? "Sending…" : "Send"}
22      </button>
23    </form>
24  );
25}

The third value, pending, is React's built-in pending boolean — no manual state needed.

Progressive Enhancement¶

Even with JavaScript disabled, this form still submits. The browser POSTs to Next's action endpoint, the server runs the action, the page re-renders with the new state in the URL.

Test it:

  1. 1Disable JS in DevTools → Settings → Debugger
  2. 2Submit the form
  3. 3The success state still appears

That's a real, working website with zero client JS for the form.

useFormStatus — Pending States for Nested Buttons¶

A parent form's pending state can be read by any nested Client Component:

tsx
7 lines
1"use client";
2import { useFormStatus } from "react-dom";
3
4export function SubmitButton({ children }: { children: React.ReactNode }) {
5  const { pending } = useFormStatus();
6  return <button disabled={pending}>{pending ? "Saving…" : children}</button>;
7}

Drop <SubmitButton>Save</SubmitButton> into any form — it knows when its enclosing form is submitting.

Multi-Field Errors¶

Return a structured error map:

ts
11 lines
1type State =
2  | { ok: true; data: { id: string } }
3  | { ok: false; errors: Record<string, string> };
4
5if (!parsed.success) {
6  const errors: Record<string, string> = {};
7  for (const issue of parsed.error.issues) {
8    errors[issue.path[0] as string] = issue.message;
9  }
10  return { ok: false, errors };
11}
tsx
3 lines
1{!state.ok && state.errors.email && (
2  <p className="text-red-600 text-sm">{state.errors.email}</p>
3)}

Previous

Server Actions From First Principles

Next

Optimistic UI with useOptimistic

Use ← → arrow keys to navigate between lessons