-
一、Vue为啥要使用SSR
本人是菜鸟一个,这个文章可能有误导性
服务器要展示vue的页面,需要webpack编译的两类文件,html模板和main.js,main.js中包含vue框架,我们写的业务逻辑,css,图片等
如图 webpack编译的.js文件在dist中,然后views中引入这些文件,
原始模板文件是在这样的
views中webpack编译后的mian.html
这就是服务器返回浏览器端的HTML代码
浏览器加载之后的HTML
服务加载过来的HTML和展示的HTML不一样,是由于加载的.js 文件中的代码对当前HTML进行了操作,这个操作是在浏览器端的,如果不让.js加载,
然后就和服务器加载过来的模板是一样的,
由于内容都是本地js被添加上去的,所以当HTML加载过来的瞬间,其实就是一个没有啥内容的模板,这是不利于SEO的,百度等搜索引擎无法获取到,对于js后添加的内容,他只能看到加载过来的模板。
SSR就是提前在服务端将上面 id=“app” 的这个模板变提成HTML,后面的交互操作依然由浏览器请求的.js等文件接手,
原始模板:
<body>
<!--vue-ssr-outlet-->
</body>
服务端获取编译结果,替换模板:
let { createBundleRenderer } = require("vue-server-renderer");
// 创建渲染器
let renderer = createBundleRenderer(path.join(root, "server/bundle.json"), {
// 模板文件
template: fs.readFileSync(path.join(root, "views/index.html"), "utf-8"), // 这里必须设置为utf-8 ,并且都必须使用绝对路径
});
// 注意.js一定要先解析,不然会被下面的覆盖,都填充为模板
app.use("/static/", express.static("./static/"));
// 定义路由
// 下面这种最好前端一样,统一为为/xxx,而不是使用*,不然所谓请求文件都会被填充为模板
app.get("*", (req, res) => {
/// 渲染字符串
renderer
.renderToString({
url: req.url,
title: "xxxx",
seo: `
<meta name="keywords" content="xxxx">
<meta name="description" content="xxx">
`,
})
.then(
(html) => {
/// 成功
// console.log(html);
res.end(html);
},
(err) => {
// 失败
console.log(err);
if (err.code == 404) {
res.status(404).end(err.msg + "=>" + req.url);
} else {
res.status(500).end("服务器内部错误");
}
}
);
});
服务器替换后的模板就是已经渲染的HTML字符串了:
<body><!-- <h1> HTML文件中中的
标签 </h1>
<div id="app">
<h2>xxx</h2>
</div> --><div id="app" data-server-rendered="true"><nav><h1>app part --helllo--10</h1> <a href="/" aria-current="page" class="router-link-exact-active router-link-active">Home</a>
|
<a href="/about">About</a>|
<a href="/test">Test</a>|
- 路由同步
但是还有一个问题,就是如果是使用了SSR,那么必须使用history模式,默认模式是不刷新页面,渲染好的HTML数据无法从服务器加载过来,没有seo的效果。如果使用history模式,当前前端请求一个路由的时候,后端是不知道这个路由该返回这个路径对应渲染的HTML数据的,所以服务端渲染HTML时要得到请求的路径,对这个路径的页面HTML字符串渲染返回,
// 下面这种最好前端一样,统一为为/xxx,而不是使用*,不然所有请求文件都会被填充
app.get("*", (req, res) => {
/// 渲染字符串
renderer
.renderToString({
url: req.url,//传入对应的url
title: "xxxx",
seo: `
<meta name="keywords" content="xxxx">
<meta name="description" content="xxx">
`,
})
.then(
(html) => {
/// 成功
console.log(html);
res.end(html);// 返回最终填充好的HTML
},
(err) => {
// 失败
console.log(err);
if (err.code == 404) {
res.status(404).end(err.msg + "=>" + req.url);
} else {
res.status(500).end("服务器内部错误");
}
}
);
});
获得url后,查看url是否在路由中,
import app from "./main";
// 引入路由实例化对象
import router from "./router/index";
///export default app;
// 暴露接口
export default (data) =>
new Promise((resolve, reject) => {
// data就在rederTo中传递的url
console.log("url", data.url);
// 解析路由
router.onReady(() => {
// 查看是否有匹配的页面
console.log(router.getMatchedComponents());
// 如果有匹配的页面
if (router.getMatchedComponents().length) {
// 成功
resolve(app);
} else {
// 没有匹配页面
reject({
code: 404,
msg: "没有找到页面",
});
}
});
// 解析路由
router.push(data.url);
});