1.SSR简介
1.1 传统web开发
传统web开发,网页内容在服务端渲染完成,一次性传输到浏览器。
打开页面查看源码,浏览器拿到的是全部的dom结构
1.2 单页应用 Single Page App
单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS渲染出来,这种方式称为客户端渲染。
打开页面查看源码,浏览器拿到的仅有宿主元素#app,并没有内容
spa问题:seo、首屏加载速度
1.3 服务端渲染 Server Side Render
SSR解决方案,后端渲染出完整的首屏的dom结构返回,前端拿到的内容包括首屏及完整spa结构,应用激活后依然按照spa方式运行,这种页面渲染方式被称为服务端渲染 (server side render)
- 特点:首屏、seo
- 缺点: 复杂度、库的支持性、性能
场景:
seo:预渲染,检测是不是爬虫,首屏到达时间
spa项目已经做完了puppeteer (Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,用来模拟 Chrome 浏览器的运行)
爬虫实现puppeteer:让它直接从spa项目中爬出结果
nuxt.js全新项目
优化手段:静态化
Vue SSR指南:https://ssr.vuejs.org/zh/
2.Nuxt.js简介
2.1 Nuxt简介 Nuxt.js中文官网:https://www.nuxtjs.cn/
Nuxt.js是一个基于Vue.js的通用应用框架。通过客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染。
2.2 Nuxt.js特性:
基于Vue.js
自动代码分层
服务端渲染
强大的路由功能,支持异步数据
静态文件服务
HTML头部标签管理
2.3 nuxt渲染流程
一个完整的服务器请求到渲染(或用户通过 切换路由渲染页面)的流程:**
2.4nuxt安装:
运行create-nuxt-app
npx create-nuxt-app <项目名>
运行项目:
npm run dev
- 目录结构
nuxt 项目目录结构
4. 路由
1、基础路由
pages目录结构如下
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
.nuxt文件夹下的router.js自动生成的路由为
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'user',
path: '/user',
component: 'pages/user/index.vue'
},
{
name: 'user-one',
path: '/user/one',
component: 'pages/user/one.vue'
}
]
}
2、动态路由
在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
Nuxt.js 生成对应的路由配置表为:
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'
}
]
}
3、嵌套路由
创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。
pages/
--| detail/
-----| _id.vue
--| detail.vue
自动生成的路由配置:
routes: [{
path: "/detail",
component: _49f428be,
name: "detail",
children: [{
path: ":id?",
component: _247e7f2e,
name: "detail-id"
}]
}]
4、导航
添加路由导航,layouts/default.vue商品管理页
<nav>
<nuxt-link to="/">首页</nuxt-link>
</nav>
功能和router-link等效 禁用预加载行为:
<n-link no-prefetch>page not pre-fetched</n-link>
5. 视图
下图展示了Nuxt.js如何为指定的路由配置数据和视图
1、默认布局
可通过添加 layouts/default.vue 文件来扩展应用的默认布局。
默认布局的源码如下:
<template>
<nuxt /> //用于显示页面的主体内容。
</template>
2、自定义布局
在layout下创建自定义的.vue文件。
使用自定义布局:
<template>
<!-- Your template -->
</template>
<script>
export default {
layout: 'blog'
// page component definitions
}
</script>
3、自定义error页面
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">页面不存在</h1>
<h1 v-else>应用发生错误异常</h1>
<p>{{error}}</p>
<nuxt-link to="/">首 页</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error'],
layout:'blank'
}
</script>
4、页面
页面组件实际上是 Vue 组件,只不过 Nuxt.js 为这些组件添加了一些特殊的配置项(对应 Nuxt.js 提供的功能特性)以便你能快速开发通用应用。
给首页添加标题和meta等
<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() {
return {
title: "课程列表",
// vue-meta利用hid确定要更新meta
meta: [
{ name: "description", hid: "description", content: "set page meta" }
],
link: [{ rel: "favicon", href: "favicon.ico" }]
};
},
// and more functionality to discover
...
}
</script>
<style>
.red {
color: red;
}
</style>
6. 异步数据
异步数据获取
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: "商品1", price: 1000 },
{ id: 2, text: "商品2", price: 1000 }
];
// 配置路由
// 获取产品列表
router.get("/goods", ctx => {
ctx.body = {
ok: 1,
goods
};
});
// 产品详情
router.get("/detail", ctx => {
ctx.body = {
ok: 1,
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 = { ok: 1, token };
} else {
ctx.body = { ok: 0 };
}
});
// 解析post数据并注册路由
app.use(bodyparser());
// 注册路由
app.use(router.routes());
app.listen(8080, () => console.log('api服务已启动'))
整合axios
安装@nuxt/axios模块:
npm install @nuxtjs/axios -s
配置:nuxt.config.js
const pkg = require('./package')
module.exports = {
mode: 'universal',
// router配置
router: {},
/*
** Headers of the page
*/
head: {},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js modules
*/
modules: [
'@nuxtjs/axios'
],
axios: {
proxy: true
},
proxy: {
"/api": "http://localhost:8080"
},
build: {}
}
页面异步请求数据示例:
<template>
<div>
<h2>商品列表</h2>
<ul>
<li v-for="good in goods" :key="good.id">
<nuxt-link :to="`/detail/${good.id}`">
<span>{{good.text}}</span>
<span>¥{{good.price}}</span>
</nuxt-link>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
goods: []
}
},
async asyncData({ $axios, error }) {
const { ok, goods } = await $axios.$get("/api/goods");
if (ok) {
// 此处返回的数据会和data进行合并
return {
goods
};
}
// 错误处理
error({statusCode: 400, message: '数据查询失败请重试~'})
}
};
</script>
7. 中间件
中间件会在一个页面或一组页面渲染之前运行我们定义的函数,常用于权限控制、校验等任务。 范例代码:管理员页面保护,创建middleware/auth.js
export default function({ route, redirect, store }) {
// 上下文中通过store访问vuex中的全局状态
// 通过vuex中令牌存在与否判断是否登录
if (!store.state.user.token) {
redirect("/login?redirect="+route.path);
}
}
局部注册中间件,admin.vue
<template>
<div>
<h2>admin page</h2>
</div>
</template>
<script>
export default {
middleware: ['auth']
};
</script>
全局注册中间件:
module.exports = {
mode: 'universal',
// router配置
router: {
extendRoutes(routes, resolve) {
routes.push({
path: '/foo',
component: resolve(__dirname, 'pages/othername.vue')
})
},
middleware: ['auth']
},
}
8、状态管理VUEX
应用根目录下如果存在store目录,Nuxt.js将启用vuex状态树。定义各状态树时具名到处state,mutations,getters,actions即可。 范例:用户登录及登录状态保存,创建store/user.js
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) {
return this.$login(u).then(({ token }) => {
if (token) {
commit("init", token);
}
return getters.isLogin;
});
}
};
9、插件
Nuxt.js会在运行应用之前执行插件函数,需要引入或设置Vue插件、自定义模块和第三方模块时特别有用。 范例代码:接口注入,引用插件机制将服务接口注入组件实例、store实例中,创建plugins/api-inject.js
// 参数1上下文
// 参数2注入函数
export default ({ $axios }, inject) => {
// 将来this.$login
inject("login", user => {
return $axios.$post("/api/login", user);
});
};
注册插件,nuxt.config.js
plugins: [
'@/plugins/api-inject',
],
范例:添加请求拦截器附加token,创建plugins/interceptor.js
export default function({ $axios, store }) {
// onRequest是@nuxtjs/axios模块提供的帮助方法
$axios.onRequest(config => {
// 附加令牌
if (store.state.user.token) {
config.headers.Authorization = "Bearer " + store.state.user.token;
}
return config;
});
}
nuxtServerInit 通过在store的根模块中定义nuxtServerInit方法,Nuxt.js调用它的时候会将页面的上下文对象作为第2个参数传给他。当我们想将服务端的一些数据传到客户端时,这个方法非常好用。 范例:登录状态初始化,store/index.js
export const actions = {
nuxtServerInit({ commit }, { app }) {
// nuxt-universal-cookie用法如下
// app是server实例也就是koa实例
const token = app.$cookies.get("token");
// 表名是登录用户
if (token) {
console.log("nuxtServerInit: token:"+token);
commit("user/init", token);
}
}
};
安装依赖模块:cookie-universal-nuxt
npm i -S cookie-universal-nuxt
注册,nuxt.config.js
modules:["cookie-universal-nuxt"]
nuxtServerInit只能写在store/index.js
nuxtServerInit仅在服务端执行
10、发布部署
服务端渲染应用部署
先进行编译构建,然后再启动Nuxt服务
npm run build
npm start
生成内容在.nuxt/dis中
静态应用部署
Nuxt.js可依据路由配置将应用静态化,使得我们可以将应用部署至任何一个静态站点主机服务商。
npm run generate
注意渲染和接口服务器都需要处于启动状态
生成内容再dist中
11、Nuxt.js demo
Nuxt demo’s github地址 https://github.com/ajunc/nuxt-demo