skip to content

Client Adapters & Layouts

createInertiaApp boots a React, Vue or Svelte adapter, resolves page components and wraps them in persistent layouts that survive visits. Interviewers probe layout state and page splitting.

on this pageshow

explore

questions

5

Why does an Inertia music app's sidebar player restart on every visit when pages wrap themselves in <Layout>, and how do persistent layouts fix it?

level: middleimportance: must knowfreq 48%

answer

  1. the page remounts on each visit
  2. a layout inside the page dies with it
  3. Page.layout set on the component
  4. same layout, same position, kept mounted
  5. React arrow layouts go in an array

basics

~20 s

Inertia remounts the page component on each visit, so a layout rendered inside the page is destroyed with it; declaring the layout on the component (Page.layout) lets Inertia render it outside the page, so the same layout instance and its player state survive.

solid answer

~50 s

On a normal visit Inertia swaps in the new page component with a fresh key, so everything the page renders, including a `<Layout>` it wraps itself in, unmounts and mounts again: the `<audio>` element is recreated and playback stops. A **persistent layout** is declared on the page component instead: `Page.layout = (page) => <Layout>{page}</Layout>` or `Page.layout = [Layout]` in React, `defineOptions({ layout: Layout })` in Vue, `export { default as layout }` from a `<script module>` block in Svelte. Inertia then renders the layout around the page itself. When the next page names the same layout, the framework sees the same component at the same position and keeps the instance, so its state, the audio element and the sidebar's scroll position survive while only the page inside changes. In Inertia 3's React adapter, an arrow-function layout component must be wrapped in an array.

code

jsx · 14 lines
jsx
import AppLayout from '@/layouts/AppLayout'

export default function Album({ album }) {
  return (
    <>
      <h1>{album.title}</h1>
      {/* track list */}
    </>
  )
}

// Inertia renders AppLayout around the page and keeps it mounted
// across visits to other pages that use the same layout.
Album.layout = (page) => <AppLayout>{page}</AppLayout>

go deeper

for a junior

Remember that layouts wrapped inside a page reset on every visit, and that assigning Page.layout keeps a layout alive.

for a middle

Explain the page key swap, why the framework keeps a same-type component at the same position, and the per-adapter syntax for declaring a layout.

for a senior

Design layout hierarchies so long-lived UI like a player sits in a layout every relevant page shares, and spot the v3 arrow-function trap in reviews.

for a principal

Decide what state belongs in persistent layouts versus a global store, weighing coupling of layouts to pages against resilience across full reloads.

## The symptom A music app has a sidebar with an **audio player**: the user starts a track, then browses albums and artists. Each page component renders its content inside `<AppLayout>`, and the player lives in `AppLayout`. Every click on a `<Link>` restarts the player from silence, and the sidebar's scroll position jumps back to the top. ## Why it happens When an **Inertia** visit completes, the adapter swaps in the new page component. Unless the visit preserves state, the page element gets a **new key**, so React (or Vue, or Svelte) treats it as a new component instance: 1. the old page unmounts, taking its whole subtree with it; 2. that subtree includes the `<AppLayout>` the page rendered itself; 3. the new page mounts a brand-new `<AppLayout>`, with a new `<audio>` element and fresh state. Nothing is wrong with the layout; it is simply owned by the page, so it lives and dies with the page. ## Persistent layouts A **persistent layout** is declared as a static property of the page component rather than rendered by it. Inertia reads that property and renders the layout **around** the page: | Adapter | Declaration | |---|---| | React | `Album.layout = (page) => <AppLayout>{page}</AppLayout>`, `Album.layout = AppLayout` for a function declaration, or `[AppLayout]` | | Vue 3 | `defineOptions({ layout: AppLayout })` in `<script setup>`, or a `layout` component option | | Svelte 5 | `<script module>export { default as layout } from './AppLayout.svelte'</script>` | Now the render tree is `AppLayout > Album` on one page and `AppLayout > Artist` on the next. Only the page element carries the changing key. The UI framework compares the trees, sees the **same layout component at the same position**, and keeps its instance. The layout's state, effects and DOM survive; the page inside it is replaced. ## What survives and what does not - **Survives**: local state in the layout, the playing `<audio>` element, open or closed sidebar sections, scroll position of the sidebar. - **Replaced**: the page component and everything it renders. - **Still updates**: data the layout reads from the page object, such as the signed-in user from shared props, because the layout re-renders with the new page. If the next page names a **different** layout component, the old layout unmounts, so the player must live in a layout every music page shares. ## The Inertia 3 React arrow-function trap The React adapter must tell a layout component from a render function such as `(page) => <AppLayout>{page}</AppLayout>`. At runtime an arrow-function component looks like a render function, so Inertia 3 no longer accepts it assigned directly: - `Album.layout = ArrowLayout` does **not** work when `ArrowLayout` is an arrow function component; - `Album.layout = [ArrowLayout]` works; - a `function AppLayout({ children })` declaration works either way. ## Avoiding repetition Setting `.layout` on every page is repetitive. Inertia 3's `createInertiaApp({ layout: () => AppLayout })` applies a default layout to every page that declares none, which is the usual way to keep one player across the whole app. ## Common mistakes - wrapping pages in `<AppLayout>` in JSX and expecting its state to persist; - giving some pages `AppLayout` and others a lookalike `PlayerLayout`, which unmounts the player on crossing; - assigning an arrow-function component directly to `.layout` in React on Inertia 3; - storing the current track only in page state, which is lost on every visit.

  • Does preserveState on a visit keep a non-persistent layout alive?
    Yes for that visit, because with `preserveState` the page keeps its key and is not remounted, so nothing inside it resets. It is a per-visit option, though, and ordinary link clicks do not use it; persistent layouts keep the player alive on every visit.
  • How would you keep the player alive when one section uses a different layout?
    Put the player in an outer layout shared by every section and nest section layouts inside it, for example `[AppLayout, AdminLayout]`. The outer layout stays at the same position across sections and survives, while the inner one changes.

A persistent layout is like a theatre's orchestra pit during a scene change: the stage crew swaps the scenery (the page) between scenes, but the band keeps playing because it was never part of the set. Wrapping the layout inside the page is like seating the band on the set itself, so it leaves with every scene.

saying these in an interview costs you the question

  • Inertia keeps the previous page mounted between visits
  • Wrapping the page in <Layout> already makes the layout persistent
  • A persistent layout stops receiving updated shared props
  • Any layout persists, even when the next page names another one
  • An arrow-function component can be assigned to .layout directly in Inertia 3 React
open as a page

How does a persistent Inertia layout read the signed-in user from shared props with usePage(), and what does usePage() return?

level: juniorimportance: should knowfreq 42%

basics

~10 s

usePage() returns the current Inertia page object, whose props include shared props such as auth.user from HandleInertiaRequests::share(), so a layout reads usePage().props.auth.user and re-renders whenever a visit brings a new page.

open as a page

In Inertia 3, what does createInertiaApp do, and how do its pages, resolve, setup and withApp options differ?

level: middleimportance: should knowfreq 38%

basics

~20 s

createInertiaApp reads the initial page object from the root element, resolves its component by name, and mounts or hydrates the adapter's App; pages or resolve decide how components load, setup takes over mounting, and withApp wraps the app in providers.

open as a page

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%

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.

open as a page

How does Inertia 3 split page components into separate chunks, and when would you set pages.lazy to false instead?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

By default Inertia 3 lazy-loads page components, so Vite builds one chunk per page that downloads on its first visit; setting pages.lazy to false, or import.meta.glob with eager: true, bundles every page up front, trading a bigger first download for no per-page fetches.

open as a page