Episode 2 — React Frontend Architecture NextJS / 2.21 — Working with Server Actions
2.21 — Exercise Questions: Server Actions
Hands-on prompts. Implement in a Next.js App Router project (or pseudo-code on paper first, then code).
A. Foundations (2.21.b)
-
Create
app/hello/actions.tswith'use server'and exportsayHello(formData: FormData)that reads anamefield and returns a greeting string. Render the result in a Server Component page without using'use client'. -
List three things you must not do inside a Server Action body.
-
Explain out loud: why can a Client Component import
deleteTodofrom a'use server'file without bundling your ORM into the browser?
B. Forms & State (2.21.c)
-
Build a signup form using
useActionStatethat shows field-level errors for invalid email and short password. -
Extract a
SubmitButtonthat usesuseFormStatusto disable itself while the form is submitting. -
Add a server-side Zod schema to the same form. Map Zod’s
flatten().fieldErrorsinto your UI.
C. Data & Cache (2.21.a + 2.21.c)
-
After creating a blog comment via a Server Action, call
revalidatePathso the comment list on the post page updates. Verify stale-then-fresh behavior manually. -
Replace path revalidation with
revalidateTagon post data fetched with{ next: { tags: ['post-' + slug] } }.
D. Authorization & Safety (2.21.d)
-
Implement
deletePost(postId: string)that loads the post, comparesauthorIdto the session user, and returns{ ok: false, code: 'FORBIDDEN' }instead of throwing when unauthorized. -
Add naive per-IP rate limiting in memory (acceptable only for demos) — articulate why production would use Redis or an edge limiter instead.
E. Comparison with Route Handlers (2.21.a)
-
Implement the same “create comment” flow twice: once with a Server Action, once with
POSTinroute.ts. Document which callers each supports. -
Write a curl command that successfully hits your Route Handler. Explain why curl cannot “call the Server Action” the same way without replicating framework internals.
F. Stretch
-
Use
useTransitionto call a Server Action from a button that is not inside a<form>. -
Debate (written paragraph): in a large team, when would you forbid Server Actions for certain domains (billing, admin) and require Route Handlers + explicit OpenAPI instead?
Answers & guidance (self-check)
1. sayHello without client
- Server page calls the action from a
<form action={sayHello}>; display message usinguseActionStatereturn or progressive enhancement pattern per your chosen UX (pure server redirect vs stateful client wrapper).
2. Three must-nots (examples)
- Do not trust client-only validation; do not return secrets; do not perform unbounded slow work without timeouts; do not skip authz checks; do not leak stack traces (pick three when answering aloud).
3. Why ORM not in browser
- The client imports a stub; the bundler strips server graph from client bundle. Actual execution happens on the server runtime.
4–6. Forms
useActionStatesignature: action receives(prevState, formData)after wrapping; return discriminated union for{ ok: true }vs{ ok: false, fieldErrors }.useFormStatusmust be used in a child of<form>to observepending.- Map Zod
error.flatten().fieldErrorskeys to inputs.
7–8. Revalidation
revalidatePath('/posts/[slug]')(or concrete path) after mutation.- Tags: ensure fetch in RSC uses same tag string; webhook or action calls
revalidateTag('post-' + slug).
9. Authz pattern
const post = await db.post.findUnique(...); if!post→NOT_FOUND; ifpost.authorId !== session.user.id→ structured FORBIDDEN (not thrown unless you want error boundary semantics).
10. In-memory limiter
- Serverless has multiple instances—each has its own memory, so limits are ineffective globally. Use Redis/edge rate limiter for shared counters + TTL.
11. Dual implementation
- Table: Server Action supports Next app internal forms/transitions; Route Handler supports curl/mobile/partner HTTP.
12. curl vs actions
- Actions use Next-internal POST protocol with encoded fields—not your arbitrary JSON REST unless you build it. curl can hit
/api/...easily.
13. useTransition
- Client button
onClick={() => startTransition(() => deleteTodo(id)))}; still show optimistic UI carefully.
14. Forbid actions in billing/admin
- Reasons: need auditable HTTP API, strict versioning, penetration testing against known routes, consumers outside React, compliance reviews, long-lived integrations—OpenAPI + Route Handlers reduce ambiguity.