Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

CamKem's avatar
Level 10

Nested Persistant Layouts in Vue / Inertia

How can I have nested layouts in Vue / Inertia? I have a Persistent layout file that is defined in my app.js file like so:

import './bootstrap';
import '../css/app.css';

import {createApp, h} from 'vue';
import {createInertiaApp, Link} from '@inertiajs/vue3';
import {resolvePageComponent} from 'laravel-vite-plugin/inertia-helpers';
import {ZiggyVue} from '../../vendor/tightenco/ziggy/dist/vue.m';
import Layout from "@/Layouts/Layout.vue";
import AppHead from "@/Layouts/AppHead.vue";

const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Aurified';

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: async (name) => {
        // Resolve the page component asynchronously
        const page = await resolvePageComponent(
            `./Pages/${name}.vue`,
            import.meta.glob('./Pages/**/*.vue')
        );
        // Add the layout to the page component
        page.default.layout ??= Layout;
        // Return the page component
        return page;
    },
 plugin:
    setup({el, App, props, plugin}) {
        return createApp({render: () => h(App, props)})
            .use(plugin)
            .use(ZiggyVue, Ziggy)
            .component('Link', Link)
            .component('AppHead', AppHead)
            .mount(el)
            .$nextTick(() => {
                delete el.dataset.page
            })
    },
}).then();

I want to keep as the main persistent layout for the entire website, however when I define another layout for the Forums sections, which I want to be a nested layout for the each view in the Forum section. However when I define it like this below, it overwrites the main layout defined in app.js. I want to keep the main layout & just add this additional forum layout as a nested layout...

<template>
        <div class="container">

            <!-- Search Bar -->
            <DiscussionSearchBar/>

            <!-- Conversations List -->
            <div v-if="conversations.data.length > 0" class="conversation-list">
                <MainThreadCard
                    v-for="conversation in conversations.data"
                    :key="conversation.slug"
                    :conversation="conversation"
                />
            </div>
            <div v-else class="flex min-h-screen justify-center rounded-xl border border-slate-800 bg-slate-700 py-12 text-center">
                <p class="text-xl text-gray-500 pt-54">No conversations found.</p>
            </div>

            <!-- Pagination links -->
            <PaginationLinks :links="conversations.links" :meta="conversations.meta"/>

        </div>
</template>

<script>
import ForumLayout from "@/Layouts/Discussion/ForumLayout.vue";
export default { layout: ForumLayout };
</script>

<script setup>
import MainThreadCard from "@/Components/Discussion/MainThreadCard.vue";
import PaginationLinks from "@/Components/PaginationLinks.vue";
import DiscussionSearchBar from "@/Layouts/Discussion/ForumSearchBar.vue";

let props = defineProps({
    conversations: Object,
})
</script>

Please help!

0 likes
3 replies
ramonrietdijk's avatar

You can add ForumLayout as it's own element in your template, there is no need to override the layout variable if it's nested.

<template>
    <ForumLayout>
        <div class="container">
            ...
        </div>
    </ForumLayout>
</template>

As long as you have a <slot></slot> defined in your template file, you should be good.

CamKem's avatar
Level 10

@ramonrietdijk It works like that but its not entirely correct, it won't nest the layout correctly, they will appear as Child > ForumLayout, not ForumLayout > Child.

I have however found a (messy) workaround.

CamKem's avatar
CamKem
OP
Best Answer
Level 10

Anyone that has this issue with Nested Layouts in Vue / Inertia, this is what needs to be done:

In vite.config.js add the DefineOptions plug.

// install the npm package - npm i -D unplugin-vue-define-options @vue-macros/volar
import {defineConfig} from 'vite';
import DefineOptions from 'unplugin-vue-define-options/vite'
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        // notice the DefineOptions() plugin here:
        DefineOptions(),
        laravel({
            input: 'resources/js/app.js',
            ssr: 'resources/js/ssr.js',
            refresh: true,
            valetTls: 'demo.test',
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    server: {
        https: true,
        host: 'demo.test',
    }
});

Then in your Vue views that use the Nested Layout, use the following code:

<script setup>
  import ForumLayout from "@/Layouts/Discussion/ForumLayout.vue";
  import Layout from "@/Layouts/Layout.vue";

  defineOptions({
      layout: [Layout, ForumLayout],
      name: "ThreadsIndex",
  });
</script>

And that it! Vue will override and persistent layout setup in the app.js CreateVueApp / CreateInertiaApp section, and place the layouts as nested & defined in the the defineOptions() macro.

You can find more information about it here: https://vue-macros.sxzz.moe/macros/define-options.html

Please or to participate in this conversation.