vue服务端渲染ssr
一、SSR概念
Server-side rendering(SSR)是应用程序通过在服务器上显示网页而不是在浏览器中渲染的能力。服务器端向客户端发送一个完全渲染的页面(准确来说是仅仅是 HTML 页面)。同时,结合客户端的JavaScript bundle 使得页面可以运行起来。
传统web渲染技术
传统的web渲染技术asp .net php jsp是浏览器向服务器发送请求。服务器进行数据库查询等操作后拼接成html返回到浏览器:
SPA
而SPA(single page web application)单页面应用程序首屏加载较慢不利于不利于SEO,单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
SSR
SSR尽量不占用前端的资源,前端这块耗时少,速度快。有利于SEO优化。不利于前后端分离,主要工作在后端哪里,开发效率慢。
二、webpack+vue2的实现方式
1.创建工程
vue create ssr
2.安装依赖
渲染器vue-server-renderer
nodejs服务器express
npm i vue-server-render express -D
3.编写一个简单的SSR
创建一个express服务器,将vue ssr集成进来,./server/index.js
运行 node index.js
// nodejs服务器
const express = require("express");
const Vue = require("vue");
// 创建express实例和vue实例
const app = express();
// 创建渲染器
const renderer = require("vue-server-renderer").createRenderer();
// 将来用渲染器渲染page可以得到html内容
const page = new Vue({
data: {
title: "SSR" },
template: "<div><h1>{
{title}}</h1><div>hello, vue ssr!</div></div>",
});
app.get("/", async (req, res) => {
try {
const html = await renderer.renderToString(page);
// eslint-disable-next-line no-console
console.log(html);
res.send(html);
} catch (error) {
res.status(500).send("服务器内部错误");
}
});
app.listen(3000, () => {
// eslint-disable-next-line no-console
console.log("渲染服务器启动成功");
});
运行如下
在浏览器访问localhost:3000
4.完整的ssr
安装vue-router
npm i vue-router -s
配置
创建./src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/components/Index'
import Detail from '@/components/Detail'
Vue.use(Router)
// 这里为什么不导出一个router实例哪?
// 每次用户请求都需要创建router实例
export default function createRouter() {
return new Router({
mode: 'history',
routes: [
{
path: '/', component: Index},
{
path: '/detail', component: Detail},
]
})
}
创建Index.vue
<template>
<div>
Index Page
</div>
</template>
创建Detail.vue
<template>
<div>
Detail Page
</div>
</template>
更新App.vue
<template>
<div id="app">
<!-- <img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/> -->
<nav>
<router-link to="/">首页</router-link>
<router-link to="/detail">详情页</router-link>
</nav>
<router-view></router-view>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
入口app.js
// 创建vue实例
import Vue from "vue";
import App from "./App.vue";
import createRouter from "./router";
export default function createApp() {
const router = createRouter();
const app = new Vue({
router,
render: h => h(App),
});
return {
app, router };
}
服务端入口entry-client.js
// 挂载、激活app
import createApp from './app'
const {
app,router} = createApp();
router.onReady(() => {
app.$mount('#app')
})
客户端入口entry-server.js
// 渲染首屏
import createApp from "./app";
// context哪来的?
export default context => {
return new Promise((resolve, reject) => {
const {
app, router } = createApp();
// 进入首屏
router.push(context.url)
router.onReady(() => {
resolve(app);
}, reject)
});
};
添加webpack打包依赖
npm install webpack-node-externals lodash.merge -D
具体配置,vue.config.js
// webpack插件
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");
const nodeExternals = require("webpack-node-externals");
const merge = require("lodash.merge");
// 环境变量:决定入口是客户端还是服务端
const TARGET_NODE = process.env.WEBPACK_TARGET === "node";
const target = TARGET_NODE ? "server" : "client";
module.exports = {
css: {
extract: false
},
outputDir: './dist/'+target,
configureWebpack: () => ({
// 将 entry 指向应用程序的 server / client 文件
entry: `./src/entry-${
target}.js`,
// 对 bundle renderer 提供 source map 支持
devtool: 'source-map',
// 这允许 webpack 以 Node 适用方式处理动态导入(dynamic import),
// 并且还会在编译 Vue 组件时告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: TARGET_NODE ? "node" : "web",
node: TARGET_NODE ? undefined : false,
output: {
// 此处告知 server bundle 使用 Node 风格导出模块
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的 bundle 文件。
externals: TARGET_NODE
? nodeExternals({
// 不要外置化 webpack 需要处理的依赖模块。
// 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 服务端默认文件名为 `vue-ssr-server-bundle.json`
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule("vue")
.use("vue-loader")
.tap(options => {
merge(options, {
optimizeSSR: false
});
});
}
};
最终代码结构如下
5.代码链接
完整代码链接如下:
https://download.csdn.net/download/qq_43548590/86947216?spm=1001.2014.3001.5503
此次参考为:
https://www.bilibili.com/video/BV1dE411C7f5?p=1
三、Vite+Vue3的实现方式
官方地址:https://vitejs.cn/guide/ssr.html
官方示例:https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue
此处我使用的是vite-ssr https://github.com/frandiox/vite-ssr
进行搭建
1、创建一个Vue项目
yarn create vite --template vue
2、添加依赖
yarn add vite-ssr vue@3 vue-router@4 @vueuse/head
3、将 Vite SSR 插件添加到您的 Vite 配置文件中
vite.config.js
// vite.config.js
import vue from '@vitejs/plugin-vue'
import viteSSR from 'vite-ssr/plugin.js'
// import react from '@vitejs/plugin-react'
export default {
plugins: [
viteSSR(),
vue(), // react()
],
}
4、修改main.js
可以在注释出添加自己的业务逻辑
import {
createApp } from 'vue'
import './style.css'
import App from './App.vue'
import routes from './routes'
import viteSSR from 'vite-ssr'
export default viteSSR(App, {
routes }, (context) => {
/* Vite SSR main hook for custom logic */
/* const { app, router, initialState, ... } = context */
})
5、添加路由
npm install vue-router@4
routes.js
import about from './components/about.vue'
import Home from './components/home.vue'
export default [
{
path:"/",
component: ()=>import('./page.vue'),
children:[
{
path: 'about', component: about },
{
path: 'hello', component: Home },
]
}
]
配置页面
component/home.vue
<script setup>
import {
ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
<template>
<h1>home</h1>
</template>
<style scoped>
a {
color: #42b983;
}
</style>
component/about.vue
<script setup>
import {
ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
<template>
<h1>about</h1>
</template>
<style scoped>
a {
color: #42b983;
}
</style>
page.vue
<script setup>
import {
ref } from 'vue'
const count = ref(0)
</script>
<template>
<h1>{
{
$store.state.count}}</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/hello">Go to hello</router-link>
================
<router-link to="/about">Go to about</router-link>
<router-view></router-view>
</p>
</template>
<style scoped>
a {
color: #42b983;
}
</style>
6、创建node服务器进行服务端渲染
server.js
// This is a simple Node server that uses the built project.
//这是一个使用构建项目的简单节点服务器。
const path = require('path')
const express = require('express')
// This contains a list of static routes (assets)
//它包含静态路由(资产)的列表
const {
ssr } = require(`./dist/server/package.json`)
// The manifest is required for preloading assets
//预装资产需要清单
const manifest = require(`./dist/client/ssr-manifest.json`)
// This is the server renderer we just built
//这是我们刚刚构建的服务器渲染器
const {
default: renderPage } = require(`./dist/server`)
// const api = require('./api')
const server = express()
// Serve every static asset route 为每个静态路由提供服务
for (const asset of ssr.assets || []) {
server.use(
'/' + asset,
express.static(path.join(__dirname, `./dist/client/` + asset))
)
}
// Custom API to get data for each page
// See src/main.js to see how this is called
// api.forEach(({ route, handler, method = 'get' }) =>
// server[method](route, handler)
// )
// Everything else is treated as a "rendering request"
server.get('*', async (request, response) => {
const url =
request.protocol + '://' + request.get('host') + request.originalUrl
const {
html, status, statusText, headers } = await renderPage(url, {
manifest,
preload: true,
// Anything passed here will be available in the main hook
request,
response,
// initialState: { ... } // <- This would also be available
})
response.writeHead(status || 200, statusText || headers, headers)
response.end(html)
})
const port = 8080
console.log(`Server started: http://localhost:${
port}`)
server.listen(port)
7、代码链接
vite-ssr项目地址
https://download.csdn.net/download/qq_43548590/86947311
参考地址:https://github.com/frandiox/vite-ssr
、https://gitee.com/blueskyliu/vite-ssr