Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Dynamic route parameters should not be set using definePageMeta #1736

Closed
kazupon opened this issue Dec 15, 2022 Discussed in #1730 · 35 comments
Closed

Feature: Dynamic route parameters should not be set using definePageMeta #1736

kazupon opened this issue Dec 15, 2022 Discussed in #1730 · 35 comments

Comments

@kazupon
Copy link
Collaborator

kazupon commented Dec 15, 2022

Discussed in #1730

Originally posted by adesombergh December 13, 2022
Hi, thanks for the great work with this module.

I stumbled into a problem with i18n module v8 and nuxt3 and I think it might be interesting to change this in the future, as it makes it difficult to use with dynamic content.

Please, correct me if i'm not doing in right.

The Problem

According to the Dynamic route parameters documentation, to set translations for route params we should use definePageMeta like so:

<script setup>
definePageMeta({
  // ...
  nuxtI18n: {
    en: { id: 'my-post' },
    fr: { id: 'mon-article' }
  }
  // ...
})
</script>

However, the Nuxt 3 documentation about definePageMeta (right here) says that the page meta object cannot reference the component (or values defined on the component).

It means that the content inside definePageMeta({}) is supposed to be hard-coded and cannot be dynamic, making it impossible to set the page route params dynamically (as they often are). The following code won't work:

<script setup>
// /pages/[slug].vue
const route = useRoute()
const { locale } = useI18n()
const { data } = await useFetch(
  `https://yourapi.com/${locale}/page/${route.params.slug}`
)

definePageMeta({ 
  nuxtI18n: data.value.other_languages // Throws an error because data is undefined
})
</script>
{
  "other_languages": {
    "en": { "slug": "tech-specs"},
    "fr": { "slug": "specifications-techniques"},
  },
  ...more content ...
}

A solution

Instead of using definePageMeta, maybe i18n module could give us a composable like setParamsTranslations or whatever, that could be used like this:

<script setup>
// /pages/[slug].vue
const route = useRoute()
const { locale, setParamsTranslations } = useI18n()
const { data } = await useFetch(
  `https://yourapi.com/${locale}/page/${route.params.slug}`
)
setParamsTranslations(data.value.other_languages)
</script>

If anyone agrees I can create an issue (feature)

@kazupon kazupon closed this as completed Dec 15, 2022
Copy link
Collaborator Author

kazupon commented Dec 15, 2022

This issue was miss-taken.
We need to more discuss this issue.

@provok-me
Copy link

provok-me commented Jan 10, 2023

I have the same issue.

My dirty workaround for the time being:

<script setup>
// /pages/[slug].vue
const route = useRoute()
const { locale } = useI18n()
const { data } = await useFetch(
  `https://yourapi.com/${locale}/page/${route.params.slug}`
)

if (data?.value) {
  route.meta.nuxtI18n = data.value.other_languages;
}
</script>

@kazupon kazupon reopened this Jan 10, 2023
@kazupon kazupon added the v8 label Jan 10, 2023 — with Volta.net
@motion-work
Copy link

motion-work commented Jan 16, 2023

having the same trouble! in nuxt 2 I used this: https://i18n.nuxtjs.org/lang-switcher#dynamic-route-parameters
would be amazing to see this in the new V8

@adriaandotcom
Copy link
Contributor

Your solution works great @provok-it, but it doesn't work on SSR, right? With view-source:, I get the same URLs:

image

And with the inspector, it shows the updated URLs:

image

Is that the case with you as well?

@adriaandotcom
Copy link
Contributor

Got is working like this:

// pages/blog/[slug].vue

const { data: blog } = await useFetch(`https://yourapi.com/${locale}/page/${route.params.slug}`)

if (blog?.value) {
  route.meta.nuxtI18n = blog.value?.languages || {};
}

const head = useLocaleHead({
  identifierAttribute: "id",
  addSeoAttributes: true,
});

useHead({
  link: head.value.link,
  meta: head.value.meta,
});

Where blog.value.languages is an object like this:

{
  en: { slug: "english-slug" },
  de: { slug: "german-slug" },
  fr: { slug: "french-slug" }
}

Works both in SSR and normal mode.

@madc
Copy link

madc commented Jan 19, 2023

It seems not to work with SSG. Has anyone tried to get with working with static rendering?

@provok-me
Copy link

@adriaanvanrossum I double checked.
My version of the hack is also working with SSR 👌

@provok-me
Copy link

It seems not to work with SSG. Has anyone tried to get with working with static rendering?

@madc SSG and nuxt-i18n are not working great for me.
See #1807

@adriaandotcom
Copy link
Contributor

adriaandotcom commented Jan 23, 2023

My version of the hack is also working with SSR 👌

For me, it worked as well, until it didn't anymore :)

@BobbieGoede
Copy link
Collaborator

The workaround seems to work, but it seems you will have to set the route meta on each route change.

I made a reproduction to show that the meta links lose their translations if query parameters change and the route meta is not set again here.

@provok-me
Copy link

@BobbieGoede
This comment will probably help you:
#1807 (comment)

@BobbieGoede
Copy link
Collaborator

@provok-it
I'm not sure if I had issues with switching locales but it would make sense if that wasn't working as well. For me it was just meta tags not updating while on the 'same' page (switching categories in a search page).

I ended up with the following composable, it's not pretty but it seems to work so far.

import { resolveUnref } from "@vueuse/shared";

type UseHeadParams = Parameters<typeof useHead>;
type useLocaleHeadParams = Parameters<typeof useLocaleHead>[0];

export default ([input, options]: UseHeadParams, localeOptions: useLocaleHeadParams) => {
    const route = useRoute();
    const i18nHead = useLocaleHead(localeOptions);

    useHead(() => {
        const i = resolveUnref(input);
        return {
            ...i,
            link: [...(resolveUnref(i?.link) ?? []), ...(i18nHead.value.link ?? [])],
            meta: [...(resolveUnref(i?.meta) ?? []), ...(i18nHead.value.meta ?? [])],
        };
    }, options);

    const _i18nParams = ref<Record<string, any>>({});
    const i18nParams = computed({
        get: () => _i18nParams.value,
        set: (val: Record<string, any>) => {
            _i18nParams.value = val;
            route.meta.nuxtI18n = i18nParams.value;
        },
    });

    watch(
        () => route.fullPath,
        () => (route.meta.nuxtI18n = i18nParams.value)
    );

    return { i18nParams };
};

Usage like so

const { i18nParams } = useI18nParams(
    [() => ({ title: t('productSearchTitle') })],
    { identifierAttribute: 'id', addSeoAttributes: true }
)

const fetchProducts = async () => {
    // ... fetch products 
    // set i18nParams triggering composable to update `route.meta.nuxtI18n`
    i18nParams.value = {
        en: { slug: "english-slug" },
        de: { slug: "german-slug" },
        fr: { slug: "french-slug" }
    }
}

@mroddev
Copy link
Contributor

mroddev commented Mar 15, 2023

Got is working like this:

// pages/blog/[slug].vue

const { data: blog } = await useFetch(`https://yourapi.com/${locale}/page/${route.params.slug}`)

if (blog?.value) {
  route.meta.nuxtI18n = blog.value?.languages || {};
}

const head = useLocaleHead({
  identifierAttribute: "id",
  addSeoAttributes: true,
});

useHead({
  link: head.value.link,
  meta: head.value.meta,
});

Where blog.value.languages is an object like this:

{
  en: { slug: "english-slug" },
  de: { slug: "german-slug" },
  fr: { slug: "french-slug" }
}

Works both in SSR and normal mode.

Can't get this to work with SRR. It always renders a diferent link between the view-source and the inspector.

Even if I do something like this to try test it. It works but the view-source will have different links in the language switcher.

const route = useRoute()

const articleLangs = {
  en: {
    slug: 'some-english-slug'
  },
  pt: {
    slug: 'some-portuguese-slug'
  }
}

route.meta.nuxtI18n = articleLangs

@adriaandotcom
Copy link
Contributor

Try using useHead and see if you can make it auto update on changes.

@FabianEllenberger
Copy link

I'm experiencing the same issue. The route.meta.nuxtI18n value seems to be updated. For example when using something like this:

const { data } = await asyncData(`https://myapi.com/page`)

if (data?.value) {
  route.meta.nuxtI18n = {
    en: {
      slug: data.value?.page?.languages.en.slug
    }
  }
}

But the switchLocalePath method still uses it's initial value – pointing to the wrong slug.
Is there maybe a way to force update switchLocalePath?

@nicolaskrebs
Copy link

I'm experiencing the same issue: switchLocalePath only updates when refreshing the page. Has anyone found a solution to this?

@bitmonti
Copy link

bitmonti commented May 19, 2023

I changed Lang Switcher slightly on strategy 'prefix_except_default'

<NuxtLink @click="switchLocale(locale.code)">
    {{ locale.name }}
</NuxtLink>
const switchLocale = async (locale) => {
  setLocale(locale);
  await navigateTo(switchLocalePath(locale));
};

Hope that helps.

@FabianEllenberger
Copy link

I changed Lang Switcher slightly on strategy 'prefix_except_default'

<NuxtLink @click="switchLocale(locale.code)">
    {{ locale.name }}
</NuxtLink>
const switchLocale = async (locale) => {
  setLocale(locale);
  await navigateTo(switchLocalePath(locale));
};

Hope that helps.

This worked for me. I adjusted it a little bit to still be able to make use of :to instead of @click in NuxtLink:

<NuxtLink
      v-for="locale in locales"
      :key="locale.code"
      :to="switchLocale(locale.code)"
    >
      {{ locale.name }}
 </NuxtLink>
const switchLocalePath = useSwitchLocalePath()
const switchLocale = (locale: string) => {
  return switchLocalePath(locale)
}

nonetheless – this is a workaround and only works if route.meta is being updated manually (as explained above).

I hope we can find a solution for using definePageMeta dynamically as it is intended to be used:

definePageMeta({ 
  nuxtI18n: data.value.other_languages // Throws an error because data is undefined
})

@ManiakMill
Copy link

This solution route.meta.nuxtI18n works perfectly for me. But i have this kind of message:
Calling useRoute within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.

Please explain to me why I get this kind of message.

@provok-me
Copy link

@ManiakMill

Your router middleware's function has already access to the next and previous routes parameters since it handles a route change event.

Accessing the current route data with useRoute makes no sense because you're in between routes.

Use the "to" (first param of the middleware function) to access the current route data.

✌️

@Jimmylet
Copy link

I also face this problem. I tried several workarounds, not being able to define it dynamically doesn’t make sense. Thanks for this great plugin in all the way!

@tomsdob
Copy link

tomsdob commented Jun 28, 2023

Any updates? The hacky workarounds mentioned here are somewhat working, but the issue with definePageMeta must be solved.

@FabianEllenberger
Copy link

I gave this another try but still can't get dynamic route parameters to work properly with data that is being fetched async.

As mentioned in the docs i tried to set them via the compiler macro definePageMeta. The issue seems to be that the compiler macro is being compiled away and can't be referenced with component data, as mentioned in the nuxt docs here.

In my case i fetch some data:

<script lang="ts" setup>
const { locale } = useI18n()

const { data } = await useAsyncGql({
  operation: 'getSlugPage',
  variables: {
    site: locale, // de
    slug: 'ueber-uns',
  },
})
// ...

The data i recive includes a page with a translation:

data: {
  page: {
    slug: 'ueber-uns',
      translation: {
        slug: 'about-us'
      }
   }
}

Then i would like to use this data and set the dynamic routes on nuxtI18n with it:

definePageMeta({
    nuxtI18n: {
      de: { slug: [data.value.page.slug] }, // should resolve to ueber-uns
      en: { slug: [data.value.page.translation.slug] } // should resolve to about-us
    },
})

When trying to do so i get the error that data is not defined:

[nuxt] [request error] [unhandled] [500] data is not defined
  at ./pages/[...slug].vue:2:21
  at ViteNodeRunner.directRequest (./node_modules/vite-node/dist/client.mjs:341:11)

If definePageMeta is not intended to hold fetched component data, how should dynamic routes with fetched data be updated? Any idea how this might be possible?

Many thanks for any help!

@bitmonti
Copy link

Hi, you can add it in script blog. Try...

const looksLike= {
  de: { slug: data.value.page.slug }
}
const route = useRoute();
route.meta.nuxtI18n = looksLike || {};

@FabianEllenberger
Copy link

Hi, you can add it in script blog. Try...

const looksLike= {
  de: { slug: data.value.page.slug }
}
const route = useRoute();
route.meta.nuxtI18n = looksLike || {};

Thank you @bitmonti for the quick respond.

I have tried your suggestion and it seems to work when wrapped inside onBeforeMount similar to what i have tried here:

<script setup>
onBeforeMount(() => {
  route.meta.nuxtI18n =
    {
      de: { slug: [data.value.page.slug] }, 
      en: { slug: [data.value.page.translation.slug] },
    } || {}

What it doesn't seem to fix is the problem when prerendering the page with nuxt generate and crawlLinks: true.
I get all routes duplicated for each locale. I'm using the prefix_except_default strategy:

Prerendering 8 initial routes with crawler
  ├─ /
  ├─ /en
  ├─ /en/about-us
  ├─ /about-us
  ├─ /ueber-uns
  ├─ /en/ueber-uns
  ...

My langswitch component looks like this (similar to your approach):

<template>
  <div>
    <NuxtLink
      v-for="loc in availableLocales"
      :key="loc.code"
      :to="switchLocale(loc.code)"
    >
      {{ loc.name }}
    </NuxtLink>
  </div>
</template>

<script lang="ts" setup>
const switchLocalePath = useSwitchLocalePath()
const { locale, locales } = useI18n()

const availableLocales = computed(() =>
  locales.value.filter((loc) => loc.code !== locale.value),
)

const switchLocale = (locale: string) => {
  return switchLocalePath(locale)
}
</script>

This setup seems to works on the server using nuxi dev but not staticly with nuxi generate. It looks like the langswitch doesn't update in time and points to a wrong route or has the wrong locale... did you get this working with static generation?

@bitmonti
Copy link

Sorry, no. I'm using ssr: true crawlLinks: true.

@FabianEllenberger
Copy link

I gave this another try but still can't get dynamic route parameters to work properly with data that is being fetched async.

As mentioned in the docs i tried to set them via the compiler macro definePageMeta. The issue seems to be that the compiler macro is being compiled away and can't be referenced with component data, as mentioned in the nuxt docs here.

In my case i fetch some data:

<script lang="ts" setup>
const { locale } = useI18n()

const { data } = await useAsyncGql({
  operation: 'getSlugPage',
  variables: {
    site: locale, // de
    slug: 'ueber-uns',
  },
})
// ...

The data i recive includes a page with a translation:

data: {
  page: {
    slug: 'ueber-uns',
      translation: {
        slug: 'about-us'
      }
   }
}

Then i would like to use this data and set the dynamic routes on nuxtI18n with it:

definePageMeta({
    nuxtI18n: {
      de: { slug: [data.value.page.slug] }, // should resolve to ueber-uns
      en: { slug: [data.value.page.translation.slug] } // should resolve to about-us
    },
})

When trying to do so i get the error that data is not defined:

[nuxt] [request error] [unhandled] [500] data is not defined
  at ./pages/[...slug].vue:2:21
  at ViteNodeRunner.directRequest (./node_modules/vite-node/dist/client.mjs:341:11)

If definePageMeta is not intended to hold fetched component data, how should dynamic routes with fetched data be updated? Any idea how this might be possible?

Many thanks for any help!

My issue explained above regarding duplicate crawled dynamic routes has been fixed since version 8.0.0-rc.3 with this setup using nuxt 3.6.5 and nuxi generate:

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    strategy: 'prefix_except_default',
    defaultLocale: 'de',
    locales: [
      {
        name: 'Deutsch',
        code: 'de',
      },
      {
        name: 'English',
        code: 'en',
      },
    ],
  },
})

@bitmonti
Copy link

bitmonti commented Aug 22, 2023 via email

@FabianEllenberger
Copy link

I have tried this, but get the following error:
[nuxt] error caught during app initialization Error: Must be called at the top of a `setup` function Caused by: SyntaxError: Must be called at the top of a `setup` function

@FabianEllenberger
Copy link

Discussed in #1730

Originally posted by adesombergh December 13, 2022

Hi, thanks for the great work with this module.

I stumbled into a problem with i18n module v8 and nuxt3 and I think it might be interesting to change this in the future, as it makes it difficult to use with dynamic content.

Please, correct me if i'm not doing in right.

The Problem

According to the Dynamic route parameters documentation, to set translations for route params we should use definePageMeta like so:

<script setup>

definePageMeta({

  // ...

  nuxtI18n: {

    en: { id: 'my-post' },

    fr: { id: 'mon-article' }

  }

  // ...

})

</script>

However, the Nuxt 3 documentation about definePageMeta (right here) says that the page meta object cannot reference the component (or values defined on the component).

It means that the content inside definePageMeta({}) is supposed to be hard-coded and cannot be dynamic, making it impossible to set the page route params dynamically (as they often are). The following code won't work:

<script setup>

// /pages/[slug].vue

const route = useRoute()

const { locale } = useI18n()

const { data } = await useFetch(

  `https://yourapi.com/${locale}/page/${route.params.slug}`

)



definePageMeta({ 

  nuxtI18n: data.value.other_languages // Throws an error because data is undefined

})

</script>
{

  "other_languages": {

    "en": { "slug": "tech-specs"},

    "fr": { "slug": "specifications-techniques"},

  },

  ...more content ...

}

A solution

Instead of using definePageMeta, maybe i18n module could give us a composable like setParamsTranslations or whatever, that could be used like this:

<script setup>

// /pages/[slug].vue

const route = useRoute()

const { locale, setParamsTranslations } = useI18n()

const { data } = await useFetch(

  `https://yourapi.com/${locale}/page/${route.params.slug}`

)

setParamsTranslations(data.value.other_languages)

</script>

If anyone agrees I can create an issue (feature)

Any news on this issue, or is it beeing tracked somewhere else?

@luca-smartpricing
Copy link

Any solution that work with nuxt-simple-sitemap?

@provok-me
Copy link

Working for me @luca-smartpricing:
#1736 (comment)

@luca-smartpricing
Copy link

@provoks-land
No, sorry it doesn't work. nuxt-simple-sitemap don't set rel="alternate" also if i set the param in the middleware.

  middleware(to) {
    to.meta.nuxtI18n.en = {
      blog: 'a-simple-blog'
    }
  }

@BobbieGoede
Copy link
Collaborator

It some time but with #2580 merged and released in rc.9 I will close this issue.

Try out the new useSetI18nParams composable, more detailed example of its usage here! Internally it works in a similar way to the workarounds described here, but improves on it by updating meta tags during SSR as well.

If you run into any bugs please open a new issue with a minimal reproduction 💪

@FabianEllenberger
Copy link

It some time but with #2580 merged and released in rc.9 I will close this issue.

Try out the new useSetI18nParams composable, more detailed example of its usage here! Internally it works in a similar way to the workarounds described here, but improves on it by updating meta tags during SSR as well.

If you run into any bugs please open a new issue with a minimal reproduction 💪

Thank you @BobbieGoede - the new release solved my issues and the useSetI18nParams seems to work as expected. 👍

@nuxt-modules nuxt-modules locked as resolved and limited conversation to collaborators Jan 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests