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
25

Forms with useActionState & Progressive Enhancement

Reading14m
26

Optimistic UI with useOptimistic

Reading12m

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 27 of 54Reading12 min

Validation with Zod + Server Actions

#Validation with Zod + Server Actions¶

Don't trust the form. Don't trust the client. Validate on the server, every time.

Why Server-Side Validation Is Non-Negotiable¶

A motivated user can:

  • Submit your form with curl
  • Modify the form's HTML in DevTools
  • Edit the JavaScript that calls your action

If the only validation is client-side, your server data is as good as untrusted.

A Reusable Validator¶

ts
12 lines
1// lib/validators/course.ts
2import { z } from "zod";
3
4export const courseInputSchema = z.object({
5  title: z.string().min(3).max(200),
6  slug: z.string().min(3).max(80).regex(/^[a-z0-9-]+$/),
7  level: z.enum(["beginner", "intermediate", "advanced"]).default("beginner"),
8  price: z.coerce.number().int().min(0).max(99_999),
9  isFree: z.coerce.boolean().default(false),
10});
11
12export type CourseInput = z.infer<typeof courseInputSchema>;

z.coerce.number() and z.coerce.boolean() are gold for FormData, where every value is a string.

Using It In an Action¶

ts
20 lines
1"use server";
2import { courseInputSchema } from "@/lib/validators/course";
3
4type State = { ok: boolean; errors?: Record<string, string> };
5
6export async function createCourse(prev: State, formData: FormData): Promise<State> {
7  const parsed = courseInputSchema.safeParse(Object.fromEntries(formData));
8
9  if (!parsed.success) {
10    return {
11      ok: false,
12      errors: Object.fromEntries(
13        parsed.error.issues.map((i) => [i.path[0] as string, i.message]),
14      ),
15    };
16  }
17
18  const course = await db.course.create({ data: parsed.data });
19  return { ok: true };
20}

Sharing the Schema with the Client¶

Re-use the same Zod schema in the client component to give instant feedback while typing — without duplicating rules:

tsx
15 lines
1"use client";
2import { courseInputSchema } from "@/lib/validators/course";
3import { useState } from "react";
4
5export function CourseForm() {
6  const [errors, setErrors] = useState<Record<string, string>>({});
7
8  function onBlur(e: React.FocusEvent<HTMLInputElement>) {
9    const field = e.target.name;
10    const value = e.target.value;
11    const result = courseInputSchema.shape[field as keyof CourseInput].safeParse(value);
12    setErrors((s) => ({ ...s, [field]: result.success ? "" : result.error.issues[0].message }));
13  }
14  // …
15}

Defense In Depth¶

LayerCatchesImplemented with
HTML attributes (required, minlength)TyposThe browser
Client-side ZodUX feedbackSame schema
Server-side ZodMalicious / scripted requestsSame schema
DB constraints (UNIQUE, CHECK)Race conditionsSQL

Use all four. The schema is the contract; the DB is the law.

Returning Field-Specific Errors¶

A robust action returns errors keyed by field name. The form then renders them adjacent to the right input:

tsx
1 line
1{state.errors?.title && <p className="text-red-600 text-xs">{state.errors.title}</p>}

A single user-visible error toast is a fallback — good forms tell the user precisely which field is wrong.

Previous

Optimistic UI with useOptimistic

Next

Chapter 5 — Quiz

Use ← → arrow keys to navigate between lessons