Keep the Vue layout between Inertia visits

The Vue starter kit already keeps one layout alive between pages. Wrapping each page in AppLayout builds a new shell every time.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/inertia-vue-persistent-layout.md
The Vue starter kit already keeps one layout alive between pages. Wrapping each page in AppLayout builds a new shell every time.
Do not add a second createInertiaApp.
Do not wrap the page as well.
Show the diff. Do not commit.

Next: Show a saved message in Vue

On this page · 8 sections
  1. What the kit already chooses
  2. Breadcrumbs are props
  3. When the template still wraps the page
  4. Where this stops
  5. Recap
  6. FAQ
  7. Next steps
  8. Sources

Click from one page to the next and your sidebar scroll jumps back to the top, an open menu snaps shut, and an audio player stops. That is a layout being rebuilt on every visit. The Vue starter kit already avoids it; here is how, and how to fix an older app.

TL;DR

  • Wrapping each page in <AppLayout> inside the template remounts the shell on every visit.
  • The kit names the layout once, in the layout callback of createInertiaApp, and Inertia keeps that instance.
  • Pass data with defineOptions({ layout: { breadcrumbs } }); that object is merged onto the default layout.

What you'll learn

  • How the starter kit picks a layout per page
  • How to pass breadcrumbs to the layout
  • How to move an old template wrapper into a persistent layout

Who it's for: Laravel + Vue developers on Inertia

Applies to: Laravel 13 · Vue starter kit · @inertiajs/vue3 3.9 · Vue 3.5

An older Vue tutorial wraps every page in <AppLayout> inside the template. Inertia destroys that component on the next visit and creates another one. The sidebar scroll jumps back to the top. The Vue starter kit does not wrap the page. It names the layout once, and Inertia keeps that instance.

Do not add a second createInertiaApp. Do not wrap the page as well. The inner shell would still remount. The title callback in that same call is the document title guide. The stack is Laravel and Vue in 2026.

What the kit already chooses#

resources/js/app.ts already passes a layout function. Leave it.

resources/js/app.tstypescript
layout: (name) => {
    switch (true) {
        case name === "Welcome":
            return null;
        case name.startsWith("auth/"):
            return AuthLayout;
        case name.startsWith("settings/"):
            return [AppLayout, SettingsLayout];
        default:
            return AppLayout;
    }
},
PageLayout
Welcomenone
auth/*AuthLayout
settings/*SettingsLayout inside AppLayout
everything elseAppLayout

A component returned from the page replaces this function for that page only. The kit pages do not return one.

AppLayout already accepts breadcrumbs. A page can pass them and still use the function above:

resources/js/pages/posts/Index.vuetypescript
defineOptions({
  layout: { breadcrumbs: [{ title: "Posts", href: "/posts" }] },
});

That object is not a component. Inertia merges it onto the default layout. The dashboard page in the kit does the same thing with its own crumbs.

Gotcha: props, not a component

Putting AppLayout in that option would replace the function in app.ts. Pass the props object only.

When the template still wraps the page#

Use this only on an app that does not already have the function above. Move the component out of the template.

Remounts every visit, then Persists:

resources/js/pages/posts/Index.vuevue
<script setup lang="ts">
import AppLayout from "@/layouts/AppLayout.vue";
</script>

<template>
  <AppLayout>
    <h1>Posts</h1>
  </AppLayout>
</template>
resources/js/pages/posts/Index.vuevue
<script setup lang="ts">
import AppLayout from "@/layouts/AppLayout.vue";

defineOptions({ layout: AppLayout });
</script>

<template>
  <h1>Posts</h1>
</template>

Delete the <AppLayout> tags around the page. The template is only the slot. On the starter kit, skip this. The function in app.ts already selected the component.

Prove the layout persists

Scroll the sidebar halfway down, then click to another page that uses AppLayout. With a persistent layout, the sidebar stays where you left it.

Where this stops#

This page does not draw the sidebar, and it does not add a layout package. Settings already nest two layouts in the array above. The logged-in user inside that shell is the setup guide.

Recap#

  • A layout in the page template is rebuilt on every visit; a layout chosen by Inertia persists.
  • The starter kit picks layouts in one layout callback; keep it.
  • Pass breadcrumbs with defineOptions({ layout: { breadcrumbs } }).
  • On older apps, move the wrapper into defineOptions({ layout: AppLayout }).

FAQ#

Why does my sidebar scroll reset on every Inertia visit?

The page wraps itself in the layout inside its template, so Inertia destroys and recreates it. Use a persistent layout instead.

Can a page use two layouts?

Yes. Return an array, like the kit does for settings: [AppLayout, SettingsLayout].

How do I pass breadcrumbs to the layout?

Use defineOptions({ layout: { breadcrumbs: [...] } }). Inertia merges that object onto the default layout as props.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.