Vue项目的SSR处理教程

1.vue的ssr方案

  • vue的createApp+express接管主服务器控制服务端渲染并与客户端水合(本章讲解的方案)
  • Nuxt.js(如果是新项目建议用这个)

2.涉及技术

  • vue + vite + 任意UI框架 + vue-router
  • express
  • nginx + pm2

3.步骤

  1. 将main.js文件修改为SSR激活模式,使用createSSRApp
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouter } from './router'

// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.
export function createApp() {
  const app = createSSRApp(App)
  const router = createRouter()
  app.use(router)
  return { app ,router}
}
  1. 修改第一步使用的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')
      }
    ]
  })
}
  1. 在src目录下创建服务端和客户端入口文件entry-server.js和entry-client.js,名字可以任意区注意区分即可
// /entry-client.js
import { createApp } from './main'

const { app, router } = createApp()

router.isReady().then(() => {
  app.mount('#app')
})
// /entry-server.js
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()

  // 渲染 HTML
  const ctx = {}
  const html = await renderToString(app, ctx)
  const head= `meta标签用于SEO优化`
  return { html, head }
}
  1. 修改根目录下的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>

  1. 根目录创建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()

/** @type {import('vite').ViteDevServer | undefined} */
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, '')

    /** @type {string} */
    let template
    /** @type {import('./src/entry-server.js').render} */
    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}`)
})

  1. 部署采用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 500M
  1. 修改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.注意

  1. 打包后需要将server.js文件放入dist目录下,其位置决定了pm2管理时运行的位置,上述pm2命令其server.js位置放置在dist/server目录下
  2. 对于不同的UI框架可能会有部分UI框架无法正常引入,会提示没有install或者没有导出函数,这个暂未处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值