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

Vite+ViteSSG+Pinia环境下,如何预加载GraphQL数据配置页面头?

解决ViteSSG中Pinia数据加载与useHead时机不匹配的问题

你当前的核心问题是useHead执行时Pinia的页面数据还没加载完成,导致无法动态设置页面标题和元描述。以下是具体的解决步骤:

1. 让Pinia的初始化方法返回Promise

首先修改store/pages.js,把initialize改成异步函数,确保数据加载完成后才标记完成:

import { defineStore } from 'pinia'

export const usePagesStore = defineStore('pages', {
  state: () => ({ pages: [] }),
  actions: {
    // 改成async函数,返回Promise
    async initialize() {
      console.log('initialize')
      try {
        const res = await fetch('https://localhost:44351/graphql/', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            query: `
            query {
              contentAll {
                nodes {
                  url
                  name
                  metaDescription: properties(
                    where: { alias: { eq: "metaDescription" } }
                  ) {
                    value {
                      ... on BasicPropertyValue { value }
                    }
                  }
                }
              }
            }`
          })
        })
        const result = await res.json()
        this.pages = result.data.contentAll.nodes
        console.log('done loading data')
      } catch (err) {
        console.error('Failed to load page data:', err)
      }
      console.log('done')
    }
  },
  getters: {
    allPages: (state) => state.pages,
    getPageById: (state) => (pageUrl) => state.pages.find(page => page.url === pageUrl)
  }
})

2. 在ViteSSG启动时等待数据加载完成

修改main.js,在ViteSSG的初始化钩子中等待initialize完成,尤其是SSR阶段必须等待,这样页面数据会被预渲染到initialState,客户端加载时直接复用,避免重复请求:

import App from './App.vue'
import { ViteSSG } from 'vite-ssg'
import { router } from './router.js'
import { provideApolloClient } from '@vue/apollo-composable'
import { ApolloClient, InMemoryCache } from '@apollo/client/core'
import { createPinia } from 'pinia'
import { usePagesStore } from './stores/pages'

const cache = new InMemoryCache()
const apolloClient = new ApolloClient({ cache, uri: 'https://localhost:44351/graphql/' })
provideApolloClient(apolloClient)

export const createApp = ViteSSG(
  App,
  { routes: router },
  async ({ app, router, routes, isClient, initialState }) => { // 改成async函数
    const pinia = createPinia()

    if (import.meta.env.SSR) {
      initialState.pinia = pinia.state.value
    } else {
      pinia.state.value = initialState?.pinia || {}
    }
    app.use(pinia)
    const pagesStore = usePagesStore(pinia)
    
    // 客户端如果已有initialState数据,就不用重复请求
    if (isClient && initialState?.pinia?.pages?.length) return
    // 等待数据加载完成
    await pagesStore.initialize()
    // SSR时把加载好的数据存入initialState
    if (import.meta.env.SSR) {
      initialState.pinia = pinia.state.value
    }
  }
)

3. 在App.vue中动态监听数据变化,更新页面头

把useHead的逻辑移到setup中,用watch监听路由变化和Pinia的页面数据,当两者任一变化时,动态更新页面标题和元描述:

<script>
import { useHead, watch } from '@vueuse/head'
import { useRoute } from 'vue-router'
import { usePagesStore } from './stores/pages'
import { computed, ref } from 'vue'

export default {
  name: 'App',
  setup() {
    const pageStore = usePagesStore()
    const route = useRoute()

    // 计算当前页面的信息,自动响应数据变化
    const currentPage = computed(() => pageStore.getPageById(route.path))

    // 监听currentPage变化,更新head
    watch(currentPage, (page) => {
      if (!page) return
      useHead({
        title: page.name || '默认标题',
        meta: [
          {
            name: 'description',
            content: page.metaDescription?.[0]?.value?.value || '默认描述'
          }
        ]
      })
    }, { immediate: true }) // 立即执行一次

    // 处理菜单逻辑
    const menuIsOpen = ref(false)
    const toggleMenu = () => {
      menuIsOpen.value = !menuIsOpen.value
    }

    return { menuIsOpen, toggleMenu }
  }
}
</script>
<template>
  <div class="sidenav" :class="{ 'is-open': menuIsOpen }">
    <div class="sidenav-extended">
      <div class="sidenav-extended__menu-items">
        <router-link to="/" @click="toggleMenu">Home</router-link>
        <router-link to="/blogs" @click="toggleMenu">Blog</router-link>
        <router-link to="/about" @click="toggleMenu">About</router-link>
      </div>
      <input placeholder="Search..." class="sidenav-extended__search" />
    </div>
    <div class="sidenav-buttons">
      <div class="sidenav-buttons__top">
        <img src="@/assets/vue.svg" class="sidenav-buttons__logo" />
        <div class="menu-icon" @click="toggleMenu">
          <div></div>
        </div>
      </div>
      <div class="sidenav-bottom">
        <div class="sidenav-bottom__tags" @click="toggleMenu">
          <font-awesome-icon icon="fas fa-tags" />
        </div>
        <div class="sidenav-bottom__search" @click="toggleMenu">
          <font-awesome-icon icon="fas fa-magnifying-glass" />
        </div>
      </div>
    </div>
  </div>
  <div class="main">
    <router-view :key="$route.path" />
  </div>
</template>

关键说明

  • SSR预加载:在ViteSSG的异步启动钩子中等待数据加载,确保静态生成时页面头信息已经被正确渲染,对SEO更友好
  • 响应式更新:用computed和watch监听路由和Pinia数据的变化,确保路由切换时页面头也能动态更新
  • 避免重复请求:客户端判断initialState是否已有数据,减少不必要的GraphQL请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:57:13