Vue3项目仅指定两个页面实现SSR方案咨询(无需迁移Nuxt.js)
Vue3 单项目中仅指定页面实现SSR方案(无需Nuxt.js)
先解决你遇到的「仅能接受字符串化Vue根组件」问题
你之前按官方文档配置时的核心问题大概率是复用了单例Vue实例,服务端是多请求并发环境,必须为每个请求创建全新的Vue实例和路由实例,否则会导致状态污染,进而出现只能处理字符串化根组件的异常。
分步实现仅指定页面SSR
1. 调整项目结构
拆分客户端/服务端入口,抽离共享路由配置:
src/ ├─ entry-client.js # 客户端入口 ├─ entry-server.js # 服务端入口 └─ router/ └─ index.js # 共享路由配置
2. 配置带SSR标记的路由
在路由meta中标记需要SSR的页面,方便服务端识别:
// src/router/index.js import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router' const routes = [ // 需要SSR的页面 { path: '/page-a', component: () => import('../views/PageA.vue'), meta: { ssr: true } }, { path: '/page-b', component: () => import('../views/PageB.vue'), meta: { ssr: true } }, // 其余CSR页面 { path: '/', component: () => import('../views/Home.vue') } ] // 根据环境创建路由实例(服务端用memoryHistory) export function createRouterInstance() { return createRouter({ history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(), routes }) }
3. 编写服务端入口
每次请求创建全新Vue实例,处理路由匹配与渲染:
// src/entry-server.js import { createSSRApp } from 'vue' import { createRouterInstance } from './router' import App from './App.vue' export async function render(url) { // 为每个请求创建独立实例 const app = createSSRApp(App) const router = createRouterInstance() app.use(router) // 跳转至请求URL await router.push(url) await router.isReady() // 获取匹配的路由记录,判断是否需要SSR const matchedRoute = router.currentRoute.value.matched[0] if (!matchedRoute?.meta?.ssr) { return { html: '' } // 返回空,交由客户端CSR处理 } // 处理页面异步数据(如果有) if (matchedRoute.components.default.asyncData) { await matchedRoute.components.default.asyncData({ router }) } // 渲染为HTML字符串 const html = await app.renderToString() return { html, router } }
4. 编写客户端入口
客户端激活服务端渲染的HTML:
// src/entry-client.js import { createApp } from 'vue' import { createRouterInstance } from './router' import App from './App.vue' const app = createApp(App) const router = createRouterInstance() app.use(router) router.isReady().then(() => { app.mount('#app') })
5. 配置Node.js服务(以Express为例)
判断请求路由是否需要SSR,动态返回渲染结果:
// server.js import express from 'express' import fs from 'fs' import path from 'path' import { createRequire } from 'module' const require = createRequire(import.meta.url) const app = express() const PORT = process.env.PORT || 3000 // 读取HTML模板 const template = fs.readFileSync(path.resolve(__dirname, 'dist/client/index.html'), 'utf-8') // 导入服务端渲染入口(构建后的文件) const { render } = require('./dist/server/entry-server.js') // 静态文件服务(客户端构建产物) app.use(express.static(path.resolve(__dirname, 'dist/client'), { index: false })) // 处理所有路由请求 app.get('*', async (req, res) => { try { const { html } = await render(req.url) // 如果是SSR页面,注入渲染内容;否则直接返回原模板 const finalHtml = html ? template.replace('<!--ssr-outlet-->', html) : template res.status(200).set({ 'Content-Type': 'text/html' }).end(finalHtml) } catch (e) { console.error(e) res.status(500).end('Server Error') } }) app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`) })
6. 配置Vite构建
修改vite.config.js,添加SSR构建配置:
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], build: { ssr: true, outDir: 'dist', rollupOptions: { input: { client: resolve(__dirname, 'index.html'), server: resolve(__dirname, 'src/entry-server.js') } } } })
7. 调整HTML模板
在index.html中添加SSR内容占位符:
<!-- index.html --> <body> <div id="app"><!--ssr-outlet--></div> <script type="module" src="/src/entry-client.js"></script> </body>
官方文档相关内容翻译(SSR vs SSG)
SSR 对比 SSG
服务端渲染(SSR)指在服务器上为每个请求实时渲染页面,随后将完整HTML发送至客户端。客户端接收HTML后,会激活Vue应用使其具备交互能力。
静态站点生成(SSG)也叫预渲染,是在构建阶段为所有页面生成静态HTML文件。这些文件可部署至任意静态文件服务器,用户请求时直接提供,无需服务器实时渲染。
SSR适合内容频繁更新或需根据请求动态生成内容的场景;SSG则适合内容相对静态、可提前预渲染的场景,拥有更优的性能与成本优势。
内容的提问来源于stack exchange,提问作者mahmoud hmaserv
相关产品推荐
相关产品推荐

