Nuxt3 + Prismic + i18n : Changing block ordering

In my Nuxt3 project, I have a page whose Prismic blocks change order when changing languages....

The page is as follows: https://dev.adngroup.com/en/negotiation-notebooks

if I display the page in French or English via a refresh of the page (eg: F5), the blocks are displayed in the correct order. On the other hand, if I change the language via the flag at the top left, the bottom block is at the top....

Here is my code:

page file :

<script setup lang="ts">

import { components } from "~/slices";

const { locale } = useI18n();

const config = useRuntimeConfig();

const prismic = usePrismic();

const { data: page } = useAsyncData("[pagecarnetsnegociation]", () =>

prismic.client.getSingle("pagecarnetsnegociation", {

lang: locale.value === "en" ? "en-gb" : "fr-fr",

}),

);


</script>

<template>

<SliceZone wrapper="main" :slices="page?.data.slices ?? []" :components="components" />

</template>

slice file of the last block :


<script setup lang="ts">
import * as prismic from "@prismicio/client";
import { type Content } from "@prismicio/client";
const { client } = usePrismic();
const { locale } = useI18n();

// The array passed to `getSliceComponentProps` is purely optional.
// Consider it as a visual hint for you when templating your slice.
defineProps(
  getSliceComponentProps<Content.CarnetsNegociationSlice>(["slice", "index", "slices", "context"]),
);

// https://prismic.io/docs/technical-reference/prismicio-client
const {
  data: carnets,
  status,
  error,
} = await useLazyAsyncData("prismicCarnetsNegociation", async () => {
  const carnets = await client.getByType("carnetnegociation", {
    lang: locale.value === "en" ? "en-gb" : "fr-fr",
    orderings: {
      field: "my.carnetnegociation.date",
      direction: "desc",
    },
  });

  return carnets;
});
</script>

<template>
  <v-container id="containerCarnetsNegociation">
    <div v-if="error">{{ error }}</div>
    <div v-else-if="status == 'pending'" class="text-center">
      <v-progress-circular indeterminate color="primary"></v-progress-circular>
    </div>
    <div v-else-if="carnets && carnets.results.length > 0">
      <div class="d-flex flex-wrap justify-center">
        <v-card
          v-for="carnet in carnets.results"
          width="340px"
          flat=""
          rounded="0"
          :to="localePath(`/carnet-negociation/${carnet?.uid}`)"
          class="ma-2 ma-md-4 ma-lg-6 ma-xl-8"
        >
          <div class="headerCard px-4 py-10">
            <h3>{{ carnet.data.titre }}</h3>
            <div class="mt-5 ouvrir">{{ slice.primary.ouvrir_le_carnet }}</div>
          </div>
          <v-img :src="carnet.data.photo.url" width="100%" height="250px" cover></v-img>
        </v-card>
      </div>
    </div>
  </v-container>
</template>

I searched for the problem for hours but couldn't find it...

Hi @ricou ,

I've seen issues like this before in Nuxt, while I'm not sure exactly what your problem is, hopefully I can point you in the right direction.

  1. One issue/soution was routing being done incorrectly. Such as when...
const { data: event } = useAsyncData('events/[uid]', () =>   
  prismic.client.getByUID('event', route.params.uid as string) 
)

Should have been…

const { data: event } = useAsyncData(`events/${route.params.uid}`, () =>   
  prismic.client.getByUID('event', route.params.uid as string) 
)
  1. Another solution was adding a conditional v-if on the SliceZone component, so that it remounts:
// [uid].vue
<template>
  <div>
    <SliceZone
      v-if="page?.data.slices" <-- This
      wrapper="main"
      :slices="page?.data.slices"
      :components="components"
    />
  </div>
</template>
  1. Another was to use the nullish coalescing operator (??) or optional chaining (?.) to access properties on a possibly null object safely. For example, updating this line:
<PrismicImage v-if="event.data.eventDetailBannerImage.url" :field="event.data.eventDetailBannerImage" />

To be

<PrismicImage v-if="event?.data.eventDetailBannerImage?.url" :field="event?.data.eventDetailBannerImage" />
  1. Finally another user solved it by wrapping their uid in a ref and watching that ref in the useAsyncData fn. Like this.
const uid = ref(useUid());
const lang = useLang();
const customType = useCustomType();
const { client } = usePrismic();

const { data: doc, refresh } = await useAsyncData(
  uid.value,
  async () => {
    const doc = await client.getByUID(customType, uid.value, { lang });

    return doc;
  },
  {
    watch: [uid],
  }
);

Let me know if any of these suggesstions help point you in the right direction :slight_smile:

Thanks.

Thanks for your help.
However, the page is static so the proposed solutions are not suitable and for the others, I tested without success.

I reached out for some help and the issue you're facing is likely due to the asynchronous data fetching and state management in your Nuxt 3 project when changing languages via the language flag (without refreshing the page). When you switch languages, the component re-renders and the data fetching may not be synchronized properly, leading to the display order changing unexpectedly.

Here are a few steps to troubleshoot and fix this issue:

1. Ensure Proper Reactivity with useAsyncData

Nuxt's useAsyncData is designed to handle server-side rendering (SSR) and client-side data fetching, but it might not always react to changes in a reactive variable like locale. When you switch languages, you need to make sure that the data is refetched and the component properly reacts to the change.

Solution: Use watch to refetch data whenever the locale changes.

Update your page file script to use watch for locale:

<script setup lang="ts">
import { components } from "~/slices";
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRuntimeConfig, useAsyncData, usePrismic } from 'nuxt-composition-api';

const { locale } = useI18n();
const config = useRuntimeConfig();
const prismic = usePrismic();
const page = ref(null);

const fetchData = async () => {
  const { data } = await prismic.client.getSingle("pagecarnetsnegociation", {
    lang: locale.value === "en" ? "en-gb" : "fr-fr",
  });
  page.value = data;
};

await fetchData();

watch(locale, async () => {
  await fetchData();
});
</script>

<template>
  <SliceZone wrapper="main" :slices="page?.data?.slices ?? []" :components="components" />
</template>

2. Ensure Data Fetching Logic for Slices is Reactive

In your slice file, the use of useLazyAsyncData with await could be causing issues, especially since this should be reactive to changes in locale. If locale changes, the slice component should refetch the data accordingly.

Solution: Make sure the slice also refetches data on language change.

Update the slice script as follows:

<script setup lang="ts">
import * as prismic from "@prismicio/client";
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { usePrismic } from 'nuxt-composition-api';

const { client } = usePrismic();
const { locale } = useI18n();

defineProps(
  getSliceComponentProps<Content.CarnetsNegociationSlice>(["slice", "index", "slices", "context"]),
);

const carnets = ref(null);
const status = ref('idle');
const error = ref(null);

const fetchData = async () => {
  status.value = 'pending';
  try {
    const response = await client.getByType("carnetnegociation", {
      lang: locale.value === "en" ? "en-gb" : "fr-fr",
      orderings: {
        field: "my.carnetnegociation.date",
        direction: "desc",
      },
    });
    carnets.value = response;
    status.value = 'success';
  } catch (e) {
    error.value = e;
    status.value = 'error';
  }
};

await fetchData();

watch(locale, async () => {
  await fetchData();
});
</script>

<template>
  <v-container id="containerCarnetsNegociation">
    <div v-if="error">{{ error }}</div>
    <div v-else-if="status == 'pending'" class="text-center">
      <v-progress-circular indeterminate color="primary"></v-progress-circular>
    </div>
    <div v-else-if="carnets && carnets.results.length > 0">
      <div class="d-flex flex-wrap justify-center">
        <v-card
          v-for="carnet in carnets.results"
          :key="carnet.id"
          width="340px"
          flat
          rounded="0"
          :to="localePath(`/carnet-negociation/${carnet?.uid}`)"
          class="ma-2 ma-md-4 ma-lg-6 ma-xl-8"
        >
          <div class="headerCard px-4 py-10">
            <h3>{{ carnet.data.titre }}</h3>
            <div class="mt-5 ouvrir">{{ slice.primary.ouvrir_le_carnet }}</div>
          </div>
          <v-img :src="carnet.data.photo.url" width="100%" height="250px" cover></v-img>
        </v-card>
      </div>
    </div>
  </v-container>
</template>

3. Ensure Keyed Re-rendering in Vue Components

Vue relies on keyed components to understand when elements are updated. Make sure all loops, such as v-for loops, have a unique key attribute.

Solution: Ensure all v-for directives have a key:

<v-card
  v-for="carnet in carnets.results"
  :key="carnet.id"
  ...
>

4. Check if State is Properly Managed

Ensure that your state management is consistent and that no reactive data is being mutated incorrectly. In Vue, direct mutation of props or other reactive sources can lead to unexpected behaviors.

Summary

The key to solving your issue is ensuring reactivity and proper data fetching logic when switching locales. Use Vue's watch to observe locale changes and refetch data as needed, and ensure that all components are correctly keyed to prevent Vue from reusing DOM nodes inappropriately.

Nickel !!!
it works :)
Thank you very much for the time you spent helping me.
The Prismic team is always on top!

1 Like

Great to hear :slight_smile: