Eson Wong's Blog

编程笔记,投资记录, 读书总结, 生活心得

0%

Nuxt.JS 介绍

Nuxt.js logo

Nuxt.js 是一个基于 Vue 可用来创建服务端渲染(SSR) Web 应用的框架。

为什么要服务器端渲染?

Web 前端的服务器端渲染(SSR)主要有以下好处:

  1. 更好的 SEO,搜索引擎可以爬取完全渲染的  HTML
  2. 更快的内容到达时间(减少访问的白屏时间)

相比非 SSR 的 Web 应用它也会带来一些缺点:

  1. 消耗服务器资源
  2. 对前端开发人员能力要求更高

Nuxt.js SSR 的流程

要做到上述优点还能保证用户交互体验,要做到在首次加载页面的时候才进行 SSR。下面的 Nuxt.js 的简要流程图反应的何时才会进行 SSR。

在浏览器进行站内导航时,无需 SSR。

Nuxt.js flow

完整的生命周期:https://nuxtjs.org/docs/concepts/nuxt-lifecycle/

开启服务器端渲染

Nuxt.js 也可以以单页应用模式和静态站点模式运行,默认以 SSR 模式运行,可在项目根目录下的配置文件 nuxt.config.js 中做修改

1
2
3
4
5
// nuxt.config.js

export default {
ssr: true, // default value
};

路由 和 Pages 文件夹

Nuxt.js 框架会 根据 pages 文件夹的结构自动生成路由的规则:

1
2
3
4
5
6
pages/
--| users.vue /users 路径下的页面的父组件,子组件渲染的地方使用 <NuxtChild/>
| users/
-----| index.vue 访问路径: /users
| search.vue 访问路径: /users/search
_.vue 访问路径: 未匹配的路径

_ 开头命名 vue 文件以定义 params, 可用在组件中以 this.$route.params 取得:

1
2
3
4
pages/
--| users.vue
--| users/
-----| _id.vue 访问路径: /users/123 获取参数 this.$route.params.id

⚠️ 使用 NuxtLink 组件在站内路由导航。

⚠ pages 文件夹不要写非页面组件,会生成不必要的路由配置。非页组件应写在 components 文件夹内。

问答: pages/user/list.vue 的访问路径是什么?

路程由的 base 配置

如果项目不是部署在域名根目录下,需要配置 router.base

1
2
3
4
5
6
7
// nuxt.config.js

export default {
router: {
base: process.env.ROUTER_BASE,
},
};

接口请求

由于 SSR,Nuxt.js 的请求库是在服务器端实例化的。所以不能简单的像单页应用一样,封装一个请求模块导出使用。这样使 Nuxt.js 在不同用户访问时共用一个请求实例,会出现用户数据泄漏的问题。

社区提供了 @nuxt/asiox 模块来处理这个问题。

使用:

  1. 安装 @nuxt/asiox

  2. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    // nuxt.config.js

    export default {
    modules: ["@nuxtjs/axios"],
    axios: {
    // Axios 选项
    },
    };
  3. 使用方法

    https://axios.nuxtjs.org/usage

    在 plugin 中添加拦截器:https://axios.nuxtjs.org/extend

为了方便在服务器和浏览器中方便和有统一的方法对 cookie 进行操作,我们可以使用社区提供的 cookie-universal-nuxt 包。

NPM

1
2
3
4
5
6
7
8
9
// nuxt.config.js

export default {
modules: [
['cookie-universal-nuxt', {
// options
}
]
}

它能在 Nuxt.js 的上下文 contextapp 和 Vue 组件实例中添加 $cookies 对象,其中有对 cookie 的操作方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// nuxt middleware
export default ({ app }) => {
app.$cookies.set("cookie-name", "cookie-value", {
path: "/",
maxAge: 60 * 60 * 24 * 7,
});
};

// client
this.$cookies.set("cookie-name", "cookie-value", {
path: "/",
maxAge: 60 * 60 * 24 * 7,
});

问答: cookit 设置成 httpOnly  要在哪一端获取 cookit , nodejs or 浏览器?

asyncData() 和 fetch()

Nuxt.js 为 Vue 添加了两个钩子

asyncData()

  • 限于页面组件
  • 它可以在服务端或浏览器端渲染页面前端调用,渲染组件前获取数据,返回的对象合并到前组件的 data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
</div>
</template>

<script>
export default {
async asyncData(context) {
// 服务器端
if (process.server) {
const { req, res, beforeNuxtRender } = context;
}
// 浏览器端
if (process.client) {
const { from, nuxtState } = context;
}

const post = await axios.get(`https://api.nuxtjs.dev/posts/${params.id}`);
return { post };
},
};
</script>

fetch()

  • 页面组件和非页面组件可用
  • 什么时候被调用:
    • ⚠️  组件每次加载前被调用(在服务端或切换至目标路由之前),渲染组件前填充应用的状态树(store)数据
    • 同 methods 一样被调用,这时可以于改变组件的 data

fetch 方法来获取数据填充应用的状态树(vuex)。为了让获取过程可以异步,你需要返回一个 Promise,Nuxt.js 会等这个 promise 完成后再渲染组件,如:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>

<script>
export default {
async fetch({ store, params }) {
let { data } = await axios.get("http://my-api/stars");
store.commit("setStars", data);
},
};
</script>

让 query 变更也触发 asyncData 和 fetch

1
2
3
4
5
6
7
8
9
10
11
// pages/somepage.vue

<template>
<h1>A Page</h1>
</template>

<script>
export default {
watchQuery: true,
};
</script>

问答:asyncData 在什类型的组件中使用,页面组件 / 非页面组件?

Store(VUEX)

state 使用 function 设置默认值。 Object 是引用类型数据,在服务器端会在多个用户访问时使用同一个对象引用来初始化 store。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// store/something.js

export const state = () => ({
list: [],
});

export const mutations = {
add(state, text) {
state.list.push({
text,
done: false,
});
},
remove(state, { todo }) {
state.list.splice(state.list.indexOf(todo), 1);
},
toggle(state, todo) {
todo.done = !todo.done;
},
};

其他最佳实践

UI 库的按需加载

减少静态资源的 size。

Element UI 和 Vant UI 的示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// nuxt.config.js

export default {
build: {
transpile: [/^element-ui/, /vant.*?less/],
babel: {
// Vant UI
plugins: [
[
"import",
{
libraryName: "vant",
style: (name) => `${name}/style/less`,
},
"vant",
],

// Element UI
[
"component",
{
libraryName: "element-ui",
styleLibraryName: "theme-chalk",
},
],
],
},
},
};
1
2
3
4
5
6
7
8
9
<template>
<el-button>Button</el-button>
</template>

<script>
import Vue from "vue";
import { Button } from "element-ui";
Vue.use(Button);
</script>

媒体资源 懒加载

使用  nuxt-lazy-load  在用户需要看见媒体时才加载资源。
https://gitlab.com/broj42/nuxt-lazy-load

1
2
3
4
5
// nuxt.config.js

export default {
modules: ["nuxt-lazy-load"],
};

默认配置所有媒体开启懒加载。

背景图片的懒加载:

1
<div lazy-background="~/assets/images/background-image.jpg">Content</div>

非懒加载的 img 标签加 data-not-lazy,避免出现一些 bug,比如图片裁切组件等等:

1
2
3
<audio controls="controls" data-not-lazy>
<source type="audio/mpeg" src="audio.mp3" />
</audio>

服务器端日志

添加服务器端日志,方便排查日志。
使用 nuxt-winson-log

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// nuxt.config.js

export default {
modules: [["nuxt-winston-log"]],
winstonLog: {
useDefaultLogger: false,
loggerOptions: {
format: combine(
label({ label: "Nuxt Server" }),
timestamp(),
printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
})
),
transports: [
new transports.Console(),
new transports.File({
filename: path.resolve(
process.cwd(),
"./logs",
`${process.env.NODE_ENV}.log`
),
maxsize: 5 * 1024 * 1024, // 单个日志文件大小
maxFiles: 20, // 最大文件数
}),
],
},
},
};

使用

1
2
3
4
5
6
7
8
9
// pages/page-a.vue

export default PageA {
asyncData(context) {
if(process.server) {
context.$winstonLog.info('Hello from asyncData on server')
}
}
}

欢迎关注我的其它发布渠道