这可能是最简略的革新计划了
简介
SSR 是 Server Side Render 简称。
在服务端应用 node 渲染出 html 页面,再传输到客户端。惯例单页利用 html 只返回极少局部 dom 字符串 +script 动静生成网页,不利于 google 爬虫的抓取。因而为了 google 更好的搜录网页,须要在服务端(node)执行 js 以渲染出 html 给 google 爬虫收录。
通用代码
- 因为 js 会在 node 端执行,所以不能蕴含客户端浏览器植入的对象,比方 window,location,localStorage,navigator,document 等对象。
-
服务端防止状态净化
官网阐明
解释: 上面一段代码记录以后用户发动的申请数量export const http = axios.create({baseURL:'/api'}) let count = 0 http.interceptors.request.use((config) => { count++ return config })
上述写法将使 count 长久化,当第二个用户发动申请时,会累加第一个用户的 count,导致统计不正确。
- 不能在 node 端运行 setInterval 等代码,因为此类代码对于服务端染没有任何意义,只会造成 node 内存透露,必须限度在客户端运行。
必须的 api
- renderToString 能够将组件渲染成 html 字符串(能够用 renderToNodeStream 等流式渲染办法)
- Suspense 组件,此组件为 node 端获取数据后渲染的要害,使得由此组件包裹的后辈组件,能够应用
async setup
函数 - vite.ssrLoadModule 利用 vite 将 vue 打包后执行,此为调试模式要害。
官网示例代码
但还有 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 MapData
const 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