Fondu

Layouts

View as Markdown

A layout is the part of the page that stays the same from one page to the next: a header, a sidebar, a footer. It stays mounted while visits move between the pages it wraps, so its state (a scroll position, an open menu) is kept.

_layout.tsx

A _layout.tsx in a directory of app/pages/ wraps every page below it. Its default export is a component that renders the page as children:

// app/pages/_layout.tsx: around every page
import type { ReactNode } from 'react'

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <>
      <header>…</header>
      <main>{children}</main>
    </>
  )
}

Layouts nest with the directories, outermost first:

app/pages/
├─ _layout.tsx           # around every page
├─ index.tsx
└─ docs/
   ├─ _layout.tsx        # around the docs pages, inside the one above
   ├─ intro.mdx
   └─ routes.mdx

docs/intro is rendered inside _layout.tsx, then docs/_layout.tsx. Pages written in MDX get their layouts the same way. Read the page and its props in a layout with Inertia's usePage().

Different layouts for different parts

A page's name is where its file is, and its URL is the route's: the two are independent. So the directories of app/pages/ can follow the layouts rather than the URLs:

app/pages/
├─ marketing/
│  ├─ _layout.tsx        # the site's header and footer
│  ├─ index.tsx          # rendered by routes/index.ts at /
│  └─ legal/
│     ├─ _layout.tsx     # a narrow column, inside the marketing layout
│     ├─ terms.tsx       # rendered by routes/terms.ts at /terms
│     └─ privacy.tsx
└─ admin/
   ├─ _layout.tsx        # the dashboard's sidebar
   └─ users.tsx

A part that should replace a layout rather than sit inside it goes in a directory beside it: pages/legal/ instead of pages/marketing/legal/.

Code-split with their pages

A layout is in the JavaScript of its pages only: a visitor of the home page doesn't download the docs' sidebar. Fondu does it at build time: each page imports the _layout.tsx files above it and gives them to Inertia as the page's layout.

The app's default layout

createInertiaApp({ layout }) in app/app.tsx is Inertia's default layout: Fondu leaves it to the pages with no _layout.tsx above them. With an app/pages/_layout.tsx, every page has one, so the default is not needed.

A page can still set its own, as Inertia allows: Page.layout = OtherLayout replaces its directories' layouts.

Files that are not pages

A file in app/pages/ whose name starts with _ is not a page: render() can't name it, and it is not in the generated types. Besides _layout.tsx, a component used by the pages of one directory can live there too, as _toc.tsx.