AI 技能人工智能【免费下载链接】skillsAnthony Fus curated collection of agent skills.项目地址https://gitcode.com/gh_mirrors/skills11/skills点击查看免费下载Pinia 是 Vue 官方的状态管理库本指南聚焦其在服务端渲染SSR场景下的正确使用方式如何在setup、getters、actions中安全调用 store如何在组件之外如路由守卫传入pinia实例以及如何通过devalue实现 XSS 安全的服务端状态序列化与客户端水合。读完本文你将掌握一套可直接落地的 Pinia SSR 架构方案能够避免跨请求状态污染、正确编写serverPrefetch/onServerPrefetch数据预取逻辑并理解 Nuxt 集成时自动处理 SSR 的差异点。本文基于当前仓库 skills/pinia/SKILL.md 所述 Pinia v4.0.3生成于 2026-09-25及 advanced-ssr.md 参考文档展开若你正在使用 Nuxt建议同时阅读 Nuxt 集成指南。为什么 Pinia 能支持 SSRPinia 之所以能够很好地服务 SSR核心在于它对「当前活跃的 pinia 实例」的追踪机制。在组件上下文中useStore()无需显式传参即可工作因为 Pinia 能够感知 Vue 的组件注入上下文而在组件之外则必须显式传入pinia实例。参考文档 best-practices-outside-component.md 明确指出Stores need thepiniainstance, which is automatically injected in components. Outside components, you may need to provide it manually.这条原则是全部 SSR 实践的基石store 的调用必须发生在 pinia 实例已经创建并安装app.use(pinia)之后而不是模块顶层。理解了这一点下面所有场景的写法就都顺理成章了。基本用法在 setup 中调用 storePinia 在 SSR 下工作的前提是在setup、getters或actions的顶层调用 store。在script setup中直接调用useStore()是安全的因为此时 Pinia 能够通过 Vue 的组件上下文感知到当前应用对应的 pinia 实例script setup // ✅ Works - pinia knows the app context in setup const main useMainStore() /script为什么不能把 store 调用放在模块顶层把useStore()提升到模块作用域是 SSR 场景下最常见的错误。原因有二模块作用域的执行时机不可控模块顶层代码在模块被import时立即执行此时 pinia 可能尚未创建或安装app.use(pinia)尚未执行调用必然失败或拿到错误实例多个请求共享同一模块实例服务端是长驻进程模块顶层只执行一次若在这里创建 store所有并发请求会共享同一份状态直接导致跨请求状态污染。参考文档 core-stores.md 中关于 actions 的 SSR 规范进一步补充了一个重要细节SSR:Call alluseStore()before anyawaitasync orderCart() { // ✅ Call stores before await const user useUserStore() await apiOrderCart(user.token, this.items) // ❌ Dont call useStore() after await in SSR }在异步 action 中所有useStore()调用必须放在第一个await之前。因为await之后执行上下文可能已切换到另一个请求此时再调用 store 很可能拿不到当前请求对应的 pinia 实例。这也解释了为什么依赖注入inject在 Setup Store 中可用——它们与组件上下文绑定同样必须在同步阶段完成。在 setup() 之外使用 store显式传入 pinia 实例路由守卫、插件、中间件等非组件上下文无法自动获得 pinia 实例必须显式将pinia传给useStore()const pinia createPinia() const app createApp(App) app.use(router) app.use(pinia) router.beforeEach((to) { // ✅ Pass pinia for correct SSR context const main useMainStore(pinia) if (to.meta.requiresAuth !main.isLoggedIn) { return /login } })与单页应用的对比导航守卫内的两种写法参考文档 best-practices-outside-component.md 给出了两种合法写法理解它们的差异能帮你写出既适用于 SPA 又适用于 SSR 的代码写法一守卫内部不传参SPA 适用router.beforeEach((to) { // ✅ Called after pinia is installed const store useUserStore() if (to.meta.requiresAuth !store.isLoggedIn) { return /login } })写法二守卫内部显式传参SPA / SSR 通用router.beforeEach((to) { // ✅ Pass pinia instance const main useMainStore(pinia) if (to.meta.requiresAuth !main.isLoggedIn) { return /login } })关键区别在于写法一依赖全局的「活跃 pinia」推断适用于单页应用写法二则通过闭包捕获的pinia实例精确绑定到当前应用因此在 SSR 场景下始终推荐显式传参。无论哪种写法都必须把useStore()放在守卫回调函数内部执行而不是放在createRouter()之后的模块顶层——后者会因 import 顺序不确定而间歇性失败。错误示范模块顶层调用import { createRouter } from vue-router const router createRouter({ /* ... */ }) // ❌ May fail depending on import order const store useUserStore() router.beforeEach((to) { if (store.isLoggedIn) { /* ... */ } })服务端数据预取serverPrefetch() 与 onServerPrefetch()SSR 的核心价值之一是首屏直出数据。Pinia 参考文档提供了两种在组件中触发数据预取的姿势选项式 APIserverPrefetch()在选项式组件中通过this.$pinia访问 pinia 实例export default { serverPrefetch() { const store useStore(this.$pinia) return store.fetchData() }, }这里this.$pinia是 Vue 应用实例上由 Pinia 注入的全局属性返回store.fetchData()的 Promise 可以让服务端等待数据就绪后再渲染 HTML。组合式 APIonServerPrefetch()在script setup中onServerPrefetch()正常工作且因为组件在 setup 阶段执行可以直接无参调用useStore()script setup const store useStore() onServerPrefetch(async () { await store.fetchData() }) /script两种方式本质相同预取动作发生在服务端渲染该组件时数据写入 store 的 state随后由水合流程传递给客户端。具体采用哪种取决于你的组件是选项式还是组合式 API 风格。状态水合State Hydration服务端序列化与客户端注入SSR 的关键闭环是服务端渲染时把 store 的 state 序列化进 HTML客户端启动时在调用任何useStore()之前把它还原进 pinia。服务端序列化 state 并注入 HTML服务端在完成渲染后从pinia.state.value取出全部 state。参考文档明确建议使用 devalue 进行XSS 安全的序列化——相比JSON.stringifydevalue 能正确处理Date、Map、Set、循环引用等JSON无法表达的数据结构同时避免/script注入等安全问题import devalue from devalue import { createPinia } from pinia const pinia createPinia() const app createApp(App) app.use(router) app.use(pinia) // After rendering, state is available const serializedState devalue(pinia.state.value) // Inject into HTML as global variable在 HTML 模板中通常将serializedState嵌入为一个全局变量例如script window.__pinia %- serializedState % /script注意这里必须使用 devalue 而非JSON.stringify直接拼接字符串否则 store 状态中含有的特殊字符可能破坏script标签结构形成 XSS 漏洞。客户端在调用任何 useStore() 之前水合客户端在创建 pinia 后、任何组件调用useStore()之前把window.__pinia中的数据还原进pinia.state.valueconst pinia createPinia() const app createApp(App) app.use(pinia) // Hydrate from serialized state (e.g., from window.__pinia) if (typeof window ! undefined) { pinia.state.value JSON.parse(window.__pinia) }时序至关重要水合动作必须发生在任何useStore()调用之前。如果某个组件先于水合执行了useStore()store 会以初始值创建服务端状态将被丢弃导致首屏闪现空数据hydration mismatch。避免跨请求状态污染每个请求一个全新的 pinia这是 SSR 中最容易踩坑、后果最严重的点。Node.js 服务端进程被多个并发请求共享而模块顶层的const pinia createPinia()只会执行一次——如果复用该实例请求 A 写入的 state 会被请求 B 读取到造成用户数据串号。正确做法是在服务端每个请求的入口创建一个全新的 pinia 实例并在请求结束后随上下文一起释放// 服务端入口伪代码每个请求执行一次 const pinia createPinia() // ✅ 每个请求全新的实例 const app createApp(App) app.use(pinia) // 渲染完成后序列化当前请求的 state const serializedState devalue(pinia.state.value)这正是参考文档 Key Points 中「Avoid cross-request state pollution by creating fresh pinia per request」的含义。与之呼应的是 features-plugins.md 中插件为 store 添加初始 state 时的 SSR 守卫写法pinia.use(({ store }) { // guard against overriding an existing value (important for SSR) if (!Object.hasOwn(store.$state, hasError)) { const hasError ref(false) store.$state.hasError hasError } store.hasError toRef(store.$state, hasError) })服务端水合后 state 已存在插件若不检查Object.hasOwn而直接赋值就会覆盖掉服务端渲染好的状态。同理插件为 store 注入外部非响应式对象如 router 实例时应使用markRaw()包裹避免其被意外序列化进水合数据import { markRaw } from vue import { router } from ./router pinia.use(({ store }) { store.router markRaw(router) })服务端渲染的完整骨架示例综合以上要点一个最小的 Pinia SSR 应用可以组织为// 服务端每个请求执行一次 import devalue from devalue import { createPinia } from pinia import { createApp } from vue import App from ./App.vue import { router } from ./router export async function renderRequest(req) { const pinia createPinia() // ① 每请求新实例隔离状态 const app createApp(App) app.use(router) app.use(pinia) router.beforeEach((to) { const main useMainStore(pinia) // ② 守卫中显式传参 if (to.meta.requiresAuth !main.isLoggedIn) { return /login } }) // ③ 路由解析、组件渲染serverPrefetch / onServerPrefetch 在此阶段执行 await router.push(req.url) await router.isReady() // ④ 序列化当前请求的 state const serializedState devalue(pinia.state.value) return { html: /* 渲染结果 */, state: serializedState, // ⑤ 注入 window.__pinia } }客户端入口则对应执行「创建 pinia → 水合window.__pinia→ 安装应用」的顺序。Nuxt 用户使用官方集成如果你在 Nuxt 3/4 中工作参考文档 advanced-nuxt.md 说明pinia/nuxt模块会自动处理 SSR、序列化与 XSS 防护你无需手工实现本文中的水合流程。npx nuxilatest module add pinia// nuxt.config.ts export default defineNuxtConfig({ modules: [pinia/nuxt], })Nuxt 会自动注入usePinia()、defineStore()、storeToRefs()、acceptHMRUpdate()等 APIstores/目录中的 store 会被自动导入在中间件等非组件上下文通过useNuxtApp().$pinia获取实例// middleware/auth.ts export default defineNuxtRouteMiddleware((to) { const nuxtApp useNuxtApp() const store useStore(nuxtApp.$pinia) if (to.meta.requiresAuth !store.isLoggedIn) { return navigateTo(/login) } })页面数据预取则推荐使用callOnce()实现 SSR 友好的「仅执行一次」语义script setup const store useStore() // Run once, data persists across navigations await callOnce(user, () store.fetchUser()) /script配套的最佳实践清单结合参考文档以下规则构成完整的 Pinia SSR 实践闭环在函数内部调用 store而不是模块作用域——模块顶层调用既拿不到实例又会造成跨请求共享状态见 best-practices-outside-component.md在组件之外使用 store 时显式传入pinia实例——路由守卫、中间件、插件中一律useStore(pinia)在调用任何useStore()之前完成状态水合——水合时序错误会导致服务端状态丢失与 hydration mismatch使用devalue或同类工具做安全序列化——JSON.stringify无法覆盖Date、Map、循环引用等类型且直接拼接字符串存在 XSS 风险每个请求创建全新的 pinia 实例——杜绝跨请求状态污染这是 SSR 应用数据安全的第一道防线在 async action 中所有useStore()调用放在第一个await之前见 core-stores.md插件添加初始 state 前检查Object.hasOwn、外部对象用markRaw包裹避免干扰水合见 features-plugins.md使用 Nuxt 时交给pinia/nuxt处理水合细节仅在中间件中通过$pinia获取实例见 advanced-nuxt.md。关键要点总结Key PointsCall stores inside functions, not at module scope —— 在函数内部调用 store而非模块作用域Passpiniainstance when using stores outside components in SSR —— 组件之外必须显式传piniaHydrate state before calling anyuseStore()—— 水合先于一切 store 调用Usedevalueor similar for safe serialization —— 用 devalue 做 XSS 安全的序列化Avoid cross-request state pollution by creating fresh pinia per request —— 每请求新建 pinia隔离跨请求状态。把这五条内化为习惯再配合 Nuxt 集成指南 与 store 核心概念 阅读你就掌握了 Pinia 在服务端渲染场景下的完整正确姿势。赞分享AI 技能人工智能【免费下载链接】skillsAnthony Fus curated collection of agent skills.项目地址https://gitcode.com/gh_mirrors/skills11/skills点击查看免费下载相关推荐Airi 中 Pinia 的服务端渲染实践setup 中的 Store、状态水合与跨请求隔离Airi 中 Pinia 的服务端渲染实践setup 中的 Store、状态水合与跨请求隔离 本文以 Airi 仓库中的 Pinia SSR 参考文档 advAI 应用人工智能大模型数字人AI Agent语音前端后端桌面应用移动开发即时通讯3D渲染Pinia SSR 服务端渲染指南状态隔离、序列化与 Hydration 完整实践Pinia SSR 服务端渲染指南状态隔离、序列化与 Hydration 完整实践 本篇技术指南聚焦 Pinia 在服务端渲染SSR场景下的完整实战方案前端状态管理Lit Query 服务端渲染SSR与客户端水合Hydration完整实践指南Lit Query 服务端渲染SSR与客户端水合Hydration完整实践指南 Lit Query tanstack/lit query 不仅面向前端缓存状态管理上一篇NoFences免费开源的桌面整理工具5步把Windows杂乱桌面变成清爽分区下一篇3 步免费激活 Windows 和 Office永久去除未激活水印创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考