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?
answer
- the page remounts on each visit
- a layout inside the page dies with it
- Page.layout set on the component
- same layout, same position, kept mounted
- React arrow layouts go in an array
basics
~20 sInertia 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 sOn 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 linesimport 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
Remember that layouts wrapped inside a page reset on every visit, and that assigning Page.layout keeps a layout alive.
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.
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.
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