跪拜 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 ↗