随着互联网的发展,搜索引擎优化(SEO)越来越重要,尤其对于网站的流量和用户体验来说。在前端开发中,Vue.js 是一个非常流行的框架,但是在使用 Vue.js 开发单页应用(SPA)时,由于内容都是通过 JavaScript 动态生成的,搜索引擎无法抓取页面内容,导致 SEO 不友好。那么该怎么办呢?这时候,我们可以使用 Vue.js 中的服务器端渲染(SSR)来解决这个问题。
SSR 简介
服务器端渲染是指在服务器端将 Vue.js 组件渲染成 HTML 字符串,然后将其发送给浏览器。这样,搜索引擎可以抓取到页面的完整内容,提高了 SEO 的友好度。同时,由于页面的 HTML 字符串是在服务器端生成的,所以浏览器可以更快地展示页面内容,提高了用户体验。
Vue.js 中的 SSR 实现
Vue.js 提供了一个官方的 SSR 库 vue-server-renderer
,可以用于在 Node.js 环境中渲染 Vue.js 应用程序。下面我们来看一下如何使用 vue-server-renderer
实现 SSR。
安装依赖
首先,需要安装 vue-server-renderer
和 express
,可以使用 npm 进行安装:
npm install vue-server-renderer express --save
编写服务端代码
接下来,我们需要编写一个服务端代码,用于渲染 Vue.js 应用程序。下面是一个简单的示例:
// javascriptcn.com 代码示例 const Vue = require('vue') const express = require('express') const server = express() const renderer = require('vue-server-renderer').createRenderer() server.get('*', (req, res) => { const app = new Vue({ data: { message: 'Hello, Vue.js!' }, template: '<div>{{ message }}</div>' }) renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } res.end(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue.js SSR</title> </head> <body>${html}</body> </html> `) }) }) server.listen(3000, () => { console.log('Server started at http://localhost:3000') })
在上面的代码中,我们创建了一个 Express 服务器,并且定义了一个路由,用于处理所有的请求。在路由处理函数中,我们创建了一个 Vue.js 实例,并使用 vue-server-renderer
的 renderToString
方法将其渲染为 HTML 字符串。最后,我们将 HTML 字符串包装在一个完整的 HTML 页面中,发送给客户端。
运行服务端代码
最后,我们需要在命令行中运行服务端代码:
node server.js
然后,在浏览器中访问 http://localhost:3000
,就可以看到渲染后的 HTML 页面了。
优化 SEO
为了进一步优化 SEO,我们可以在服务端渲染时,将数据预取到组件中,然后在客户端渲染时,直接使用预取的数据,避免了客户端再次请求数据的过程,提高了页面的加载速度和用户体验。
数据预取
数据预取是指在渲染组件前,将组件所需的数据提前获取到。在 Vue.js 中,我们可以使用 created
钩子函数来获取数据。在服务端渲染时,我们可以在 server.js
中定义一个路由,用于获取数据,并将数据传递给组件。下面是一个简单的示例:
// javascriptcn.com 代码示例 server.get('/data', (req, res) => { const data = { message: 'Hello, Vue.js!' } res.json(data) }) server.get('*', (req, res) => { const app = new Vue({ data: { message: '' }, created() { if (typeof window === 'undefined') { // 在服务端渲染时,从服务器获取数据 axios.get('http://localhost:3000/data').then((res) => { this.message = res.data.message }) } else { // 在客户端渲染时,直接使用预取的数据 this.message = window.__INITIAL_STATE__.message } }, template: '<div>{{ message }}</div>' }) renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } res.end(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue.js SSR</title> <script>window.__INITIAL_STATE__ = ${JSON.stringify({ message: app.message })}</script> </head> <body>${html}</body> </html> `) }) })
在上面的代码中,我们定义了一个 /data
路由,用于获取数据。在组件的 created
钩子函数中,我们判断当前是在服务端渲染还是客户端渲染,然后分别获取数据。服务端渲染时,我们使用 axios 发送请求获取数据,然后将数据赋值给组件的 message
属性;客户端渲染时,我们直接使用预取的数据。最后,我们将预取的数据序列化成 JSON 字符串,并在 HTML 页面中使用 JavaScript 变量存储起来,以便客户端渲染时使用。
使用路由
在实际项目中,我们通常会使用路由来管理页面和数据。在 Vue.js 中,我们可以使用 vue-router
库来实现路由。在服务端渲染时,我们需要根据当前请求的 URL,匹配到对应的路由,并且获取路由所需的数据。下面是一个简单的示例:
// javascriptcn.com 代码示例 const router = require('./router') server.get('*', (req, res) => { const context = { url: req.url, message: '' } router.push(context.url) const matchedComponents = router.getMatchedComponents() if (!matchedComponents) { return res.status(404).end('Not Found') } const promises = [] matchedComponents.forEach((component) => { if (component.asyncData) { const promise = component.asyncData({ store, route: router.currentRoute }) promises.push(promise) } }) Promise.all(promises).then(() => { const app = new Vue({ router, store, render: (h) => h(App) }) renderer.renderToString(app, context, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } res.end(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue.js SSR</title> <script>window.__INITIAL_STATE__ = ${JSON.stringify(store.state)}</script> </head> <body>${html}</body> </html> `) }) }) })
在上面的代码中,我们首先定义了一个 context
对象,用于存储当前请求的 URL 和组件所需的数据。然后,使用 vue-router
的 push
方法,将当前 URL 推入路由栈中。接着,我们通过 vue-router
的 getMatchedComponents
方法,匹配到当前 URL 对应的路由组件。然后,遍历路由组件,如果组件有 asyncData
方法,就调用它,并将返回的 Promise 对象存入 promises
数组中。最后,使用 Promise.all
方法等待所有的 Promise 对象都返回后,再渲染 Vue.js 应用程序。
在上面的代码中,我们还使用了 Vuex,用于管理应用程序的状态。在服务端渲染时,我们需要将状态预取到组件中,并在客户端渲染时,直接使用预取的状态。下面是一个简单的示例:
// javascriptcn.com 代码示例 const store = require('./store') const app = new Vue({ store, router, render: (h) => h(App) }) server.get('*', (req, res) => { const context = { url: req.url } router.push(context.url) const matchedComponents = router.getMatchedComponents() if (!matchedComponents) { return res.status(404).end('Not Found') } const promises = [] matchedComponents.forEach((component) => { if (component.asyncData) { const promise = component.asyncData({ store, route: router.currentRoute }) promises.push(promise) } }) Promise.all(promises).then(() => { renderer.renderToString(app, context, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } res.end(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue.js SSR</title> <script>window.__INITIAL_STATE__ = ${JSON.stringify(store.state)}</script> </head> <body>${html}</body> </html> `) }) }) })
在上面的代码中,我们定义了一个 Vuex 的 store
,用于管理应用程序的状态。在服务端渲染时,我们需要将状态预取到组件中,然后在客户端渲染时,直接使用预取的状态。下面是一个简单的示例:
// javascriptcn.com 代码示例 const Home = { template: '<div>{{ message }}</div>', asyncData({ store }) { return store.dispatch('fetchMessage') }, computed: { message() { return this.$store.state.message } } } const store = new Vuex.Store({ state: { message: '' }, mutations: { setMessage(state, message) { state.message = message } }, actions: { fetchMessage({ commit }) { return axios.get('http://localhost:3000/data').then((res) => { commit('setMessage', res.data.message) }) } } })
在上面的代码中,我们定义了一个 Home
组件,用于显示 message
的值。在组件中,我们使用 asyncData
方法,获取 message
的值,并将其存入 Vuex 的状态中。在组件的 computed
属性中,我们直接使用 Vuex 的状态,渲染组件。
总结
通过本文的介绍,我们了解了 Vue.js 中的服务器端渲染(SSR),以及如何使用 vue-server-renderer
库实现 SSR。同时,我们还介绍了如何通过预取数据和使用路由,优化 SEO 和提高用户体验。希望本文能对大家理解和使用 Vue.js SSR 有所帮助。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/655e02aed2f5e1655d84d173