Catch-All Routing with Next.js
Website Rebuild with Next.js
Chapter 3 · Catch-All Routing with Next.js
Every sibling course's own routing chapter cites this one by the same shape: one catch-all route, one lookup by path, resolved fresh per request, with no pre-declared list of valid URLs anywhere. This chapter builds that shape directly, using the exact mandatory async params pattern Chapter 1 already flagged as firmer than ever in Next.js 16.
Generating Type Helpers First
This generates the globally-available PageProps/LayoutProps/RouteContext type helpers, used below with zero import needed — they're ambient types, resolved automatically from the project's own route structure.
The Catch-All Route
[...path].astro gives Astro.params.path as a single, already-slash-joined string. Next.js's [...path] gives params.path as a genuine string[] — one array element per URL segment. path.join('/') reconstructs the full path string this course's own fullPath column expects. Forgetting this and querying against the raw array directly is a real, easy mistake — Prisma won't error, it'll just never match any row.
Not Found: No Manual Status Code Needed
Astro.response.status = 404 and Astro.rewrite('/404') together — one for the status code, one for the content — because its catch-all otherwise shadows 404.astro entirely. Next.js's notFound() does both jobs by itself: calling it renders the nearest not-found.tsx boundary and sets a real 404 HTTP status automatically. One function call, not two separate pieces of manual bookkeeping.
Routing, Compared Across the Whole Series
| Next.js | Django | Laravel | Rails | Astro | Express | |
|---|---|---|---|---|---|---|
| Mechanism | [...path] folder | <path:full_path> | {path?} + regex | *path glob | [...path].astro | /*splat (Express 5) |
| Path arrives as | A string[] array — needs .join('/') | A single string | A single string | A single string | A single, already-joined string | A single string |
| Resolved by | A database lookup | A database lookup | A database lookup | A database lookup | A database lookup | A database lookup |
| Not-found handling | One call: notFound() | Manual Http404 raise | Manual abort(404) | Manual render status: :not_found | Two pieces: status + rewrite | Manual res.status(404) |
The mechanism differs in shape everywhere, but the underlying architecture — a database lookup standing in for a pre-declared path list — is genuinely identical across all six frameworks. Only Next.js's own array-vs-string parameter shape and its single-call notFound() stand out as real, structural differences rather than syntax variation.
Hands-On Exercises
Build app/[...path]/page.tsx with a real Prisma query resolving the joined params.path against the fullPath column, and confirm a real stored page renders correctly at its own full path.
📄 View solutionDeliberately query against the raw params.path array instead of the joined string, and observe exactly what happens — no error, just a page that never matches. Explain why Prisma doesn't raise a type error here even though the query is wrong.
📄 View solutionVisit a genuinely nonexistent path with app/not-found.tsx already in place, and confirm both the correct page content renders and the real HTTP response carries a 404 status code — with only the single notFound() call handling both.
📄 View solutionChapter 3 Quick Reference
npx next typegen— generates the ambientPageProps/LayoutPropshelpers used with zero importapp/[...path]/page.tsx— the catch-all route;params.patharrives as astring[], joined with.join('/')- No pre-declared path list — every request resolves fresh, via a real database lookup
notFound()+app/not-found.tsx— one call handles both the correct content and a real 404 status, unlike Astro's own two-piece combination- Next chapter: React Components & Layout