1.vue的ssr方案
vue的createApp+express接管主服务器控制服务端渲染并与客户端水合(本章讲解的方案) Nuxt.js (如果是新项目建议用这个)
2.涉及技术
vue + vite + 任意UI框架 + vue-router express nginx + pm2
3.步骤
将main.js文件修改为SSR激活模式,使用createSSRApp
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'
export function createApp ( ) {
const app = createSSRApp ( App)
const router = createRouter ( )
app. use ( router)
return { app , router}
}
修改第一步使用的createRouter ,使用方法将router进行返回以便于服务端使用,注意在服务端时仅能使用createMemoryHistory 模式
import { createMemoryHistory, createRouter as createRouterBase, createWebHistory } from "vue-router"
export function createRouter ( ) {
const isServer = typeof window === 'undefined'
const history = isServer ? createMemoryHistory ( ) : createWebHistory ( )
return createRouterBase ( {
history: history,
routes: [
{
path: '/' ,
name: 'home' ,
component : ( ) => import ( '../components/HelloWorld.vue' )
} , {
path: '/about' ,
name: 'about' ,
component : ( ) => import ( '../components/About.vue' )
}
]
} )
}
在src目录下创建服务端和客户端入口文件entry-server.js和entry-client.js,名字可以任意区注意区分即可
import { createApp } from './main'
const { app, router } = createApp ( )
router. isReady ( ) . then ( ( ) => {
app. mount ( '#app' )
} )
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'
export async function render ( url ) {
const { app, router } = createApp ( )
await router. push ( url)
await router. isReady ( )
const ctx = { }
const html = await renderToString ( app, ctx)
const head= ` meta标签用于SEO优化 `
return { html, head }
}
修改根目录下的index.html,添加占位标记来替代服务端内容
< ! doctype html>
< html lang= "en" >
< head>
< meta charset= "UTF-8" / >
< link rel= "icon" type= "image/svg+xml" href= "/vite.svg" / >
< meta name= "viewport" content= "width=device-width, initial-scale=1.0" / >
< title> Vite + Vue< / title>
< ! -- app- head-- >
< / head>
< body>
< div id= "app" > < ! -- app- html-- > < / div>
< script type= "module" src= "/src/entry-client.js" > < / script>
< / body>
< / html>
根目录创建server.js文件用于项目启动
import fs from 'node:fs/promises'
import express from 'express'
const isProduction = process. env. NODE_ENV === 'production'
const port = process. env. PORT || 5173
const base = process. env. BASE || '/'
const templateHtml = isProduction
? await fs. readFile ( './dist/client/index.html' , 'utf-8' )
: ''
const app = express ( )
let vite
if ( ! isProduction) {
const { createServer } = await import ( 'vite' )
vite = await createServer ( {
server: { middlewareMode: true } ,
appType: 'custom' ,
base,
} )
app. use ( vite. middlewares)
} else {
const compression = ( await import ( 'compression' ) ) . default
const sirv = ( await import ( 'sirv' ) ) . default
app. use ( compression ( ) )
app. use ( base, sirv ( './dist/client' , { extensions: [ ] } ) )
}
app. use ( '*all' , async ( req, res ) => {
try {
const url = req. originalUrl. replace ( base, '' )
let template
let render
if ( ! isProduction) {
template = await fs. readFile ( './index.html' , 'utf-8' )
template = await vite. transformIndexHtml ( url, template)
render = ( await vite. ssrLoadModule ( '/src/entry-server.js' ) ) . render
} else {
template = templateHtml
render = ( await import ( './dist/server/entry-server.js' ) ) . render
}
const rendered = await render ( url)
const html = template
. replace ( ` <!--app-head--> ` , rendered. head ?? '' )
. replace ( ` <!--app-html--> ` , rendered. html ?? '' )
res. status ( 200 ) . set ( { 'Content-Type' : 'text/html' } ) . send ( html)
} catch ( e) {
vite?. ssrFixStacktrace ( e)
console. log ( e. stack)
res. status ( 500 ) . end ( e. stack)
}
} )
app. listen ( port, ( ) => {
console. log ( ` Server started at http://localhost: ${ port} ` )
} )
部署采用nginx+pm2进行管理
server {
listen 5173 ; # 前端服务端口
server_name localhost;
# 前端静态资源
location / {
root \dist\client; # 替换为你的静态资源路径
index index. html;
try_files $uri / index. html; # 支持 SPA 路由
}
# 反向代理 API 请求
location / api/ {
proxy_pass http: / / your- domain. com; # 替换为实际的后端服务地址
proxy_http_version 1.1 ;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade' ;
proxy_set_header Host $host;
proxy_set_header X - Real- IP $remote_addr;
proxy_set_header X - Forwarded- For $proxy_add_x_forwarded_for;
proxy_set_header X - Forwarded- Proto $scheme;
}
}
pm2 start . / dist/ server/ server. js -- name art- ssr -- env NODE_ENV = production -- env PORT = 5173 -- max- memory- restart 500 M
修改package.json文件启动方式
"scripts" : {
"dev" : "node server" ,
"build" : "npm run build:client && npm run build:server" ,
"build:client" : "vite build --outDir dist/client" ,
"build:server" : "vite build --ssr src/entry-server.js --outDir dist/server" ,
"preview" : "cross-env NODE_ENV=production node server"
} ,
4.注意
打包后需要将server.js文件放入dist目录下,其位置决定了pm2管理时运行的位置,上述pm2命令其server.js位置放置在dist/server目录下 对于不同的UI框架可能会有部分UI框架无法正常引入,会提示没有install或者没有导出函数,这个暂未处理