初始化项目
$ npm init vite@latest
添加客户端入口
改造main.js
import { createSSRApp } from 'vue'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
return { app }
}
新建客户端入口文件 src/entry-client.js
import { createApp } from "./main";
const { app } = createApp()
app.mount('#app')
修改index.html引入文件
<script type="module" src="/src/entry-client.js"></script>
重新运行项目,确保一切正常
本地node开启ssr
const fs = require('fs')
const path = require('path')
const express = require('express')
const { createServer: createViteServer } = require('vite')
async function createServer() {
const app = express()
const vite = await createViteServer({
server: { middlewareMode: 'ssr' }
})
app.use(vite.middlewares)
app.use('*', async (req, res) => {
const url = req.originalUrl
try {
let template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8'
)
template = await vite.transformIndexHtml(url, template)
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
const appHtml = await render(url)
const html = template.replace(`<!--ssr-outlet-->`, appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite.ssrFixStacktrace(e)
console.error(e)
res.status(500).end(e.message)
}
})
app.listen(3000, () => console.log('server is running at http://localhost:3000'))
}
createServer()
新建服务端入口文件 src/entry-server.js
import { createApp } from "./main"
import { renderToString } from 'vue/server-renderer'
export async function render(url) {
const { app } = createApp()
const html = renderToString(app)
return html
}
index.html添加占位符供服务端渲染时注入
<div id="app"><!--ssr-outlet--></div>
安装express & 启动node
npm i express -D
node server
预览 preview中已经可以看到返回的html源文件

打包运行线上环境
安装cross-env依赖,区分环境
npm i cross-env -D
添加启动、打包命令
"scripts": {
"dev": "vite",
"start:ssr": "cross-env NODE_ENV=developemnt node server",
"prod:ssr": "cross-env NODE_ENV=production node server",
"build": "vite build",
"preview": "vite preview",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js",
"build:ssr": "npm run build:client && npm run build:server"
},
server.js 改造
const serveStatic = require('serve-static')
const isProd = process.env.NODE_ENV === 'production'
let vite = await createViteServer({
server: { middlewareMode: 'ssr' }
})
if (isProd) {
app.use(serveStatic(path.resolve(__dirname, 'dist/client'), { index: false }))
} else {
app.use(vite.middlewares)
}
if (isProd) {
template = fs.readFileSync(
path.resolve(__dirname, 'dist/client/index.html'),
'utf-8'
)
render = require('./dist/server/entry-server.js').render
} else {
template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8'
)
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.js')).render
}
server.js 完整代码
const fs = require('fs')
const path = require('path')
const express = require('express')
const serveStatic = require('serve-static')
const { createServer: createViteServer } = require('vite')
const isProd = process.env.NODE_ENV === 'production'
async function createServer() {
const app = express()
let vite = await createViteServer({
server: { middlewareMode: 'ssr' }
})
if (isProd) {
app.use(serveStatic(path.resolve(__dirname, 'dist/client'), { index: false }))
} else {
app.use(vite.middlewares)
}
app.use('*', async (req, res) => {
const url = req.originalUrl
let template
let render
try {
if (isProd) {
template = fs.readFileSync(
path.resolve(__dirname, 'dist/client/index.html'),
'utf-8'
)
render = require('./dist/server/entry-server.js').render
} else {
template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8'
)
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.js')).render
}
const appHtml = await render(url)
const html = template.replace(`<!--ssr-outlet-->`, appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite.ssrFixStacktrace(e)
console.error(e)
res.status(500).end(e.message)
}
})
app.listen(3000, () => console.log(`server is running at ${isProd ? '线上' : '开发'}环境: http://localhost:3000`))
}
createServer()