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

hacku5's avatar

Vue <script setup> re-render problem

I was testing something today and I realized that in the application I'm working on, all components were being re-rendered from scratch on every page transition. For example, when I clicked on a title with dropdowns in the menu, the dropdown would disappear because it was being re-rendered on the new page.

To give an example;

// TestLayout.vue

<template>
    <input type="text">
     <slot></slot>
</template>

<script setup>
    // Some Hello worlds
</script>

// Dashboard.vue

<template>
    <TestLayout>
        <h1>Welcome to "Dashboard" Page!</h1>
        <Link href="/admin/welcome">
        Dashboard
        </Link>
    </TestLayout>
</template>

<script>
import { Link } from '@inertiajs/vue3';
import TestLayout from '@Admin/Layouts/TestLayout.vue'
export default {
  components: {
    Link
  },
  layout: TestLayout,
}
</script>

// Welcome.page

<template>
    <TestLayout>
        <h1>Welcome to "Welcome" Page!</h1>
        <Link href="/admin/">
        Dashboard
        </Link>
    </TestLayout>
</template>

<script>
  import { Link } from '@inertiajs/vue3';
  import TestLayout from '@Admin/Layouts/TestLayout.vue'
  export default {
    components: {
      Link
    },
    layout: TestLayout,
  }
  </script>

I have an <input type="text"> here in TestLayout.vue and I noticed that whenever I wrote any value inside it using script tags generated via API, the <input type="text"> value remained the same even when I changed the page.

<script setup>
import { Link } from '@inertiajs/vue3';
import TestLayout from '@Admin/Layouts/TestLayout.vue'
</script>

However, if I use <script setup>, the <input type="text"> value gets cleared on every page change.

I'm sure there is a logical explanation for this, but I'm new to developing applications with Inertia, Vue, and Laravel. There are a lot of things that are confusing me. Is it possible to do this with <script setup> instead of Vue.js setup API?

0 likes
2 replies
piljac1's avatar
piljac1
Best Answer
Level 28

Even if it might not be apparent, there's a huge difference between these two blocks of code

<script>
  import { Link } from '@inertiajs/vue3';
  import TestLayout from '@Admin/Layouts/TestLayout.vue'
  export default {
    components: {
      Link
    },
    layout: TestLayout,
  }
</script>
<script setup>
  import { Link } from '@inertiajs/vue3';
  import TestLayout from '@Admin/Layouts/TestLayout.vue'
</script>

What is the difference? The first one defines a persistent Inertia layout and the second one doesn't. In order to have a layout that isn't recreated/remounted until a full page refresh (which is what a persitent layout is), you need to define it in your Vue component's options as shown in the first code block. However, wether import { Link } from '@inertiajs/vue3'; or any other imports are defined in a regular script block or script setup block doesn't matter much in your current use case.

There are multiple of ways to work with a persistent layout:

Option 1. As the first code block of my current reply shows.

Option 2. You can use an hybrid alternative which defines the persistent layout in a regular script tag and the rest of the code in a script setup tag:

<script>
  import TestLayout from '@Admin/Layouts/TestLayout.vue';

  export default {
    layout: TestLayout,
  };
</script>

<script setup>
  import { Link } from '@inertiajs/vue3';

  // rest of the code (refs, methods, computeds, etc.).
</script>

Option 3. Install and configure the defineOptions plugin in order to define everything in a script setup tag instead of two separate tags:

<script setup>
  import { Link } from '@inertiajs/vue3';
  import TestLayout from '@Admin/Layouts/TestLayout.vue';

  defineOptions({ layout: TestLayout })

  // rest of the code (refs, methods, computeds, etc.).
</script>

Bonus. Add a default layout to your base Inertia configuration:

import TestLayout from '@Admin/Layouts/TestLayout.vue';

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true });

    const page = pages[`./Pages/${name}.vue`];

    page.default.layout = page.default.layout || TestLayout;

    return page;
  },
  // ...
})

P.S. Also, when you define a persistent layout, you don't have to include it in your template tag. So the following code:

<template>
    <TestLayout>
        <h1>Welcome to "Welcome" Page!</h1>
        <Link href="/admin/">
            Dashboard
        </Link>
    </TestLayout>
</template>

becomes this:

<template>
    <h1>Welcome to "Welcome" Page!</h1>
    <Link href="/admin/">
        Dashboard
    </Link>
</template>
1 like
hacku5's avatar

@piljac1 I followed your advice and solved my problem by using a persistent layout. I had tried to use it before, but for some reason that I can't remember now, I gave up on it. After thinking a bit, I realized that using a persistent layout was the right way to go for me. Thank you for taking the time!

Please or to participate in this conversation.