You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt电商分页Hydration节点不匹配及生产环境空白问题求助

问题:Nuxt电商站点店铺页 hydration 不匹配及生产模式空白问题

问题现象

  • 开发模式下访问店铺页(含带page参数的URL)会出现hydration节点不匹配警告
  • 生产模式下直接访问http://localhost:3000/shop?page=1时,page=1内容闪显后页面变为空白
  • 点击菜单进入店铺页、点击分页按钮、浏览器前进后退操作均正常

开发模式警告信息

[Vue warn]: Hydration node mismatch:
- Client vnode: Symbol(v-cmt) 
- Server rendered DOM: <div class="grid grid-cols-1 gap-3 phone:grid-cols-4 laptop:grid-cols-8 justcol" data-v-7db6cc15><!--[--><div data-v-7db6cc15>…</div><div class="card text-center flex flex-col justify-between" data-v-7db6cc15 data-v-fe52aa40>…</div></div><div data-v-7db6cc15>…</div><div data-v-7db6cc15>…</div><div data-v-7db6cc15>…</div><!--]--></div>

相关代码

店铺页模板代码

<template>
  <div
    v-if="paginproducts"
    class="grid grid-cols-1 gap-3 phone:grid-cols-4 laptop:grid-cols justcol"
  >
    <div v-for="product in paginproducts" :key="product._id">
      <ProductCard v-if="paginproducts" :product="product" />
    </div>
  </div>
  <Pagination
    v-if="paginationlenght"
    :paginationlenght="paginationlenght"
    :paginationrow="paginationrow"
    @clicked="changePagination"
  />
</template>

店铺页脚本代码

<script setup>
const route = useRoute();
const router = useRouter();
const runtimeConfig = useRuntimeConfig();
const apitKey = runtimeConfig.public.apiToken;
const paginproducts = ref(null);
const paginationlenght = ref(null);
const paginationrow = 4;
const errorhandler = ref(false);

//Fetch Products from API
const fetchProducts = async (skip) => {
  const { data: products, error } = await useFetch(
    `${runtimeConfig.public.apiUrl}/api/content/items/Products? limit=${paginationrow}&skip=${skip}`,
    {
      method: "GET",
      headers: {
        "api-key": apitKey,
      },
    }
  );
  for (let i in products._value) {
    if (products._value.data.length) {
      paginproducts.value = {};
      paginationlenght.value = {};
      paginproducts.value = products._value.data;
      paginationlenght.value = products._value.meta;
      errorhandler.value = false;
    } else {
      errorhandler.value = true;
    }
  }
  if (error.value || errorhandler.value) {
    unwatch();
    paginproducts.value = null;
    paginationlenght.value = null;
    setTimeout(() => {
      showError({
        statusCode: 404,
        statusMessage: "Page not found.",
        data: "Generated from Category page.",
      });
    }, 1000);
  }
};
const changePagination = (changerow) => {
  let queypage = changerow + 1;
  router.push({ path: "/shop", query: { page: queypage } });
  console.log("pagination");
  if (process.browser) {
    window.scrollTo({ top: 0, behavior: "smooth" });
  }
};
onMounted(() => {
  if (!route.query.page || route.query.page === "") {
    router.replace({ path: "/shop", query: { page: "1" } });
  }
});
const unwatch = watch(
  () => route.query,
  () => {
    if (route.query.page !== "") {
      let skipping = parseInt(route.query.page) - 1;
      skipping = skipping * paginationrow;
      fetchProducts(skipping);
    }
  },
  { immediate: true }
);
</script>

分页组件模板代码

<template>
  <div v-if="totalButtons != 1" class="mt-6 w-full">
    <ul
      class="flex justify-center w-auto mx-auto place-items-center flex-col sm:flex-row"
    >
      <li class="mx-2 my-6 sm:my-auto">
        <button
          @click="onClickNumber(0)"
          :disabled="activeButton === 0"
          type="button"
          class="pagbutton"
          :class="{ active: activeButton === 0 }"
        >
          <span class="material-icons">keyboard_double_arrow_left </span>
        </button>
      </li>

      <li class="mx-2 my-6 sm:my-auto">
        <button
          @click="onClickNumber(activeButton - 1)"
          :disabled="activeButton === 0"
          type="button"
          class="pagbutton"
          :class="{ active: activeButton === 0 }"
        >
          <span class="material-icons"> chevron_left </span>
        </button>
      </li>
      <li
        v-for="(number, index) in totalButtons"
        :key="index"
        class="mx-2 my-6 sm:my-auto"
      >
        <p>
          <button
            @click="onClickNumber(index)"
            :disabled="activeButton === index"
            type="button"
            class="pagbutton buttonnumber"
            :class="{ activenumber: activeButton === index }"
          >
            {{ number }}
          </button>
        </p>
      </li>
      <li class="mx-2 my-6 sm:my-auto">
        <button
          @click="onClickNumber(activeButton + 1)"
          :disabled="activeButton === totalButtons - 1"
          type="button"
          class="pagbutton"
          :class="{ active: activeButton === totalButtons - 1 }"
        >
          <span class="material-icons"> chevron_right </span>
        </button>
      </li>
      <li class="mx-2 my-6 sm:my-auto">
        <button
          @click="onClickNumber(totalButtons - 1)"
          :disabled="activeButton === totalButtons - 1"
          type="button"
          class="pagbutton"
          :class="{ active: activeButton === totalButtons - 1 }"
        >
          <span class="material-icons">keyboard_double_arrow_right </span>
        </button>
      </li>
    </ul>
  </div>
</template>

分页组件脚本代码

<script setup>
const route = useRoute();
const { paginationlenght, paginationrow } = defineProps([
  "paginationlenght",
  "paginationrow",
]);
const emit = defineEmits(["clicked"]);
const activeButton = ref(0);
let totalButtons;
if (paginationlenght && paginationlenght.total > 0) {
  const calcButtons = paginationlenght.total % paginationrow;

  if (calcButtons === 0) {
    totalButtons = paginationlenght.total / paginationrow;
  } else {
    totalButtons = (paginationlenght.total - calcButtons) / paginationrow + 1;
  }
} else {
  totalButtons = 1;
}
const onClickNumber = (changenumber) => {
  activeButton.value = changenumber;
  emit("clicked", activeButton.value);
};

const unwatch = watch(
  async () => route.query,
  async () => {
    if (route.query.page) {
      activeButton.value = parseInt(route.query.page) - 1;
    }
  },
  { immediate: true }
);
</script>

排查尝试与求助

怀疑问题源于分页组件中的Google Icons,已尝试为其添加client-only标签,但问题仍存在,求解决方案。

内容的提问来源于stack exchange,提问作者Klas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:20:54