如何用Node.js Express服务端渲染Vue模板?
2024-08-08 22:57:46
如何使用 Node.js Express 服务端渲染 Vue 模板
在构建现代 Web 应用时,兼顾 SEO 和用户体验至关重要。传统的客户端渲染(CSR)虽然能带来流畅的交互,但在 SEO 方面存在不足。服务端渲染(SSR)则能有效解决这个问题,同时提升首屏加载速度,改善用户体验。本文将带您深入了解如何使用 Node.js Express 框架服务端渲染 Vue.js 模板,并提供详细的代码示例,帮助您轻松上手 Vue SSR。
从问题出发:浏览器为何无法识别 Vue 组件?
您可能遇到过这样的情况:使用 Node.js Express 框架搭建服务器后,浏览器无法正确渲染 Vue 模板文件。经过分析,问题根源在于浏览器无法识别 Vue 单文件组件(SFC)的 MIME 类型。
Express 服务器默认将 .vue
文件识别为二进制文件 (application/octet-stream
),而浏览器在加载模块脚本时,期望接收到的是 JavaScript 模块脚本 (application/javascript
)。这种 MIME 类型的不匹配导致浏览器无法解析 Vue 组件文件,进而引发错误。
解决方案:借助官方工具,配置 Express 服务器
为了解决这个问题,我们需要对 Express 服务器进行配置,使其能够正确识别和处理 Vue 单文件组件。vue-server-renderer
和 vue-template-compiler
这两个官方库将成为我们的得力助手。
1. 安装必要依赖
首先,安装以下依赖包:
npm install vue vue-server-renderer vue-template-compiler --save
vue-server-renderer
:负责在服务器端渲染 Vue 组件。vue-template-compiler
:用于编译 Vue 单文件组件。
2. 修改服务器端代码
修改 server.js
文件,引入 vue-server-renderer
并创建渲染器实例:
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
const fs = require('fs');
const { createBundleRenderer } = require('vue-server-renderer');
// ... 其他代码 ...
// 创建 Vue SSR 渲染器
const renderer = createBundleRenderer({
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, 'public/views/index-vue.html'), 'utf-8'),
clientManifest: require('./dist/vue-ssr-client-manifest.json'),
serverBundle: require('./dist/vue-ssr-server-bundle.json')
});
// 修改路由处理逻辑
router.get('/', async (request, response) => {
if (tokenClass.checkToken(request.cookies.user_token) !== -1) {
const context = {
title: '首页',
meta: '<meta name="description" content="这是一个示例页面">'
};
try {
const html = await renderer.renderToString(context);
response.send(html);
} catch (error) {
console.error(error);
response.status(500).send('Internal Server Error');
}
} else {
response.redirect('/?token_expired');
}
});
// ... 其他代码 ...
这段代码主要完成了以下任务:
- 引入
vue-server-renderer
模块。 - 使用
createBundleRenderer
创建一个渲染器实例,指定服务器 bundle 文件路径、客户端清单文件以及模板文件。 - 在路由处理函数中,调用
renderer.renderToString
方法渲染 Vue 组件,并将渲染结果发送给客户端。
3. 创建 Vue SSR 配置文件
在项目根目录下创建 vue.config.js
文件,配置服务端和客户端打包:
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 isProduction = process.env.NODE_ENV !== 'development';
const baseConfig = {
outputDir: isProduction ? 'dist/prod' : 'dist',
configureWebpack: {
// ... 其他配置 ...
}
};
module.exports = merge(baseConfig, {
configureWebpack: config => {
if (TARGET_NODE) {
config.target = 'node';
config.output.libraryTarget = 'commonjs2';
config.output.filename = 'vue-ssr-server-bundle.json';
config.externals = nodeExternals({
whitelist: /\.css$/
});
config.plugins = (config.plugins || []).concat([
new VueSSRServerPlugin()
]);
} else {
config.entry = {
app: './src/entry-client.js'
};
config.output.filename = '[name].[chunkhash:8].js';
config.plugins = (config.plugins || []).concat([
new VueSSRClientPlugin()
]);
}
},
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
merge(options, {
optimizeSSR: true
});
return options;
});
}
});
这段配置主要完成了以下几项工作:
- 引入必要的插件,包括
VueSSRServerPlugin
、VueSSRClientPlugin
和nodeExternals
。 - 根据不同的打包目标 (TARGET_NODE),设置不同的配置,包括打包目标、输出文件名、外部依赖等。
- 使用
chainWebpack
对vue-loader
进行配置,开启optimizeSSR
选项以优化服务端渲染。
4. 创建服务端和客户端入口文件
创建 src/entry-server.js
作为服务端入口文件:
import { createApp } from './main';
export default context => {
return new Promise((resolve, reject) => {
const { app, router } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject({ code: 404 });
}
Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
store,
route: router.currentRoute
}))).then(() => {
context.state = store.state;
resolve(app);
}).catch(reject);
}, reject);
});
};
创建 src/entry-client.js
作为客户端入口文件:
import { createApp } from './main';
const { app, router } = createApp();
router.onReady(() => {
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to);
const prevMatched = router.getMatchedComponents(from);
let asyncDataHooks = [];
const activate = matched.filter((c, i) => {
return prevMatched.indexOf(c) === -1;
});
activate.forEach(c => {
if (c.asyncData) {
asyncDataHooks.push(c.asyncData({ store, route: to }));
}
});
Promise.all(asyncDataHooks).then(() => {
next();
}).catch(next);
});
app.$mount('#app');
});
if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('Service worker registered successfully:', registration);
}).catch(err => {
console.log('Service worker registration failed:', err);
});
});
}
服务端入口文件主要负责创建应用程序实例,处理路由,获取数据,并将最终渲染的应用程序实例返回给渲染器。客户端入口文件则负责在客户端创建应用程序实例,处理路由,并将其挂载到 DOM 上。
5. 构建 Vue SSR 应用
在 package.json
中添加以下脚本:
{
"scripts": {
"build:client": "vue-cli-service build --target client",
"build:server": "vue-cli-service build --target server --modern",
"build": "npm run build:client && npm run build:server",
"start": "node server.js"
}
}
然后依次运行以下命令构建 Vue SSR 应用:
npm run build:client
npm run build:server
6. 启动服务器
最后,运行以下命令启动服务器:
npm start
代码示例解析
以下是一些关键代码片段的解析:
1. 创建渲染器实例:
const renderer = createBundleRenderer({
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, 'public/views/index-vue.html'), 'utf-8'),
clientManifest: require('./dist/vue-ssr-client-manifest.json'),
serverBundle: require('./dist/vue-ssr-server-bundle.json')
});
这段代码使用 createBundleRenderer
方法创建了一个渲染器实例。其中:
runInNewContext
: 设置为false
,表示在与服务器进程相同的全局上下文中运行渲染器,可以提高性能。template
: 指定渲染使用的 HTML 模板文件。clientManifest
: 指定客户端构建生成的清单文件,包含了所有 JavaScript 和 CSS 文件的信息。serverBundle
: 指定服务器端构建生成的 bundle 文件,包含了 Vue 应用程序的代码。
2. 服务端渲染组件:
const html = await renderer.renderToString(context);
response.send(html);
这段代码调用渲染器的 renderToString
方法,将 Vue 组件渲染成 HTML 字符串,然后将 HTML 字符串发送给客户端。
3. 处理异步数据:
Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({
store,
route: router.currentRoute
}))).then(() => {
// ...
});
这段代码在服务端入口文件中处理异步数据。它遍历所有匹配的组件,如果组件定义了 asyncData
方法,则调用该方法获取数据,并将数据存储在 Vuex store 中。最后,等待所有异步数据获取完成后,才将渲染的应用程序实例返回给渲染器。
常见问题解答
1. 什么是服务端渲染 (SSR)?
服务端渲染是指在服务器端将 Vue 组件渲染成 HTML 字符串,然后将 HTML 字符串发送给客户端。这样做的好处是可以提高 SEO 和首屏加载速度。
2. 为什么需要服务端渲染?
- SEO 优化 : 搜索引擎爬虫可以直接抓取到服务端渲染生成的 HTML 内容,有利于 SEO 优化。
- 首屏加载速度更快 : 服务端渲染将 HTML 内容直接返回给客户端,浏览器无需等待 JavaScript 代码下载和执行即可渲染页面,因此首屏加载速度更快。
3. 什么是 vue-server-renderer
?
vue-server-renderer
是 Vue.js 官方提供的服务端渲染库,它提供了一系列 API 用于在 Node.js 环境中渲染 Vue 组件。
4. 如何处理服务端渲染中的异步数据?
可以使用组件的 asyncData
方法在服务端获取异步数据,并将数据存储在 Vuex store 中。
5. 如何调试服务端渲染?
可以使用 console.log
在服务端打印调试信息,也可以使用 Chrome DevTools 的 “Network” 面板查看网络请求。
希望本文能帮助您更好地理解和使用 Node.js Express 服务端渲染 Vue 模板。