跪拜 Guibai
← All articles
Next.js · React · App Router · TypeScript · Shadcn-ui

App Router Content Sites Need One Data Source Driving Routes, Metadata, and 404s

By BreezeJiang ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most App Router tutorials teach layout, params, and metadata as separate features. This structure shows they are one pipeline: a single data source that determines whether a page exists, what it contains, and how it's described, with the framework handling the failure branch. A developer who wires these together avoids drift between route configs and content.

Summary

A small Next.js 16.3.1 project shows how five App Router APIs — layout, dynamic [slug], generateStaticParams, generateMetadata, and notFound — answer one question together: how content gets discovered, located, described, and handled when missing. The key is a single blogPosts array that feeds the list page, detail lookups, build-time static paths, and dynamic page titles, so adding an article never touches multiple route configs. When a slug returns nothing, the page calls notFound(), which re-enters the routing system and lands on a root-level not-found.tsx with recovery links. The project also uses shadcn/ui Button with Base UI's render prop to keep link semantics intact instead of nesting a button inside a link.

Takeaways
List, detail, static params, and metadata all read from the same blogPosts array; adding an article is a single data insertion.
generateStaticParams returns slugs from the data source, not a hand-maintained list.
generateMetadata reads the same getBlogPost(slug) function as the page component, so titles never fall out of sync.
When getBlogPost returns undefined, calling notFound() hands control to the root not-found.tsx rather than rendering a blank or null page.
shadcn/ui link buttons use Base UI's render prop to output a Next.js Link or anchor element directly, preserving correct HTML semantics.
Next.js 16 types params as Promise<{ slug: string }>; local types and installed-version docs take priority over older tutorials.
A self-check checklist covers route structure, data consistency, framework boundaries, and interaction semantics for any content site.
Conclusions

Framing layout, dynamic routes, static params, metadata, and notFound as a single pipeline rather than five separate APIs changes how a developer approaches a content site: the question becomes 'what is the source of truth?' rather than 'which file do I create next?'

The project's getBlogPost function is a deliberate boundary. Swapping the static array for a CMS or database only changes that function's internals; every consumer — list, detail, metadata — stays untouched, which is a practical separation that many real projects miss.

Using notFound() instead of returning null is a semantic choice with framework-level consequences: it triggers the correct HTTP status code and lets the root 404 page provide recovery paths, whereas a null render leaves the user stranded with no explanation.

Concepts & terms
generateStaticParams
A Next.js App Router function exported from a dynamic route page that tells the framework which paths to pre-render at build time. It returns an array of param objects, typically derived from a data source.
generateMetadata
An async function exported from a page or layout that returns a Metadata object (title, description, etc.). It receives the same params as the page, so it can look up dynamic content to set per-page SEO tags.
notFound()
A function imported from next/navigation that triggers the nearest not-found.tsx boundary. Calling it inside a page component signals to the framework that the requested resource does not exist, producing a proper 404 response.
Base UI render prop
A pattern in the Base UI library (which shadcn/ui builds on) that lets a component render as a different element or custom component. A Button with render={<Link href='/blog' />} outputs a Next.js Link styled as a button without nesting a button inside a link.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗