我在我的项目中使用 Nuxt 3 + Pinia + FirebaseStore,当我通过 AsyncData 函数包装来自 Firebese 的加载数据函数时,我开始收到错误“Hydration 子项不匹配”。我没有找到带有 ssr 的 nuxt 3 应用程序的好示例,或者如何修复此警告的解决方案,或者带有 ssr 的 nuxt 3 应用程序的好示例,或者如何修复此问题的解决方案。
布局默认组件:
<script setup lang="ts">
import { useAuthStore } from "@/stores/authStore";
import { storeToRefs } from "pinia";
//Get Authorized user
const { isAuthorized } = useAuthStore();
const { statAuth } = storeToRefs(useAuthStore());
//Fetch AboutUs data
const { aboutUs } = storeToRefs(useAboutUsStore());
const { getAboutUs } = useAboutUsStore();
//Fetch Contactsr links data
const { stateData } = storeToRefs(useContactStore());
const { getContacts } = useContactStore();
//Fetch Nav and Footer links data
const { navLinks, footerLinks } = storeToRefs(useNavStorage());
const { getList, getFooterList } = useNavStorage();
//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { getPostList } = useArticleStore();
const { imageList } = storeToRefs(useGalaryStore());
const { getGalaryDBList } = useGalaryStore();
//CategoryLinks
const { categoryState } = storeToRefs(useCategoryStorage());
const { getGategoryList } = useCategoryStorage();
//Fetch Podcast List
const { podcastsState } = storeToRefs(usePodcastsStore());
const { getPodCastList } = usePodcastsStore();
//Fetch Advertisement List
const { advertiseList } = storeToRefs(useAdvertiseStore());
const { getAdvertiseList } = useAdvertiseStore();
async function loadStores2() {
await getPostList();
await getFooterList();
await getPodCastList();
await getList();
await getGategoryList();
await getGalaryDBList();
await getAdvertiseList();
await getAboutUs();
await getContacts();
}
await useAsyncData("loadData", loadStores2);
onMounted(async () => {
//check auth user in firebase
await isAuthorized();
});
</script>
<template>
<div class="defaultLayout">
<div class="header_block">
<UiAddTopHeader v-if="footerLinks" :category-links="navLinks" :top-links="footerLinks" />
<UiAddHeaderMiddle />
<UiAddHeader v-if="navLinks" :nav-links="navLinks" />
</div>
<div class="body_block">
<slot />
</div>
<div class="footer_block">
<UiAddFooter
v-if="footerLinks?.length && postsState?.postList?.length"
:about-us-links="footerLinks"
:favorites="postsState?.favoriteList"
:categories="categoryState" />
</div>
</div>
</template>
页脚组件。
<script setup lang="ts">
import type { IArticle } from "types/IArticle";
import type { ICategory } from "types/ICategory";
import type { INavigation } from "types/INavigation";
defineProps({
categories: {
type: Array as PropType<ICategory[]>,
default: null,
},
aboutUsLinks: {
type: Array as PropType<INavigation[]>,
default: null,
},
favorites: {
type: Array as PropType<IArticle[]>,
default: null,
},
});
</script>
<template>
<div class="footer_container">
<img class="footer_image" src="/images/footer.jpg" alt="footer" />
<div class="content">
<div class="media_block grid_block">
<h2 class="media_block_title">WORLD IMPULSE</h2>
<p>© Munich, LLC. All rights reserved, LLC</p>
<UiAddSocialMediaList :is-inline-block="true" />
</div>
<div class="about_block grid_block">
<h2 class="title_block">About Us</h2>
<UiAddAboutLinks
v-if="aboutUsLinks"
direction="flex"
gaps="20px"
:aboutlinks="aboutUsLinks" />
</div>
<div class="popular_block grid_block">
<h2 class="title_block">Popular Category</h2>
<UiAddAboutLinks
v-if="categories?.length"
direction="grid"
gaps="20px"
:categorylinks="categories?.slice(1, 8)" />
</div>
<div class="editor_picks grid_block">
<h2 class="title_block">Editor Piks</h2>
<ArticleSingleArticle
class-type="image_content_rigth"
:show-title="true"
:show-image="true"
font-size="16px"
:font-weight="400"
v-for="(el, i) in favorites.slice(0, 2)"
:key="i"
:single-post="el" />
</div>
</div>
</div>
</template>
[Id],ArticleListBlock组件调用的页面
<script setup lang="ts">
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
//Get Route params
const route = useRoute();
const routeSlug = String(route.params.slug);
const routeId = String(route.params.id);
//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { isExistPost } = useArticleStore();
//Get Category data
const categoryStore = useCategoryStorage();
const { isExistCategory } = categoryStore;
// Check route params
const isRouteCorrect = () => {
if (routeSlug === "search") {
return true;
} else if (routeId && (routeId === "list" || isExistPost(routeId))) {
return true;
}
return false;
};
//Throw an error id route params are not correct
if (!isRouteCorrect()) {
throw createError({ statusCode: 404, statusMessage: `The Page not found` });
}
</script>
<template>
<div class="wrapper">
<ArticleListBlock v-if="route.params.slug === 'search' || (routeId && routeId === 'list')" />
<ArticleSingleBlock v-else-if="routeId && isExistPost(routeId)" />
</div>
</template>
ArticleListBlock 组件
<script setup lang="ts">
import type { NuxtError } from "nuxt/app";
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
const route = useRoute();
const articlesByCategory = ref<IArticle[]>([]);
const search = ref("");
const errorResponse = ref<NuxtError>();
// Articles state
const { postsState } = storeToRefs(useArticleStore());
const { getArticlesByCategory, findArticlesByName } = useArticleStore();
// Advertisement galary state
const { advertiseList } = storeToRefs(useAdvertiseStore());
if (route.params.slug === "search") {
search.value = String(route.params.id);
console.log(search.value);
}
// Getting data by search request
const searchComputed = computed(() => {
if (!search.value) {
route.params.slug === "search" && (articlesByCategory.value = postsState.postList);
route.params.id === "list" &&
(articlesByCategory.value = getArticlesByCategory(String(route.params.slug)));
return articlesByCategory.value;
} else {
articlesByCategory.value = findArticlesByName(search.value);
if (!articlesByCategory.value.length) {
errorResponse.value = createError({ statusCode: 404, statusMessage: "Not found results" });
}
return articlesByCategory.value;
}
});
useHead({
title: `${String(route.params.slug)} list`,
meta: [
{ name: `${String(route.params.slug)}`, content: `${String(route.params.slug)} daily news` },
],
});
</script>
<template>
<div class="list_container grid_block">
<div class="top grid_block">
<div class="advertise_block">
<UiElementsAdvertise label="Advertisement" :link="advertiseList.databaseList[1]" />
</div>
<div class="topic grid_block">
<h5 class="category">CATEGORY</h5>
<h1 class="title_category">{{ String(route?.params?.slug)?.toLocaleUpperCase() }}</h1>
<h4 class="info">
Read the latest news with the Best WordPress News Theme – Newspaper by Sergio Belov!
</h4>
</div>
<div class="form_block">
<UiElementsAddPostInput
label="Search"
width-form="100%"
font-size="2rem"
name="search"
placeholder="Input search"
v-model:value.trim="search" />
</div>
</div>
<div class="main_section">
<div class="list grid_block" v-if="searchComputed.length">
<div class="item" v-for="(el, i) in searchComputed" :key="i">
<ArticleSingleArticle
class-type="image_content_left"
:show-image="true"
:show-title="true"
:show-short-body="true"
:show-date="true"
:single-post="el"
:id="el?.id" />
</div>
</div>
<ErrorResponse v-else-if="errorResponse" :error-event="errorResponse" />
<div class="right_bar" v-if="postsState.postList.length">
<h3>Latest News</h3>
<ArticleSingleArticle
class-type="image_content_rigth"
:show-title="true"
:show-image="true"
:show-date="true"
v-for="(el, i) in postsState.postList?.slice(4, 7)"
:key="i"
:single-post="el" />
</div>
</div>
</div>
</template>
来自 ArticleListBlock 的调用的ArticleSingleArticle 组件
<script setup lang="ts">
import type { IArticle } from "types/IArticle";
defineProps({
singlePost: {
type: Object as PropType<IArticle>,
default: null,
},
fontSize: {
type: String,
default: "18px",
},
fontWeight: {
type: Number,
default: 600,
},
rowSize: {
type: String,
default: "auto",
},
showContent: {
type: Boolean,
default: false,
},
showShortBody: {
type: Boolean,
default: false,
},
showCategory: {
type: Boolean,
default: false,
},
showTitle: {
type: Boolean,
default: false,
},
showImage: {
type: Boolean,
default: false,
},
showDate: {
type: Boolean,
default: false,
},
classType: {
type: String,
default: "",
},
});
</script>
<template>
<div class="single_post" :class="classType">
<div class="tag" v-if="showCategory">
<h5>{{ singlePost?.category?.toLocaleUpperCase() }}</h5>
</div>
<div class="title_post" v-if="showTitle">
<NuxtLink :to="{ path: `/${singlePost?.category}/${singlePost?.id}` }">
<p>{{ singlePost?.title }}</p>
</NuxtLink>
</div>
<div class="preview_image" v-if="showImage">
<img class="image" :src="singlePost?.image" alt="postPriview" />
</div>
<div v-if="showShortBody" class="shortn_block">
<p>{{ singlePost?.shortBody }}</p>
</div>
<div class="post_created" v-if="showDate">
<span>
By <b class="author">{{ singlePost?.author }}</b>
{{ singlePost?.date && formatDate(singlePost?.date) }}
</span>
</div>
<div v-if="showContent" class="description_block">
<div class="pre" v-html="singlePost?.body"></div>
<div class="share_block_container">
<UiElementsShareElement />
<UiAddSocialMediaList :is-inline-block="true" :enable-bg="true" color-icon="white" />
</div>
</div>
</div>
</template>
我删除了每个部分和组件中的所有注释标签,并尝试尽可能删除 v-if,但错误并没有消失。标签 ClientOnly 不适合我,因为我希望我的数据在服务器上呈现。
对于任何面临需要在组件中使用 html 标签的问题的人,例如p、href 等标签。使用“ClientOnly”来包装这些元素。
例如-
<ClientOnly>
<div>
<p>some text in here</p>
<a href="<my-link>">Click Me</a>
</div>
</ClientOnly>