API Routes & Server Actions

Website Rebuild with Next.js

Chapter 8 · API Routes & Server Actions

This chapter gives the admin a way to actually change something — updatePageTitle, a Server Action, with no auth check yet, deliberately: the security gap gets closed properly in Chapter 9. Every one of this course's own five already-complete siblings built the identical feature, the identical gap, closed the identical way one chapter later — a real, intentional continuity thread running through the whole series, not a coincidence of independent design.

Enabling the Cache Components Model

// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { cacheComponents: true, }; export default nextConfig;

This unlocks 'use cache', cacheTag, and updateTag — the tools this chapter actually needs to give an admin edit a genuine read-your-writes guarantee.

Caching the Page Lookup

// lib/pages.ts import { cacheTag } from 'next/cache'; import { prisma } from './prisma'; export async function getPageByPath(fullPath: string) { 'use cache'; cacheTag(`page:${fullPath}`); return prisma.page.findUnique({ where: { fullPath } }); }

Chapter 3's own route now calls getPageByPath(fullPath) in place of a direct prisma.page.findUnique — the query itself is unchanged, but its result is now cached and taggable.

The Server Action: updatePageTitle

// app/actions.ts 'use server'; import { prisma } from '@/lib/prisma'; import { updateTag } from 'next/cache'; export async function updatePageTitle(pageId: number, newTitle: string) { const page = await prisma.page.update({ where: { id: pageId }, data: { title: newTitle }, }); updateTag(`page:${page.fullPath}`); }
No auth check — deliberately, for now
updatePageTitle is fully wired up and genuinely works — anyone who can call it can rename any page on the site, right now, with nothing standing in the way. This is left exactly this way on purpose, matching every sibling course's own identical gap, so Chapter 9 has a real, working mutation to actually secure rather than a hypothetical one.

revalidateTag's New Required Argument, vs. updateTag

// The old single-argument form no longer type-checks in Next.js 16: // revalidateTag(`page:${page.fullPath}`); // TypeScript error — missing 2nd argument // The current form requires an explicit cacheLife profile: revalidateTag(`page:${page.fullPath}`, 'max');
Why this chapter uses updateTag, not revalidateTag
revalidateTag marks cached data stale and lets it refresh in the background — the visitor who happens to load the page in the exact next instant might still briefly see the old title. updateTag expires and refreshes the cache within the same request, so the admin who just renamed a page sees the new title immediately on the very next page they load — genuine read-your-writes semantics, and a materially better fit for this specific "I just edited something, show me the result" scenario than what this chapter's own original version had available.

Built-In CSRF Protection — The One Piece Nobody Else Had to Wire Up

A genuine, structural difference from every sibling
A Server Action is compiled to a unique, unguessable endpoint ID and is only ever invocable via POST — and Next.js automatically compares the request's own Origin header against the deployment's allowed origins before running it, rejecting the call outright on a mismatch. Django needed an explicit {% csrf_token %} in every form. Laravel needed @csrf. Rails needed protect_from_forgery plus form_with's own automatic authenticity token. updatePageTitle above has no equivalent line anywhere — this specific protection is a structural property of what a Server Action is, not something this chapter had to add.

The Same Mutation, Four Structural Answers

Next.jsDjangoLaravelRails
ShapeOne function — implicit client/server boundaryView function + ModelForm + URL — three explicit piecesController method + FormRequest — a class-based extra layerSingle Controller action, Strong Parameters inline — the leanest of the four
Auth gap made visible asA missing check inside the function bodyAn absent permission check in the viewauthorize() literally returning trueAn absent before_action filter
CSRF protectionAutomatic — built into what a Server Action isManual — {% csrf_token %}Manual — @csrfManual — protect_from_forgery + automatic token

Next.js's own implicit boundary is genuinely a double-edged design: it's what makes updatePageTitle callable like an ordinary function with zero explicit wiring, and it's also exactly why the missing auth check is easy to miss on a casual read — there's no separate file, no visible authorize() method, nothing forcing the question to be asked. Every sibling's own explicit alternative makes the gap structurally harder to overlook, at the cost of more ceremony to write in the first place.

Hands-On Exercises

Exercise 1

Build updatePageTitle exactly as written in this chapter, call it against a real page with no authentication in place, and confirm the title actually changes — demonstrating the security gap is real and currently exploitable, not hypothetical.

📄 View solution
Exercise 2

Explain why revalidateTag now requires a second argument in Next.js 16, and why this chapter chose updateTag instead of revalidateTag for updatePageTitle specifically — tying the answer to what "read-your-writes" actually means for an admin who just made an edit.

📄 View solution
Exercise 3

Explain, in your own words, how a Server Action's built-in Origin-header check achieves the same protective goal as Django's {% csrf_token %}, without any code in updatePageTitle resembling a token at all.

📄 View solution

Chapter 8 Quick Reference

  • updatePageTitle — a working Server Action, deliberately missing its auth check until Chapter 9
  • cacheComponents: true + 'use cache' + cacheTag — the current model for taggable, invalidatable server-side caching
  • updateTag over revalidateTag — genuine read-your-writes for an admin who just made an edit
  • Automatic CSRF protection — a structural property of Server Actions; the one piece every sibling course had to wire up manually
  • Next chapter: Admin Authentication