Книжная полка

Некорректные данные useRoute

Так уж вышло, что на одном из проектов (Nuxt 3) мне понадобилось получить meta-данные текущего роута внутри дефолтного шаблона, чтобы потом завязать на них логику запросов внутри хука onMounted.

Недолго думая, я пошел в default.vue и написал там примерно такое:

import { useRoute, onMounted } from '#imports'

const { meta } = useRoute()

onMounted(() => {
  if (meta.someMeta) {
    // Тут что-то делаем
  }
})

Это решение работало корректно до тех пор, пока в проекте не появился второй шаблон payment.vue.

Проблема

С появлением второго шаблона (для страницы оплаты) возник так называемый layout transition (смена макета) при переходе на страницу оплаты и обратно.

Как выяснилось позже, в таком случае useRoute из Nuxt, используемый внутри шаблона, ведет себя иначе, чем ожидалось.

Например:

// pages/index.vue
<template>
  <div>Главная страница</div>
  <NuxtLink to="/cabinet">Перейти в Личный кабинет</NuxtLink>
</template>
// pages/cabinet.vue
<template>
  <div>Личный кабинет</div>
  <NuxtLink to="/">Перейти на Главную</NuxtLink>
</template>

<script setup>
definePageMeta({
  layout: 'cabinet',
  someMeta: true
})
</script>
// layouts/default.vue
<template>
  <div class="default-layout">
    <div>DefaultLayout</div>
    <slot />
  </div>
</template>
// layouts/cabinet.vue
<template>
  <div class="cabinet-layout">
    <div>CabinetLayout</div>
    <slot />
  </div>
</template>

<script setup>
import { useRoute } from '#imports';

const { name, meta } = useRoute()

onMounted(() => {
  console.log({ name }) // При переходе на клиенте здесь будет "index"
  console.log(meta.someMeta) // А здесь undefined
})
</script>
Некорректное имя шаблона

Посмотреть демо

Если открыть демо, развернуть консоль и перейти с главной страницы в личный кабинет, то будет видно, что в консоль выводится route.name главной страницы, а не текущей (cabinet). Корректный же name прилетает, только если, находясь в личном кабинете, перезагрузить страницу.

То есть если мы используем накстовский useRoute внутри шаблонов, то при переходе по страницам, сопровождаемом сменой шаблонов, мы получим не текущий роут (как ожидалось), а тот роут, с которого мы зашли в приложение (в данном случае главная страница).

Почему так происходит, можно узнать вот здесь, здесь, здесь и здесь.

Решение

Наверно, самым правильным решением было бы вообще не использовать useRoute для логики внутри шаблона.

Однако если этот вариант не подходит, то можно сделать вот так:

<template>
  <div class="cabinet-layout">
    <div>CabinetLayout</div>
    <slot />
  </div>
</template>

<script setup>
import { useRouter } from '#imports';

const { name } = useRouter().currentRoute.value

onMounted(() => {
  console.log({ name })
})
</script>

или даже так:

<template>
  <div class="cabinet-layout">
    <div>CabinetLayout</div>
    <slot />
  </div>
</template>

<script setup>
import { useRoute } from 'vue-router';

const { name } = useRoute()

onMounted(() => {
  console.log({ name })
})
</script>

Теперь при переходе между лейаутами в хуке onMounted всегда будет доступен корректный текущий роут.

Источники

  1. https://nuxt-route-layout-issue.surge.sh
  2. https://github.com/nuxt/nuxt/issues/21340
  3. https://github.com/nuxt/nuxt/issues/28804
  4. https://github.com/nuxt/nuxt/issues/21283#issuecomment-1569963217
  5. https://github.com/nuxt/nuxt/issues/20674#issuecomment-1534739499
  6. https://nuxt.com/docs/api/composables/use-route
  7. https://router.vuejs.org/guide/advanced/composition-api