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
相关产品推荐
相关产品推荐

