这可能是最简略的革新计划了

简介

SSR是Server Side Render简称。
在服务端应用node渲染出html页面,再传输到客户端。惯例单页利用html只返回极少局部dom字符串+script动静生成网页,不利于google 爬虫的抓取。因而为了google更好的搜录网页,须要在服务端(node)执行js以渲染出html给google爬虫收录。

通用代码

  1. 因为js会在node端执行,所以不能蕴含客户端浏览器植入的对象,比方window,location,localStorage,navigator,document等对象。
  2. 服务端防止状态净化
    官网阐明
    解释: 上面一段代码记录以后用户发动的申请数量

    export const http = axios.create({baseURL:'/api'})let count = 0http.interceptors.request.use((config) => { count++ return config})

上述写法将使count长久化,当第二个用户发动申请时,会累加第一个用户的count,导致统计不正确。

  1. 不能在node端运行setInterval 等代码,因为此类代码对于服务端染没有任何意义,只会造成node内存透露,必须限度在客户端运行。

必须的api

  1. renderToString 能够将组件渲染成html字符串(能够用renderToNodeStream等流式渲染办法)
  2. Suspense 组件,此组件为node端获取数据后渲染的要害,使得由此组件包裹的后辈组件,能够应用async setup函数
  3. vite.ssrLoadModule 利用vite将vue打包后执行,此为调试模式要害。

官网示例代码
但还有2个问题

  1. 如何获取数据后渲染
  2. 如何将服务端的申请数据长久化,防止客户端二次申请
1.获取数据后渲染的办法

在About组件中须要获取数据后渲染:

<script setup lang="ts">const get = () => {  return new Promise(res => {    setTimeout(() => {      res(Math.random())    }, 3000)  })}const b = await get()</script><template><div>{{b}}</div></template>

app组件:

<template>    <Suspense>          <About/>    </Suspense></template>

服务端代码:

const ctx = {}const html = await renderToString(App,ctx)

上述html会渲染出b的数值。其中在组件中能够应用useSSRContext获取ctx。ctx.modules 含有以后所渲染页面须要的依赖js,css,图片等。可将这些依赖退出预下载,放慢渲染速度。

2. 解决客户端反复申请数据

在服务端,申请数据后,将数据保留到window变量中,在客户端再次申请时,发现曾经有数据了,那么就不用在此申请。间接应用window中的数据。是否删除次数据,按照状况而定。
此处附一个axios的申请做法。
ssrCache.ts

import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, Method } from 'axios'import md5 from 'md5'type MapData = {    [k: string]: {        data: any,        count: number    }}let map = {} as MapDataconst getKey = (config: AxiosRequestConfig<any>) => {    const { url, data, baseURL, params } = config    let paramsStr = ''    for (let key in params) {        if (Object.prototype.hasOwnProperty.call(params, key))            paramsStr += `${key}=${params[key]}`    }    return md5(baseURL || '' + url + data + paramsStr).substring(0, 8)}export const createProxy = (http: AxiosInstance) => {    const ssr = typeof window === 'undefined'    const request = async (config: AxiosRequestConfig<any>) => {        const key = getKey(config)        if (ssr) {            if (map[key]) {                // 只有当页面的数据申请数据做缓存。并且记录申请次数                map[key].count++                return map[key].data            }            try {                const res = await http(config)                let data = res                if (res.request) data = res.data                map[key] = { data, count: 1 }                return res            } catch (err) {                return Promise.reject(err)            }        }        if (window.__SSRDATA__ !== undefined) {            const map = window.__SSRDATA__            if (map[key]) {                map[key].count--                 if (map[key].count === 0) {                    delete map[key]                }                return map[key].data            }        }        return http(config)    }    const fn = (method: string) => {        return (url: string, config: AxiosRequestConfig<any>) => {            return request({ url, method, ...config })        }    }    const set = new Set(["get", "post", "options", "delete", "put", "patch"])    // 此处做一层proxy,因为axios能够axios()应用,也能够axios.get()应用    // 须要将他们转换成一样的申请模式    return new Proxy(http, {        get(target, key: keyof AxiosInstance) {            if (set.has(key.toLowerCase())) {                return fn(key)            }            return http[key]        },        apply(v) {            return request(v)        }    }) as AxiosInstance}export const collect =() => {    map={}    return () => `<script>window.__SSRDATA__=${JSON.stringify(map)}</script>`}

应用:

const http = createProxy(axios.create({baseURL:''}))

当在ssr中应用http获取数据,会将所有返回记录到map中,再将其转换为字符串潜入到html字符串中

const getScriptString = collect()let html = await renderToString(App,ctx)html = getScriptString() + html