vue.jsnuxt.jsvue-routernuxt3.js

Route params are undefined in layouts/components in Nuxt 3


I'm having problems with route params being undefined in components/layouts in Nuxt 3. It seems like a bug but it would be such a critical bug, that I almost don't believe it can be a bug.

Consider the following files:

// pages/index.vue
<template>
    <div>
        <NuxtLink to="/test-123">Click me</NuxtLink>
    </div>
</template>
// pages/test-[id].vue
<script setup lang="ts">
definePageMeta({ layout: "test" });
const route = useRoute();
console.log("page", route.params.id);
</script>

<template>
    <div>
        {{ route.params.id }}
    </div>
</template>
// layouts/test.vue
<script setup lang="ts">
const route = useRoute();
console.log("layout", route.params.id);
</script>

<template>
    <div>
        <test></test>
        <slot></slot>
    </div>
</template>
// components/test.vue
<script setup lang="ts">
const route = useRoute();
console.log("component", route.params.id);
</script>

<template>
    <div>
        {{ route.params.id }}
    </div>
</template>

Clicking the link will give the following console output:

layout undefined       [test.vue:4:8](http://localhost:3001/_nuxt/layouts/test.vue)
component undefined    [test.vue:4:8](http://localhost:3001/_nuxt/components/test.vue)
page 123               [test-[id].vue:6:8](http://localhost:3001/_nuxt/pages/test-[id].vue)

If we navigate from / to /test-123 with a NuxtLink and print route.params.id in the layout, component and page, only the page is able to access the id. The layout and component will print undefined. If I reload the page, the component, layout and page all are able to access the route params. So the issue only occurs when navigating from one page to another.

I'm on Nuxt 3.4.2. Here is a reproduction of the issue.

Is this a bug or am I doing something wrong here?


Solution

  • I had the same problem accessing the route parameter in the layouts before. I am unsure if this is related to the NuxtLink component.

    This is my understanding of why it is doing like this. It is because I think the NuxtLayout runs first before the NuxtPage🤔. The same thing for the component that is inside the NuxtLayout. But, if you try to place the test component inside the page, it will display the parameter id.

    To fix this, use the middleware. As stated on the Nuxt documentation, middleware is ideal for extracting code that you want to run before navigating to a particular route.

    ~middlware/check-auth.ts

    export default defineNuxtRouteMiddleware((to) => {
        console.log('Middleware', to.params.id)
    })

    With this, when you try to navigate to test-[id] page it will display the parameter id.

    If you want to use the displayed parameter id from the middleware, you can use the useState composable, like this.

    ~middlware/check-auth.ts

    export default defineNuxtRouteMiddleware((to) => {
        console.log('Middleware', to.params.id)
        useState('routeParamId', () => to.params.id)
    })

    You can use that inside the layout.

    ~layouts/test.vue

    <script setup lang="ts">
    const paramId = useState('routeParamId')
    console.log('ParamId is', paramId.value)
    </script>
    
    <template>
        <div>
            <slot></slot>
        </div>
    </template>

    In your test-[id] page. ~pages/test-[id].vue

    <script setup lang="ts">
    definePageMeta({ layout: "test", middleware: 'check-route' });
    const route = useRoute();
    </script>
    
    <template>
        <div>
            {{ route.params.id }}
        </div>
    </template>

    Tested and it works.