使用Vite+Vue3搭建前端SSR应用(二)搭建SSR服务端渲染
笔特 | 11/26/2022, 1:41:31 AM | 4396 次阅读
1 引言
使用Vite+Vue3搭建前端SSR应用
相关文章
- 使用Vite+Vue3搭建前端SSR应用(一)新建项目并配置基础环境
- 使用Vite+Vue3搭建前端SSR应用(二)搭建SSR服务端渲染
- 使用Vite+Vue3搭建前端SSR应用(三)SEO网页头配置
- 使用Vite+Vue3搭建前端SSR应用(四)应用的部署
关键词
- Vite
- Vue
- SSR
- SEO
源代码地址
https://gitee.com/bitem/vite-vue-ssr-demo.git
2 安装依赖
# 有些依赖前面已经装过了,重复安装没关系的
yarn add vite-ssr vue-router @vueuse/head axios
yarn add -D @types/node vite-plugin-pages less
1
2
3
2
3
3 关键代码改造
// vite.config.ts 中加入viteSSR插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'
import viteSSR from 'vite-ssr/plugin.js'
export default defineConfig({
plugins: [
viteSSR({
build: {
keepIndexHtml: true,
},
})
]
})
// 无关代码忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/route.ts 中删除多余代码直接导出routes即可
import routes from '~pages'
export { routes }
1
2
3
2
3
// src/main.ts
import '@/assets/index.less'
import App from './App.vue'
import { routes } from "./route"
import viteSSR, { ClientOnly } from 'vite-ssr'
import { createHead } from '@vueuse/head'
import { ApiBase } from './api/config/api.config'
const options = {
routes: routes
}
// 导出viteSSR函数,vite-ssr只有一个入口,服务端和客户端共用一个端口,系统会自动完成环境区分
export default viteSSR(App, options, (context) => {
const { app, router } = context
const head = createHead()
app.use(head)
app.component(ClientOnly.name, ClientOnly)
router.beforeEach(async (to: any, from: any, next: any) => {
if (!!to.meta.state && Object.keys(to.meta.state).length > 0) {
// 已经存在数据,不需要在请求了
return next()
}
try {
// 这里为了演示,直接请求固定路径的接口,正式环境,可根据路由匹配对应的后端接口
console.log("router =>",to.name)
// 这里的ApiBase需要根据环境判断,如果是SSR,则必须是全路径
// export const ApiBase = import.meta.env.SSR ? "http://www.bitem.cn" : ""
const res = await fetch(ApiBase + '/api/web/article/a8feb225-ae66-288a-5f21-0f9deafe6a80',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
// 存储数据
to.meta.state = await res.json()
} catch (error) {
// 跳转错误页
console.error(error)
}
next()
})
return { head }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!-- src/views/about.vue -->
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useContext } from 'vite-ssr'
export default defineComponent({
methods: {
onButtonClick() {
this.count++
}
},
setup() {
const count = ref(0)
// 这里通过useContext取出初始数据,页面就可以进行SSR渲染了
const { initialState } = useContext()
return {
count,
initialState
}
}
})
</script>
<template>
<div class="view-about">
<div>
这是关于 <button @click="onButtonClick">{{ count }}</button>
</div>
<p>
{{initialState}}
</p>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4 结果展示
5 直接在Vue组件中请求数据进行服务端渲染
这是一个非常友好的功能,我们可以像平常开发那样开发SSR
删除main.ts中的router.beforeEach的代码
// main.ts 只保留以下代码
import '@/assets/index.less'
import App from './App.vue'
import { routes } from "./route"
import viteSSR, { ClientOnly } from 'vite-ssr'
import { createHead } from '@vueuse/head'
const options = {
routes: routes
}
export default viteSSR(App, options, (context) => {
const { app } = context
const head = createHead()
app.use(head)
app.component(ClientOnly.name, ClientOnly)
return { head }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
新建请求函数
// src/api/init/about.ts
import { useContext } from 'vite-ssr'
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import { ApiBase } from '../config/api.config'
export async function getInitData() {
const { initialState } = useContext()
// 这只是一个唯一值,这里直接取路由的名称,也可以直接自定义,如:view-about 或者 component-helloworld
const { name } = useRoute()
const state = ref(initialState[name as string] || null)
if (!state.value) {
state.value = await (await fetch(ApiBase + '/api/web/article/a8feb225-ae66-288a-5f21-0f9deafe6a80')).json()
// SSR需要将state的值赋值给initialState,这样任何地方都可以直接调用这个值,并且,如果不赋值的话服务端和客户端会请求两次数据造成浪费
if (import.meta.env.SSR) {
initialState[name as string] = state.value
}
}
return state
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
调用初始化数据请求函数
<!-- src/views/about.vue -->
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { getInitData } from "@/api/init/about"
export default defineComponent({
methods: {
onButtonClick() {
this.count++
}
},
async setup() {
const count = ref(0)
// setup异步函数直接同步请求数据后进行SSR渲染
const state = await getInitData()
return {
count,
state
}
}
})
</script>
<template>
<div class="view-about">
<div>
这是关于 <button @click="onButtonClick">{{ count }}</button>
</div>
<p>
{{ state }}
</p>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
6 结果展示
这张图是将src/api/init/about.ts中赋值initialState注释掉的结果,可以看到没有__INITIAL_STATE__照样能够SSR渲染,只不过请求过来的数据是一次性的,客户端会再次执行数据请求(仔细看图能够发现有一个请求接口的记录)
这张图是正常的结果,请求只发生了一次,服务端请求后,客户端共用服务端的数据,所以图中没有请求接口的记录