skip to content

In Inertia 3, how do nested layout arrays, the default layout option and layout props combine to decide what wraps a page?

level: seniorimportance: should knowfreq 24%

answer

  1. Page.layout = [Outer, Inner]
  2. createInertiaApp({ layout: (name, page) => ... })
  3. per-page layout beats the default
  4. tuple [Layout, { title }] for static props
  5. setLayoutProps resets on navigation

basics

~20 s

A page's own layout wins, otherwise createInertiaApp's layout option supplies the default; an array nests layouts outer to inner, and layout props come from setLayoutProps first, then static tuple or callback props, then the layout's own defaults.

solid answer

~50 s

Inertia 3 decides a page's wrappers in two steps. **Which layouts**: the page's own `layout` if set, otherwise the `layout` option of `createInertiaApp`, a function of the page name and page object that may return `null` for pages such as `Public/*`. An array `[AppLayout, LibraryLayout]` nests outer to inner, and a named object `{ app: AppLayout, content: LibraryLayout }` does the same with names. **Which props**: a tuple `[AppLayout, { title: 'Library' }]` passes static props, a callback `(props) => [AppLayout, { title: props.album.title }]` derives them from page props, and `setLayoutProps({ showQueue: false })` sets dynamic ones from inside a page. Priority is dynamic, then static or callback, then the layout's parameter defaults; dynamic props reset on each navigation unless `preserveState` is on. Persistence still follows position: a layout stays mounted only while the next page renders the same component at the same depth.

code

jsx · 8 lines
jsx
import { createInertiaApp } from '@inertiajs/react'
import AppLayout from '@/layouts/AppLayout'

createInertiaApp({
  pages: './pages',
  // Public landing pages get no player; everything else does.
  layout: (name) => (name.startsWith('Public/') ? null : AppLayout),
})

go deeper

for a junior

Recall that a layout array nests layouts and that createInertiaApp can give every page a default layout.

for a middle

Explain precedence between page and default layouts, the tuple, callback and named forms, and the three-level layout prop priority.

for a senior

Structure layout hierarchies so long-lived UI keeps its depth across sections, and use layout props instead of duplicate layouts for per-page variation.

for a principal

Define the layout architecture of a large app, balancing a single default layout against section layouts and the cost of props flowing upward from pages.

## Choosing the layouts For every page, **Inertia**'s adapter decides which layout components wrap it: 1. If the page component declares `layout`, that wins. 2. Otherwise the **default layout** from `createInertiaApp({ layout })` applies. It can be a function receiving the page name and page object. 3. If neither applies, the page renders bare. A default-layout function is how a music app keeps its player around every page except the public landing pages: - `layout: (name) => name.startsWith('Public/') ? null : AppLayout` The `layout` option is new in Inertia 3; earlier apps achieved the same by mutating `page.default.layout` inside `resolve`, which still works. ## Nesting Layouts can be combined: | Form | Meaning | |---|---| | `Page.layout = AppLayout` | one layout | | `Page.layout = [AppLayout, LibraryLayout]` | nested, first is outermost | | `Page.layout = { app: AppLayout, content: LibraryLayout }` | nested, with names for targeting props | | `Page.layout = [AppLayout, { title: 'Library' }]` | one layout with static props (a tuple) | | `Page.layout = [[AppLayout, { theme: 'dark' }], [LibraryLayout, { padding: 'sm' }]]` | nested, each with static props | The renderer builds these from the outside in, placing the page innermost. The framework then reconciles by **component type and position**: moving from `[AppLayout, LibraryLayout]` to `[AppLayout, SettingsLayout]` keeps `AppLayout` (and its playing audio) but replaces the inner layout. ## Layout props Persistent layouts often need per-page data such as a heading, the active navigation item or whether to show the play queue. Inertia 3 provides **layout props** with three sources: 1. **Dynamic props** from `setLayoutProps({ showQueue: false })`, called inside a page, or `setLayoutProps('content', { padding: 'lg' })` to target a named layout; 2. **Static props** from the tuple form, or from a **callback** such as `Album.layout = (props) => [AppLayout, { title: props.album.title }]`, which receives the page props; 3. **Defaults** declared as parameter defaults on the layout component, for example `function AppLayout({ title = 'Tunes', showQueue = true, children })`. Higher numbers lose: dynamic beats static, static beats defaults. When a default layout is configured, a page's callback may return just a props object, `Album.layout = (props) => ({ title: props.album.title })`, and Inertia applies it to the default layout. ## Reset behaviour Dynamic layout props are **reset on every navigation** unless the visit preserves state, so each page starts clean and only what it sets applies. `resetLayoutProps()` clears them manually. Static and callback props are recomputed from each page's declaration. ## How this interacts with persistence Layout props change what a layout **renders**, not whether it **remounts**. Passing a new `title` to `AppLayout` re-renders it with the same instance, so the player continues. Choosing a different layout component does remount. ## Choosing the right mechanism | Need | Mechanism | |---|---| | the same shell on almost every page | the `layout` option of `createInertiaApp` | | a section with an extra sidebar | a nested array, keeping the shared layout outermost | | a heading or flag that follows page data | a layout callback returning static props | | a value a page decides while it runs | `setLayoutProps` | | a page that must render without the shell | return `null` from the default-layout function for it | ## Common mistakes - expecting a per-page layout to merge with the default; the page's own layout replaces it; - calling `setLayoutProps` on one page and being surprised it vanished after the next visit; - reordering nested arrays between sections, which moves `AppLayout` to another depth and remounts it; - passing per-page data by rendering a second copy of the layout inside the page.

  • Why did a sidebar flag set with setLayoutProps disappear after clicking a link?
    Dynamic layout props reset on every navigation that does not preserve state, so the next page starts from its static props and the layout's defaults. Set the flag on each page that needs it, or make it a static prop in that page's layout definition.
  • Does a page's own layout merge with the default layout?
    No. A per-page layout takes precedence over the default. The exception is a layout callback or plain object that returns only props: with a default layout configured, those props are applied to the default layout.

saying these in an interview costs you the question

  • Static tuple props override values set with setLayoutProps
  • Dynamic layout props persist across every later visit
  • A per-page layout is nested inside the default layout
  • Changing a layout prop remounts the layout
  • Nested layout arrays list the innermost layout first