N
Nuxt2mo ago
Kumzy

Nuxt content / i18n / useAsyncData

Hello, upgraded from nuxt3 to nuxt4 and everything works really fine except for something regarding content/i18n My website is in EN and FR and also all the content pages: content/blog <- md in english content/fr <- md in french I use useAsyncData to fetch articles in english or french. The issue I am facing is when switching languages, it does not change the language of the article (before it was the case with nuxt3). On page load, there are no issues at all if I got the correct language set. I think is has something to do with the change on useAsyncData but did not find anything yet This is my composable for the article list
import type { BlogArticle } from "../types";

export const useBlog = () => {
const articles = useState<BlogArticle[]>("articles", () => []);
const featuredArticles = useState<BlogArticle[]>("featured", () => []);
const {
locale: { value: localCode },
} = useI18n();

let collection_name = "blog";
if (localCode === "fr") {
collection_name = "blogfr";
}
async function fetchList() {
try {
const { data } = await useAsyncData("blog-list", () => {
return queryCollection(collection_name)
.where("extension", "=", "md")
.order("date", "DESC")
.all();
});

articles.value = data.value;
featuredArticles.value = articles.value
?.filter((obj) => obj.featured)
.slice(0, 1);
} catch (e) {
articles.value = [];
featuredArticles.value = [];
return e;
}
}

return {
articles,
featuredArticles,
fetchList,
};
};
import type { BlogArticle } from "../types";

export const useBlog = () => {
const articles = useState<BlogArticle[]>("articles", () => []);
const featuredArticles = useState<BlogArticle[]>("featured", () => []);
const {
locale: { value: localCode },
} = useI18n();

let collection_name = "blog";
if (localCode === "fr") {
collection_name = "blogfr";
}
async function fetchList() {
try {
const { data } = await useAsyncData("blog-list", () => {
return queryCollection(collection_name)
.where("extension", "=", "md")
.order("date", "DESC")
.all();
});

articles.value = data.value;
featuredArticles.value = articles.value
?.filter((obj) => obj.featured)
.slice(0, 1);
} catch (e) {
articles.value = [];
featuredArticles.value = [];
return e;
}
}

return {
articles,
featuredArticles,
fetchList,
};
};
9 Replies
kapa.ai
kapa.ai2mo ago
Hi @Kumzy. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Your issue is likely related to how useAsyncData now handles data fetching and keying in Nuxt 4. In Nuxt 4, useAsyncData and useFetch share data by key across the app, and if you use a static key (like "blog-list"), the data will not be refetched when the language changes, because Nuxt thinks it's the same data. This is a change from Nuxt 3, where the behavior was less strict. Solution:
You need to make the key for useAsyncData reactive to the current locale. This way, when the locale changes, the key changes, and Nuxt will refetch the data for the new language. Here's how you can update your composable:
import { useI18n } from 'vue-i18n'
import type { BlogArticle } from "../types";

export const useBlog = () => {
const articles = useState<BlogArticle[]>("articles", () => []);
const featuredArticles = useState<BlogArticle[]>("featured", () => []);
const { locale } = useI18n();

function getCollectionName() {
return locale.value === "fr" ? "blogfr" : "blog";
}

async function fetchList() {
try {
const { data } = await useAsyncData(
() => `blog-list-${locale.value}`, // Make the key reactive
() => queryCollection(getCollectionName())
.where("extension", "=", "md")
.order("date", "DESC")
.all(),
{
watch: [locale], // Optionally, watch the locale for changes
}
);

articles.value = data.value;
featuredArticles.value = articles.value
?.filter((obj) => obj.featured)
.slice(0, 1);
} catch (e) {
articles.value = [];
featuredArticles.value = [];
return e;
}
}

return {
articles,
featuredArticles,
fetchList,
};
};
import { useI18n } from 'vue-i18n'
import type { BlogArticle } from "../types";

export const useBlog = () => {
const articles = useState<BlogArticle[]>("articles", () => []);
const featuredArticles = useState<BlogArticle[]>("featured", () => []);
const { locale } = useI18n();

function getCollectionName() {
return locale.value === "fr" ? "blogfr" : "blog";
}

async function fetchList() {
try {
const { data } = await useAsyncData(
() => `blog-list-${locale.value}`, // Make the key reactive
() => queryCollection(getCollectionName())
.where("extension", "=", "md")
.order("date", "DESC")
.all(),
{
watch: [locale], // Optionally, watch the locale for changes
}
);

articles.value = data.value;
featuredArticles.value = articles.value
?.filter((obj) => obj.featured)
.slice(0, 1);
} catch (e) {
articles.value = [];
featuredArticles.value = [];
return e;
}
}

return {
articles,
featuredArticles,
fetchList,
};
};
Key points: - Use a key that includes the current locale (e.g., `blog-list-${locale.value}`). - Optionally, use the watch option to automatically refetch when the locale changes. This approach aligns with the new Nuxt 4 data fetching system, which requires unique keys for different data sets and will ensure your articles update when the language changes. For more details, see the Nuxt 4 upgrade guide on singleton data fetching and key change behavior. If you use a static key, Nuxt will not refetch the data when the locale changes, which matches the issue you're seeing. Making the key reactive to the locale is the recommended fix in Nuxt 4. If you need more details on how keys work in useAsyncData, see the official documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@238709222005669888> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kumzy
KumzyOP2mo ago
@kapa.ai How to do the same for this [slug].vue file?
<script setup lang="ts">

const {
locale: { value: localCode },
} = useI18n();

const route = useRoute();
const slug = useRoute().params.slug;

let collection_name = "blog";
if (localCode === "fr") {
collection_name = "blogfr";
}

// @ts-ignore - Ignoring type error for collection_name
const { data: article } = await useAsyncData(`blog-${slug}`, () => {
return queryCollection(collection_name).path(route.path).first();
});

if (!article.value) {
throw createError({
statusCode: 404,
statusMessage: "Article not found",
fatal: true,
});
}


// @ts-ignore - Ignoring type error for collection_name
const surround = await queryCollectionItemSurroundings(
collection_name,
route.path,
{
fields: ["title", "description", "navigation"],
},
);

const title = article.value.title || article.value.title;
const description = article.value.description_seo || article.value.description;
useSeoMeta({
titleTemplate: "%s",
title,
description,
ogDescription: description,
ogTitle: `${title} · Laby Blog`,
});
defineOgImageComponent("og", {
title: title,
headline: "Blog",
description: "",
});
</script>
<script setup lang="ts">

const {
locale: { value: localCode },
} = useI18n();

const route = useRoute();
const slug = useRoute().params.slug;

let collection_name = "blog";
if (localCode === "fr") {
collection_name = "blogfr";
}

// @ts-ignore - Ignoring type error for collection_name
const { data: article } = await useAsyncData(`blog-${slug}`, () => {
return queryCollection(collection_name).path(route.path).first();
});

if (!article.value) {
throw createError({
statusCode: 404,
statusMessage: "Article not found",
fatal: true,
});
}


// @ts-ignore - Ignoring type error for collection_name
const surround = await queryCollectionItemSurroundings(
collection_name,
route.path,
{
fields: ["title", "description", "navigation"],
},
);

const title = article.value.title || article.value.title;
const description = article.value.description_seo || article.value.description;
useSeoMeta({
titleTemplate: "%s",
title,
description,
ogDescription: description,
ogTitle: `${title} · Laby Blog`,
});
defineOgImageComponent("og", {
title: title,
headline: "Blog",
description: "",
});
</script>
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To ensure your [slug].vue page fetches the correct article when the language changes in Nuxt 4, you need to make the useAsyncData key reactive to both the slug and the current locale. This way, when either the slug or the locale changes, Nuxt will refetch the data. You can achieve this by using a computed key that combines both values, as shown in the official documentation for reactive keys:
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'

const { locale } = useI18n()
const route = useRoute()
const slug = computed(() => route.params.slug)
const collectionName = computed(() => locale.value === 'fr' ? 'blogfr' : 'blog')

// Make the key reactive to both slug and locale
const asyncDataKey = computed(() => `blog-${slug.value}-${locale.value}`)

const { data: article } = await useAsyncData(
asyncDataKey,
() => queryCollection(collectionName.value).path(route.path).first()
)

if (!article.value) {
throw createError({
statusCode: 404,
statusMessage: "Article not found",
fatal: true,
})
}

// Fetch surroundings as needed (not reactive to locale here, but you can adapt similarly)
const surround = await queryCollectionItemSurroundings(
collectionName.value,
route.path,
{
fields: ["title", "description", "navigation"],
},
)

const title = article.value.title || article.value.title
const description = article.value.description_seo || article.value.description
useSeoMeta({
titleTemplate: "%s",
title,
description,
ogDescription: description,
ogTitle: `${title} · Laby Blog`,
})
defineOgImageComponent("og", {
title: title,
headline: "Blog",
description: "",
})
</script>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'

const { locale } = useI18n()
const route = useRoute()
const slug = computed(() => route.params.slug)
const collectionName = computed(() => locale.value === 'fr' ? 'blogfr' : 'blog')

// Make the key reactive to both slug and locale
const asyncDataKey = computed(() => `blog-${slug.value}-${locale.value}`)

const { data: article } = await useAsyncData(
asyncDataKey,
() => queryCollection(collectionName.value).path(route.path).first()
)

if (!article.value) {
throw createError({
statusCode: 404,
statusMessage: "Article not found",
fatal: true,
})
}

// Fetch surroundings as needed (not reactive to locale here, but you can adapt similarly)
const surround = await queryCollectionItemSurroundings(
collectionName.value,
route.path,
{
fields: ["title", "description", "navigation"],
},
)

const title = article.value.title || article.value.title
const description = article.value.description_seo || article.value.description
useSeoMeta({
titleTemplate: "%s",
title,
description,
ogDescription: description,
ogTitle: `${title} · Laby Blog`,
})
defineOgImageComponent("og", {
title: title,
headline: "Blog",
description: "",
})
</script>
Key points: - Use a computed key that includes both slug and locale.value. - This ensures Nuxt will refetch the article when the language or slug changes, matching the new Nuxt 4 data fetching behavior. If you only use a static key (e.g., blog-${slug}), Nuxt will not refetch when the locale changes, which is the issue you described. Making the key reactive to both values is the recommended solution in Nuxt 4 Docs > useAsyncData > Reactive Keys. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@238709222005669888> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kumzy
KumzyOP2mo ago
@kapa.ai I do not need to add watch to useAsyncData?
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- No, you do not need to add the watch option to useAsyncData when you use a reactive key (such as a computed ref that depends on locale.value or slug). Nuxt will automatically refetch the data when the key changes. This is documented in the official Nuxt docs:
You can use a computed ref, plain ref or a getter function as the key, allowing for dynamic data fetching that automatically updates when the key changes Docs > useAsyncData > Reactive Keys.
The watch option is only needed if you want to refetch data when other reactive sources (not included in the key) change. In your case, since the key already includes both the locale and the slug, Nuxt will handle refetching for you. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@238709222005669888> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kumzy
KumzyOP2mo ago
Well it's not working on the change of locale when I am on a article, it seems when changing the locale, it detect it if I use the console.log but I go undefined right after when queriying Got it working by changing the name of useAsyncData and add watch local

Did you find this page helpful?