React Components & Layout
Website Rebuild with Next.js
Chapter 4 · React Components & Layout
This chapter builds the real layout Chapter 3's own database-backed route renders through — a persistent site shell, a breadcrumb trail, and, like every sibling course before it, a way to render stored HTML content as real markup rather than escaped text.
Nested Layouts: A Genuine Routing Primitive
{% extends %}, Rails' own layout convention, Astro's imported <Layout> component). Next.js's app/layout.tsx is different in kind: it's wired directly into the routing tree itself — every route under app/ automatically renders inside it, with no per-page import or extends statement needed anywhere. Nested folders can each add their own layout.tsx, wrapping progressively, which is genuinely useful for a site with real hierarchical structure like this one — though this course doesn't need that depth of nesting to work correctly.
The Breadcrumb
getBreadcrumb walks current.parentId one row at a time inside a while loop — one database query per ancestor level, exactly the pattern every one of this course's own five already-complete siblings built into its own breadcrumb code on purpose. It's left as written here, to be caught and fixed in Chapter 6, once this course's own relational-query tooling is actually introduced.
Wiring It Into the Route
dangerouslySetInnerHTML: React's Own Answer
dangerouslySetInnerHTML={{ __html: page.body }} sets an element's HTML content directly, bypassing React's own default auto-escaping — the exact same job every sibling framework does in its own way.
| Framework | Mechanism |
|---|---|
| Next.js (React) | <div dangerouslySetInnerHTML={{ __html: page.body }} /> — a prop, deliberately named to signal danger |
| Django | {{ page.body|safe }} — a template filter |
| Laravel | {!! $page->body !!} — a Blade output-tag variant |
| Rails | <%== @page.body %> — an ERB output-tag variant |
| Astro | <div set:html={page.body} /> — a template directive on the element |
| Express (EJS) | <%- page.body %> — an EJS output-tag variant, same family as Rails' |
dangerouslySetInnerHTML is only safe for content this project's own admin interface writes — never for rendering arbitrary user input directly, since it bypasses the exact escaping that protects against injected markup. React's own naming choice — the only one of the six that puts a warning directly in the API's own name — is a small, genuine design signal the other five don't share.
Hands-On Exercises
Build PageShell.tsx rendering a page's title and body via dangerouslySetInnerHTML, and confirm real stored HTML (e.g. a <strong> tag inside body) renders as actual formatted HTML, not escaped text.
📄 View solutionBuild getBreadcrumb() and confirm it produces the correct, correctly-ordered ancestor chain for a real page stored at least three levels deep.
📄 View solutionExplain, in your own words, why app/layout.tsx doesn't need to be imported or extended by app/[...path]/page.tsx the way Django's {% extends %} or Astro's imported <Layout> component both require.
📄 View solutionChapter 4 Quick Reference
app/layout.tsx— a real routing primitive; every route beneath it renders inside it automatically, no import neededgetBreadcrumb— deliberately inefficient (one query per ancestor level); left unfixed until Chapter 6dangerouslySetInnerHTML— React's own answer to rendering trusted stored HTML; only ever safe for admin-written content- Next chapter: Styling — Dark Theme