为什么要使用 SSR

GPT 回复

更快的首屏加载时间:SSR 将初始 HTML 页面在服务器上生成并发送给客户端,从而使用户在请求页面时无需等待 JavaScript 的加载和执行即可看到页面内容。这种方式可以显著提升首屏加载时间和用户体验。

更好的 SEO:搜索引擎蜘蛛能够更容易地抓取和索引由 SSR 生成的页面内容,从而提高网站在搜索引擎中的排名。这对于内容驱动的网站(如博客、新闻网站等)尤为重要。

便于预渲染静态页面:对于不经常变化的页面内容,SSR 可以预渲染并缓存生成的静态页面,从而减少服务器的负载和请求处理时间。

说白了 ,更快的加载速度 不用向 spa 项目一样进行首次进去加载比较久, seo 友好,并且可以预渲染

起步工作

安装

# 安装vue
pnpm add vue
# 安装vite 和 vitejs支持vue的插件
pnpm add vite @vitejs/plugin-vue -D
# 安装服务端插件
pnpm add express

创建 vite 配置文件 vite.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
});

创建 src 目录,建立 App.vue 和 main.ts 目录

// main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";

export function createApp() {
  const app = createSSRApp(App);
  return { app };
}
// App.vue
<template>
  <div>
   <h1>Vite SSR</h1>
  </div>
</template>

<style scoped>

</style>

创建 entry-client.ts 和 entry-server.ts 文件, 并且将 index.html 连接至 entry-client.ts

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <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.ts"></script>
  </body>
</html>

编写客户端入口和服务端入口

// entry-client.ts
import { createApp } from "./main";

const { app } = createApp();

app.mount("#app");
// entry-server.ts
import { renderToString } from "vue/server-renderer";
import { createApp } from "./main";

export async function render() {
  const { app } = createApp();

  const ctx = {};
  const html = await renderToString(app, ctx);

  return { html };
}

2 3

编写编译客户端编译指令 和 服务端编译指令

package.json

{
  "scripts": {
    "build": "pnpm build:client && pnpm build:server",
    "build:client": "vite build --ssrManifest --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server"
  }
}

执行 pnpm build 命令 , 构建客户端和服务端查看结果

4

可以看到分别构建了 SSR的客户端和服务端

# 安装 压缩插件 和 静态资源插件
pnpm add sirv compression

编写server.ts 启动服务,完成开发环境场景

import express from 'express';
import fs from 'node:fs/promises'


// 区分开发环境和生产环境
const isProd = process.env.NODE_ENV === 'production';

// 根路径
const base = process.env.BASE || '/'

// 启动端口
const port = 3000

// 读取客户端模板
const templateHtml = isProd ? await fs.readFile('./dist//index.html', 'utf-8') : "";

// 读取ssr资源
const ssrManifest = isProd ? await fs.readFile('./dist/client/.vite/ssr-manifest.json', undefined) : undefined;


// 创建http服务
const app = express();

let vite

// 如果不是生产环境,启动vite  , 如果不是启动express中间件
if (!isProd) {
    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('*', async (req, res) => {
    try {
        const url = req.originalUrl.replace(base, '/')

        let template
        let render

        if (!isProd) {
            // 如果是开发环境
            template = await fs.readFile('./index.html', 'utf-8')
            template = await vite.transformIndexHtml(url, template)
            render = (await vite.ssrLoadModule('/src/entry-server.ts')).render
        } else {
            // 如果是生产
            template = templateHtml;
            render = (await import('./dist/server/entry-server'))
        }

        const rendered = await render(url, ssrManifest);

        // 占位符替换渲染
        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(`SSR项目已经启动 http://localhost:${port}`)
})

编写启动指令,因为我们是ts语言, 我们使用antfu大佬写的’tsx’轮子来启动

pnpm add tsx -D

修改命令

"scripts": {
    "dev":"tsx server",
    "build": "pnpm build:client && pnpm build:server",
    "build:client": "vite build --ssrManifest --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server"
},

可以看到我们已经启动成功了,页面渲染也正常 ,成功!

5

6

相关资料

编写的测试模板存放地址:https://github.com/itmowang/vite-vue-ssr-template.git