一、理解SSR概念
传统web开发
传统web开发,网页内容在服务端渲染完成,一次性传输到浏览器。
问题:服务器端渲染需要消耗更多的服务器端资源(cpu、内存等)
单页应用 Single Page App
单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS渲染出来,这种方式称为客户端渲染。
打开页面查看源码,浏览器拿到的仅有宿主元素#app,并没有内容。
- seo问题,对搜索引擎不友好
- 首屏加载时间较长
服务端渲染 Server Side Render
ssr解决方案,后端返回完整的首屏dom结构,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行,这种页面渲染方式被称为服务端渲染(Server Side Render)
ssr是一种折中方法,利用vue语法编写程序,还能在服务端渲染html内容。
二、实现Vue SSR
创建工程
vue create ssr
安装相关依赖
npm install vue-server-renderer express -D
启动脚本
创建一个express服务,将vue ssr集成进来,在根目录下创建server/ssr.js
// express
const express = require('express')
// vue
const Vue = require('vue')
// vue-server-renderer
const { createRenderer } = require('vue-server-renderer')
// 创建express实例
const app = express()
// 创建渲染器
const renderer = createRenderer()
// 待渲染页面
const vm = new Vue({
data: { name: 'sofiya' },
template: '<div>{{name}}</div>'
})
// express路由
app.get('/', async (req, res) => {
const html = await renderer.renderToString(vm)
res.send(html)
})
app.listen(3000, () => {
console.log('渲染服务器成功')
})
路由
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
// 导出工厂函数,可以返回新的Router实例
// 对于服务器,面对的是多个用户,不管面对的是单个用户还是多个用户,每次都应该是一个全新的router实例,这样对于不同的用户请求,不容易产生状态的污染
// 每个请求一个单独实例,避免状态污染
export default function createRouter () {
return new VueRouter({
mode: 'history',
routes
})
}
构建
对于客户端应用程序和服务器应用程序,我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」 然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。
1.vue创建实例
main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import store from './store'
Vue.config.productionTip = false
// context是服务器传递给vue实例的参数对象
export function createApp (context) {
// 1.获取router实例
const router = createRouter()
// 2.创建vue实例
const app = new Vue({
router,
store,
render: h => h(App)
})
return { app, router }
}
2.服务端入口
在src下创建entry-server.js
// 这个文件将来在服务器执行——只负责渲染首屏内容
// 给服务器提供一个方法,可以根据接收url设置路由地址,然后返回创建vue实例
import { createApp } from './main.js'
export default context => {
return new Promise((resolve, reject) => {
// 获取vue、router实例
const { app, router } = createApp(context)
// 跳转至首屏
router.push(context.url)
// onReady完成时,异步任务都会结束
router.onReady(() => {
resolve(app)
}, reject)
})
}
3.客户端入口
在src下创建entry-client.js
// 这个文件将来在浏览器执行
// 挂载创建vue实例
import { createApp } from './main.js'
// 创建vue实例
const { app, router } = createApp()
// 路由就绪,执行挂载(激活过程)
router.onReady(() => {
app.$mount('#app')
})
整体顺序是这样的:用户request=> express=> main.js=>router
webpack配置
1.安装相关依赖
npm install webpack-node-externals lodash.merge -D
2.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 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',
// target设置为node使webpack以Node适用的方式处理动态导入,
// 并且还会在编译Vue组件时告知`vue-loader`输出面向服务器代码。
target: TARGET_NODE ? 'node' : 'web',
// 是否模拟node全局变量
node: TARGET_NODE ? undefined : false,
output: {
// 此处使用Node风格导出模块
libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// 外置化应用程序依赖模块。可以使服务器构建速度更快,并生成较小的打包文件。
externals: TARGET_NODE
? nodeExternals({
// 不要外置化webpack需要处理的依赖模块。
// 可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件, // 还应该将修改`global`(例如polyfill)的依赖模块列入白名单
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 服务端默认文件名为 `vue-ssr-server-bundle.json`
// 客户端默认文件名为 `vue-ssr-client-manifest.json`。
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
merge(options, {
optimizeSSR: false
})
})
}
}
脚本配置
1.安装相关依赖
npm i cross-env -D
2.修改package.json文件
"scripts": {
"serve": "vue-cli-service serve",
"build:client": "vue-cli-service build",
"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
"build": "npm run build:server && npm run build:client",
"lint": "vue-cli-service lint"
}
3.执行打包
npm run build
宿主文件
直接修改public/index.html即可
<!-- vue-ssr-outlet -->这是约定,服务端渲染的插槽,必须这么写,挂载就有目标了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- vue-ssr-outlet -->
</body>
</html>
修改服务器启动文件
server/ssr.js
// 加载本地文件
const fs = require('fs')
// 处理url
const path = require('path')
const express = require('express')
const app = express()
// 获取绝对路径
const resolve = dir => {
return path.resolve(__dirname, dir)
}
// 第 1 步:开放dist/client目录,关闭默认下载index页的选项,不然到不了后面路由
app.use(express.static(resolve('../dist/client'), { index: false }))
// 第 2 步:获得一个createBundleRenderer
const { createBundleRenderer } = require('vue-server-renderer')
// 第 3 步:导入服务端打包文件
const bundle = require(resolve('../dist/server/vue-ssr-server-bundle.json'))
// 第 4 步:创建渲染器
const template = fs.readFileSync(resolve('../public/index.html'), 'utf-8')
const clientManifest = require(resolve('../dist/client/vue-ssr-client- manifest.json'))
const renderer = createBundleRenderer(bundle, {
runInNewContext: false, // https://ssr.vuejs.org/zh/api/#runinnewcontext
template, // 宿主文件
clientManifest // 客户端清单
})
app.get('*', async (req, res) => {
console.log(req.url)
// 设置url和title两个重要参数
const context = {
title: 'ssr test',
url: req.url
}
const html = await renderer.renderToString(context)
res.send(html)
})
const port = 3001
app.listen(port, function () {
// eslint-disable-next-line no-console
console.log(`server started at localhost:${port}`)
})
整合Vuex
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 导出工厂函数
export function createStore () {
return new Vuex.Store({
state: {
count: 108
},
mutations: {
add (state) {
state.count += 1
},
// 加一个初始化
init (state, count) {
state.count = count
}
},
actions: {
getCount ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('init', Math.random() * 198)
resolve()
}, 1000)
})
}
}
})
}
main.js
挂载store
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
Vue.config.productionTip = false
// context是服务器传递给vue实例的参数对象
export function createApp (context) {
// 1.获取router实例
const router = createRouter()
// 2. 获取vuex实例
const store = createStore()
// 3. 创建vue实例
const app = new Vue({
router,
store,
render: h => h(App)
})
return { app, router, store }
}
获取数据
如果应用依赖于一些异步数据,那么在开始渲染之前,需要先预取和解析好这些数据。
- 组件中
export default {
asyncData ({ store, route }) {
return store.dispatch('getCount')
}
}
- 服务端数据预取(
entry-server.js)
// 这个文件将来在服务器执行——只负责渲染首屏内容
// 给服务器提供一个方法,可以根据接收url设置路由地址,然后返回创建vue实例
import { createApp } from './main.js'
export default context => {
return new Promise((resolve, reject) => {
// 获取vue、router实例
const { app, router, store } = createApp(context)
// 跳转至首屏
router.push(context.url)
// onReady完成时,异步任务都会结束
router.onReady(() => {
// 获取匹配的路由组件数组
const matchedComponents = router.getMatchedComponents()
// 若无匹配则抛出异常
if (!matchedComponents.length) {
return reject(new Error({ code: 404 }))
}
// 对所有匹配的路由组件调用可能存在的`asyncData()`
Promise.all(
matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute
})
}
})
)
.then(() => {
// 所有预取钩子 resolve 后,
// store 已经填充入渲染应用所需状态
// 将状态附加到上下文,且 `template` 选项用于 renderer 时,
// 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
context.state = store.state
resolve(app)
})
.catch(reject)
}, reject)
})
}
- 客户端在挂载到应用程序之前,store就应该获取到状态(
entry-client.js)
// 这个文件将来在浏览器执行
// 挂载创建vue实例
import { createApp } from './main.js'
// 创建vue实例
const { app, router, store } = createApp()
// 当使用 template 时,context.state 将作为 window.__INITIAL_STATE__ 状态自动嵌入到最 终的 HTML
// 在客户端挂载到应用程序之前,store 就应该获取到状态:
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
// 路由就绪,执行挂载(激活过程)
router.onReady(() => {
app.$mount('#app')
})
- 客户端数据预取处理(
main.js)
// 确保客户端每个组件如果有asyncData,要执行它
Vue.mixin({
beforeMount () {
const { asyncData } = this.$options
if (asyncData) {
// 将获取数据操作分配给 promise
// 以便在组件中,我们可以在数据准备就绪后
// 通过运行 `this.dataPromise.then(...)` 来执行其他任务
this.dataPromise = asyncData({
store: this.$store,
route: this.$roure
})
}
}
})
总结
- 优点
- seo
- 首屏内容到达时间短
- 缺点
- 负载大
- 开发条件限制
- 构建部署:nodejs环境
- 技术选型
- 首屏内容到达时间重要程度如何?
- seo是否是重要需求?仅有少量营销页面需要seo,考虑预渲染
pre-render - 已经完成spa项目,重构量很大怎么办?可以考虑
Puppeteer - 高流量情况是否做好充足服务器负载准备、缓存策略制定
三、nuxt.js实现ssr
Nuxt.js 是一个基于 Vue.js 的通用应用框架。nuxt特性
- 基于 Vue.js
- 自动代码分层
- 服务端渲染
- 强大的路由功能,支持异步数据
- 静态文件服务
- ES2015+ 语法支持
- 打包和压缩 JS 和 CSS
- HTML 头部标签管理
- 本地开发支持热加载
- 集成 ESLint
- 支持各种样式预处理器: SASS、LESS、 Stylus 等等
- 支持 HTTP/2 推送
nuxt安装
- 安装
npx create-nuxt-app <项目名>
-
选项
-
运行项目
npm run dev
路由
1.路由生成
Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。
生成的路由文件在.nuxt/router.js
2.导航
文件:layout/default.vue
<template>
<div>
<nuxt-link to="/">
首页
</nuxt-link>
<NLlink to="/admin">
管理
</NLlink>
<n-link to="/cart">
购物车
</n-link>
<Nuxt />
</div>
</template>
以上3种效果等同
3.动态路由
在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'users-id',
path: '/users/:id?',
component: 'pages/users/_id.vue'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
},
{
name: 'slug-comments',
path: '/:slug/comments',
component: 'pages/_slug/comments.vue'
}
]
}
你会发现名称为 users-id 的路由路径带有 :id? 参数,表示该路由是可选的。如果你想将它设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。
4.嵌套路由
创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。
Warning: 别忘了在父组件(.vue文件) 内增加 用于显示子视图内容。
pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
router: {
routes: [
{
path: '/users',
component: 'pages/users.vue',
children: [
{
path: '',
component: 'pages/users/index.vue',
name: 'users'
},
{
path: ':id',
component: 'pages/users/_id.vue',
name: 'users-id'
}
]
}
]
}
5.配置路由
要扩展 Nuxt.js 创建的路由,我们需要在nuxt.config.js修改:
// nuxt.config.js
export default {
router: {
extendRoutes (routes, resolve) {
routes.push({
name: "foo",
path: "/foo",
component: resolve(__dirname, "pages/custom.vue")
}); }
} }
视图
Nuxt.js 应用中为指定的路由配置数据和视图,包括应用模板、页面、布局和HTML头部等内容。
布局
- 默认布局
可通过添加 layouts/default.vue 文件来扩展应用的默认布局。
<template>
<nuxt/>
</template>
提示: 别忘了在布局文件中添加 组件用于显示页面的主体内容。
- 自定义布局
layouts 目录中的每个文件 (顶级) 都将创建一个可通过页面组件中的 layout 属性访问的自定义布局。
假设我们要创建一个 博客布局 并将其保存到layouts/blog.vue:
<template>
<div>
<div>我的博客导航栏在这里</div>
<nuxt/>
</div>
</template>
然后我们必须告诉页面 (即pages/posts.vue) 使用您的自定义布局:
<template>
<!-- Your template -->
</template>
<script>
export default {
layout: 'blog'
// page component definitions
}
</script>
- 错误页面
你可以通过编辑 layouts/error.vue 文件来定制化错误页面.
警告: 虽然此文件放在 layouts 文件夹中, 但应该将它看作是一个 页面(page).
这个布局文件不需要包含 标签。你可以把这个布局文件当成是显示应用错误(404,500等)的组件。
默认的错误页面源码在 这里.
举一个个性化错误页面的例子 layouts/error.vue:
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">页面不存在</h1>
<h1 v-else>应用发生错误异常</h1>
<nuxt-link to="/">首 页</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'blog' // 你可以为错误页面指定自定义的布局
}
</script>
页面
页面组件实际上是 Vue 组件,只不过 Nuxt.js 为这些组件添加了一些特殊的配置项(对应 Nuxt.js 提供的功能特性)以便你能快速开发通用应用。
<template>
<h1 class="red">Hello {{ name }}!</h1>
</template>
<script>
export default {
asyncData (context) {
// called every time before loading the component
return { name: 'World' }
},
fetch () {
// The fetch method is used to fill the store before rendering the page
},
head () {
// Set Meta Tags for this Page
},
// and more functionality to discover
...
}
</script>
<style>
.red {
color: red;
}
</style>
Nuxt.js 为页面提供的特殊配置项:
- asyncData:最重要的一个键, 支持 异步数据处理,另外该方法的第一个参数为当前页面组件的 上下文对象。
- fetch: 与
asyncData方法类似,用于在渲染页面之前获取数据填充应用的状态树(store)。不同的是fetch方法不会设置组件的数据。 - head: 配置当前页面的 Meta 标签
- layout: 指定当前页面使用的布局(layouts 根目录下的布局文件)。
- loading: 如果设置为false,则阻止页面自动调用
this.$nuxt.$loading.finish()和this.$nuxt.$loading.start(),您可以手动控制它,请看例子,仅适用于在nuxt.config.js中设置loading的情况下。 - transition: 指定页面切换的过渡动效。
- scrollToTop: 布尔值,默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景。
- validate: 校验方法用于校验 动态路由的参数。
- middleware: 指定页面的中间件,中间件会在页面渲染之前被调用。
默认Meta标签
Nuxt.js 允许你在 nuxt.config.js 里定义应用所需的所有默认 meta 标签,在 head 字段里配置就可以了:
head: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
],
link: [
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
]
}
异步数据获取
asyncData 方法使得我们可以在设置组件数据之前异步获取或处理数据。
准备接口
- 安装相关依赖
npm i koa-router koa-bodyparser -S
- 接口文件
server/api.js
// 此文件并非nuxt生成,它为演示项目提供数据服务接口
const Koa = require('koa')
const app = new Koa()
const bodyparser = require('koa-bodyparser')
const router = require('koa-router')({ prefix: '/api' })
// 设置cookie加密秘钥
app.keys = ['some secret', 'another secret']
const goods = [
{ id: 1, text: 'Web全栈架构师', price: 1000 },
{ id: 2, text: 'Python架构师', price: 1000 }
]
// 配置路由
// 获取产品列表
// http://localhost:8080/api/goods
router.get('/goods', (ctx) => {
ctx.body = {
code: 0,
goods
}
})
// 产品详情
router.get('/detail', (ctx) => {
ctx.body = {
code: 0,
data: goods.find(good => good.id === ctx.query.id)
}
})
// 登录
router.post('/login', (ctx) => {
const user = ctx.request.body
if (user.username === 'jerry' && user.password === '123') {
// 将token存入cookie
const token = 'a mock token'
ctx.cookies.set('token', token)
ctx.body = { code: 0, token }
} else {
ctx.body = { code: -1 }
}
})
// 解析post数据并注册路由
app.use(bodyparser())
// 注册路由
app.use(router.routes())
app.listen(8090, () => console.log('api服务已启动'))
整合axios
- 安装
@nuxt/axios模块
npm i @nuxtjs/axios -S
- 配置
nuxt.config.js
modules: ['@nuxtjs/axios'],
axios: {
proxy: true
},
proxy: {
'/api': 'http://localhost:8090'
},
解决跨域,使用
proxy代理
请求数据
async asyncData ({ $axios, error }) {
// 注意:这里不能使用this,因为此刻组件实例还未创建
// $axios由nuxtjs/axios模块注入
// $get是axios模块封装的类fetch风格api
const { code, goods } = await $axios.$get('/api/goods')
console.log(code, goods)
if (code === 0) {
// 这里返回的数据将来会和data合并
return { goods }
}
// 错误处理
error({ statusCode: 400, message: '数据请求失败' })
}
中间件
中间件会在一个页面或一组页面渲染之前运行我们定义的函数,常用于权限控制、校验等任务。
- 创建中间件
middleware/auth.js
// 定义中间件,参数是nuxt提供的上下文对象
export default function ({ route, redirect, store }) {
// 上下文中通过store访问vuex中的全局状态
if (!store.state.user.token) {
redirect('/login?redirect=' + route.path)
}
}
- 使用
【页面级】
// admin.vue——必须登录后才能访问admin页面
export default {
// 页面级-中间件
middleware: ['auth']
}
【路由级】
// nuxt.config.js
router: {
middleware: ['auth']
}
每访问一个路由,都会先执行中间件
状态管理Vuex
Nuxt.js 会尝试找到src目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:
- 引用
vuex模块 - 将
vuex模块 加到vendors构建配置中去 - 设置
Vue根实例的store配置项
使用状态树两种方式
Nuxt.js 支持两种使用 store 的方式,你可以择一使用:
- 模块方式:
store目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)
export const state = () => ({
token: ''
})
export const mutations = {
init (state, token) {
state.token = token
}
}
export const getters = {
isLogin (state) {
return !!state.token
}
}
export const actions = {
login ({ commit, getters }, u) {
// this.$axios由@nuxt/axios模块提供
return this.$axios.$post('/api/login', u).then(({ token }) => {
if (token) {
commit('init', token)
}
return getters.isLogin
})
}
}
- Classic(不建议使用): store/index.js返回创建Vuex.Store实例的方法。
案例:登录页面使用
<template>
<div>
<h1>用户登录</h1>
<input v-model="user.username" type="text">
<input v-model="user.password" type="password">
<button @click="submit">
登录
</button>
</div>
</template>
<script>
export default {
data () {
return {
user: {
username: '',
password: ''
}
}
},
methods: {
submit () {
this.$store.dispatch('user/login', this.user).then((res) => {
if (res) {
const redirect = this.$route.query.redirect || '/'
this.$router.push(redirect)
}
})
}
}
}
</script>
nuxtServerInit 方法
如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第2个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。
export const actions = {
nuxtServerInit ({ commit }, { app }) {
// 登录后,服务端将token写入了cookie中,前端通过`cookie-universal-nuxt`取出token来
const token = app.$cookies.get('token')
if (token) {
console.log('nuxtServerInit: token:' + token)
commit('user/init', token)
}
}
}
- 安装依赖模块
npm i -S cookie-universal-nuxt
- 配置
nuxt.config.js
modules: [
'cookie-universal-nuxt'
],
注意:
必须定义到store/index.js文件中
nuxtServerInit仅在服务端执行
插件
Nuxt.js允许您在运行Vue.js应用程序之前执行js插件。这在您需要使用自己的库或第三方模块时特别有用。
插件只执行一次,时间点比较靠前
使用第三方插件plugin/element-ui.js
import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
export default () => {
Vue.use(Element, { locale })
}
axios请求拦截器plugin/interceptor.js
export default function ({ $axios, store }) {
// 为$axios实例添加一个请求事件监听
// onRequest是nuxtjs/axios模块提供的帮助方法
$axios.onRequest((config) => {
if (store.state.user.token) {
config.headers.Authorization = 'Bearer ' + store.state.user.token
}
return config
})
}
配置nuxt.config.js
plugins: [
'@/plugins/element-ui',
'@/plugins/interceptor'
]
发布部署
服务端渲染应用部署
部署 Nuxt.js 服务端渲染的应用不能直接使用 nuxt 命令,而应该先进行编译构建,然后再启动 Nuxt 服务,可通过以下两个命令来完成:
nuxt build
nuxt start
静态应用部署
Nuxt.js 可依据路由配置将应用静态化,使得我们可以将应用部署至任何一个静态站点主机服务商。
可利用下面的命令生成应用的静态目录和文件:
npm run generate
四、总结
- 优点
- spa+ssr通用框架
- 约定路由
- 代码分层
- 优化到位
- 缺点
若是ssr模式,前面需要考虑的因素都存在
- nuxt流程图
学完nuxt,再来看nuxt执行的流程图就会一目了然了