nuxt 服务端渲染

Nuxt.js 是一个基于 Vue.js(2.0) 的服务端渲染应用框架,它可以帮我们轻松的实现同构应用。

1
2
3
4
npm i create-nuxt-app -g
create-nuxt-app my-nuxt-demo
cd my-nuxt-demo
npm run dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create-nuxt-app v4.0.0
✨ Generating Nuxt.js project in my-nuxt-demo
? Project name: my-nuxt-demo
? Programming language: JavaScript
? Package manager: Npm
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? What is your GitHub username? sacgog
? Version control system: None

nuxt的项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
└─my-nuxt-demo
├─.nuxt // Nuxt自动生成,临时的用于编辑的文件,build
├─assets // 用于组织未编译的静态资源如LESS、SASS或JavaScript,对于不需要通过 Webpack 处理的静态资源文件,可以放置在 static 目录中
├─components // 用于自己编写的Vue组件,比如日历组件、分页组件
├─layouts // 布局目录,用于组织应用的布局组件,不可更改
├─middleware // 用于存放中间件
├─node_modules
├─pages // 用于组织应用的路由及视图,Nuxt.js根据该目录结构自动生成对应的路由配置,文件名不可更改
├─plugins // 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。
├─static // 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。文件夹名不可更改。
└─store // 用于组织应用的Vuex 状态管理。文件夹名不可更改。
├─.editorconfig // 开发工具格式配置
├─.eslintrc.js // ESLint的配置文件,用于检查代码格式
├─.gitignore // 配置git忽略文件
├─nuxt.config.js // 用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。文件名不可更改。
├─package-lock.json // npm自动生成,用于帮助package的统一设置的,yarn也有相同的操作
├─package.json // npm 包管理配置文件
├─README.md

路由

nuxt根据pages文件夹创建路由

例如:

  • /test/index.vue 创建 https://www.acgog.com/test
  • /test/a.vue 创建 https://www.acgog.com/test/a
  • /test/_id.vue 创建 https://www.acgog.com/test/{id}
  • PS:参数页面文件名必须按格式 _参数名

路由跳转

1
2
3
4
5
<nuxt-link :to="{name:'article-id',params:{id:item.id}}">
{{item.title}}
<span>发布日期:{{item.time}} </span>
</nuxt-link>
<!-- nuxt-link会自动生成一个a标记 -->

js带参数跳转

1
2
3
4
this.$router.push({
name: 'list-id',
params:{id:1},
})

接收页面方法

1
this.$route.params

使用element ui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cnpm i element-ui -S-d

// 安装完了以后
// 找到 plugins文件夹,新建一个js文件,名字叫 ElementUI.js
// 然后再里面输入:
import Vue from 'vue'
import ElementUI from 'element-ui'
Vue.use(ElementUI)

// 然后打开 nuxt.config.js添加下面这些:
css: [
'element-ui/lib/theme-chalk/index.css'
],
plugins: [
{src: '~/plugins/ElementUI', ssr: true }
],
build: {
vendor: ['element-ui']
}

axios

注意这里的写法,是@nuxtjs/axios而不是直接axios,否则你下面配置的时候会提示找不到axios

1
cnpm install @nuxtjs/axios

unxtjsajax,你先别往你那个异步上去思考,其实这里面所有的ajax最后都会形成页面。你别想着,我一点按钮,调用一个方法,然后再ajax去加载数据。因为我们最后全部都会生成静态,所以任何的获取数据的操作,最后都会变成页面的跳转。

所以,官方给了一套写法,你必须按照这个去写,
并且这里的ajax会再页面渲染之前就执行。这个东西跟生命周期这些都是平级的。

1
2
3
4
5
6
7
8
9
10
11
// 这里引入context是上下文参数,代替了this,
// 因为在asyncData方法是在组件初始化时调用,所以没法通过this来引用组件实例对象。
asyncData(context) {
return context.$axios.get('index/query')
.then(res => {
//获取到内容
console.log(res.data.result[0].logo);
//赋值
return {txt:res.data.result[0].logo};
})
},

然后打开nuxt.config.js
开始修改,先把代理这块写好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
modules: [
'@nuxtjs/axios'
],
axios: {
//baseURL:"api.dangyunlong.com", //设置统一的基础url,线上环境关闭代理时使用它。
proxy: true, // 表示开启代理
prefix: '/api', // 表示给请求url加个前缀 /api
credentials: true // 表示跨域请求时是否需要使用凭证
},
proxy: {
'/api': {
target: 'http://localhost:3002/', // 目标接口域名
pathRewrite: {
'^/api': '/', // 把 /api 替换成 /
changeOrigin: true // 表示是否跨域
},
}
},

然后下面,加上axios,防止二次打包

1
2
3
4
5
6
7
8
9
build: {
vendor: ['element-ui','axios'],
postcss: {
preset: {
features: {
customProperties: false
}
}
},

如何同时发起多个请求

1
2
3
4
5
6
7
8
9
async asyncData({$axios}){
let index = await $axios.get("index/query");
let list = await $axios.get("index/query/list");
return {
index: index.data.result[0],
list: list.data.result
}
},
// 必须使用这种方式才能同时发起多个请求。

如果通过接收route的参数,发送带参数的请求

1
2
3
4
5
6
7
8
async asyncData ({params,$axios}) {
//这里有几个巨坑的地方 这里的params 就是指route.params
//这里的store就是指 $store
let data = await $axios.get(`article/query?id=${params.id}`);
return {
data: data.data.result[0]
}
}

vuex

nuxt加载vuex非常简单。甚至都无需我们去修改任何的配置文件,因为nuxt中已经自带了vuex:我们只需要再store文件夹中新建一个index.js输入下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = () => new Vuex.Store({
state: {
counter: 0
},
mutations: {
increment (state) {
state.counter++
}
}
})
export default store

然后页面中调用就完事了。

1
<button @click="$store.commit('increment')">{{ $store.state.counter }}</button>

其他插件

其实其他插件的使用方法跟elementui的一样
假如我们想使用 vue-notifications 显示应用的通知信息,我们需要在程序运行前配置好这个插件。
首先增加文件 plugins/vue-notifications.js

1
2
3
import Vue from 'vue'
import VueNotifications from 'vue-notifications'
Vue.use(VueNotifications)

然后, 在 nuxt.config.js 内配置 plugins 如下:

1
2
3
module.exports = {
plugins: ['~/plugins/vue-notifications']
}

jquery

1
2
cnpm install jquery@3.1.0 -S-d
// 然后nuxt.config中加入这一段

然后nuxt.config中加入这一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
build: {
vendor: ['jquery'],
plugins: [
new webpack.ProvidePlugin({
'$': 'jquery'
})
],
postcss: {
preset: {
features: {
customProperties: false
}
}
},
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
}

less

nuxt中并不包含less解析工具

1
cnpm install less less-loader -S-d

安装完毕以后,跟vuecli中使用的方法一样。

1
<style lang="less"><style>

模板

打开layouts这个文件夹,里面都是你在pages中可以引入的模板,你也可以自己创建一个模板。

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<div>top</div>
<nuxt>
<div>bottom</div>
</div>
</template>
<script>
export default{

}
</script>

每一个pages中的vue文件都有一个layout属性,把我们要用的模板填进去。(模版文件名)

全局组件

plugins文件夹目录建一个component.js文件

1
2
3
import Vue from 'vue'
import 组件 from '@/components/组件.vue'
Vue.component("xxx",组件);

打开 nuxt.config.js

1
2
3
4
5
module.exports = {
plugins:[
'@/plugins/component.js',
]
}

其他操作

修改head
nuxt本身支持一个head方法,放到跟生命周期同级即可。

1
2
3
4
5
6
7
8
9
10
head () {
return {
title: this.title,
meta: [{
hid: 'description',
name: 'description',
content: 'content',
}]
}
}

加载图片

页面中加载

1
2
<!--加载图片-->
<img src="~/assets/image.png">

css中加载

1
2
3
.{
background:url("~assets/banner.svg")
}

nuxt generate

generate:构建应用程序并生成每个路径作为HTML文件(用于静态托管)

  • 使用generate打包后每个对应的页面都会生成一个html,你在打包的时候不能关闭后台,他会请求后台数据生成首屏的数据
  • 这样打包有一个弊端,当你首屏的数据发生更改的时候,对不起,他还是显示的是之前的数据,要想改变的话,需要重新打包发布才行。
  • 所以,如果你的首屏是动态的就不建议使用这种打包方式了。
  • 有人看到每个页面都生成了HTML页面,以为不再请求后台(我最开始就是这样认为的),实际上不是的,他的首屏数据之前渲染好了,但是其它数据还是从后台获取,比如翻页,第二页数据是从新请求后台的,你再次返回第一页也是再次请求的。

获取数据

官方:不能在组件中使用异步。

但是你可以在父级使用异步,在通过组件传值的方法传给你的组件。

首页拿到数据以后,先给自己的data暂存一下,然后在通过created周期,通过commit发送给vuex即可,或者通过props发送给组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
asyncData(context) {
return context.$axios.get('index/query')
.then(res => {
//赋值
return {index:res.data.result[0]};
})
},
created:function(){
//首页数据提交给vuex
this.$store.commit('getIndex',this.index)
//console.log("页面加载完毕!");
},
methods: {
async fetchSomething() {
const ip = await this.$axios.$get('http://icanhazip.com')
this.ip = ip
}
}

store

1
2
3
4
5
6
7
8
9
// In store
{
actions: {
async getIP ({ commit }) {
const ip = await this.$axios.$get('http://icanhazip.com')
commit('SET_IP', ip)
}
}
}

axios配置

nuxt.config.js

1
2
3
4
5
{
axios: {
// Axios options here
}
}

拦截

nuxt.config.js

1
2
3
4
5
export default {
plugins: [
'~/plugins/axios'
]
}

创建plugins/axios.js

1
2
3
4
5
6
7
8
9
10
11
12
export default function ({ $axios, redirect }) {
$axios.onRequest(config => {
console.log('Making request to ' + config.url)
})

$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}

setToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Adds header: `Authorization: 123` to all requests
this.$axios.setToken('123')

// Overrides `Authorization` header with new value
this.$axios.setToken('456')

// Adds header: `Authorization: Bearer 123` to all requests
this.$axios.setToken('123', 'Bearer')

// Adds header: `Authorization: Bearer 123` to only post and delete requests
this.$axios.setToken('123', 'Bearer', ['post', 'delete'])

// Removes default Authorization header from `common` scope (all requests)
this.$axios.setToken(false)

重写axios

/plugin/api.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default function ({ $axios }, inject) {
// Create a custom axios instance
const api = $axios.create({
headers: {
common: {
Accept: 'text/plain, */*'
}
}
})

// Set baseURL to something different
api.setBaseURL('https://my_api.com')

// Inject to context as $api
inject('api', api)
}

nuxt.config.js

1
2
3
export default {
plugins: ['~/plugins/api.js']
}

然后您的组件可以使用新的Axios实例:

1
2
3
4
5
6
7
8
export default {
fetch() {
this.$api.get(...)
},
asyncData({ $api }) {
$api.get(...)
}
}

全局样式

nuxt.config.js

1
2
3
4
5
6
7
8
css: [
'@/assets/css/xxx.less'
]

build: {
// 将css单独打包成一个文件,默认的是全部加载到页面
extractCSS: { allChunks: true },
},

环境区分

1
npm install --save-dev cross-env

根目录创建env.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
// 开发测试环境
DEV: {
NODE_ENV: 'DEV',
API_URL: 'http://18.163.xxx.xxx:8080',
IMKEY: '11xxdd34234290546#xxxxxx'
},
TEST: {
NODE_ENV: 'TEST',
API_URL: 'http://18.163.xxx.xxx:8080',
IMKEY: '11xxdd34234290546#xxxxxx'
},
// 生产环境
PRO: {
NODE_ENV: 'PRO',
API_URL: 'https://xxxxxx.com',
IMKEY: '1234567800111#xxxxxv'
},
}

uxt.config.js

1
2
3
4
import env from './env.js' // 头部导入
env[process.env.NODE_ENV].NODE_ENV
env[process.env.NODE_ENV].API_URL
env[process.env.NODE_ENV].IMKEY

端口

package.json

1
2
3
4
5
6
7
8
{
"config":{
"nuxt":{
"host":"0.0.0.0",
"port":"3001"
}
}
}

部署

部署前远程服务器需要安装nodepm2nginx

1
npm run build

放置到服务器上的代码只需要4个目录和文件

  • .nuxt 这是打包构建好的所有依赖文件及源文件等等

  • static 静态资源包,因为上面打包构建时,不会被打进去的

  • nuxt.config.js 配置文件

  • package.json 比较重要。当”start”: “cross-env NODE_ENV=production node server/index.js”,时需要把它更改为:”start”: “nuxt start”

  • 宝塔

  • 网站

  • node项目

  • 稳定版

  • 启动:start

  • 端口:3000 默认

问题

错误提示:”You did not set any plugins, parser, or stringifier. Right now, PostCSS does nothing. Pick plugins for your case on https://www.postcss.parts/ and use them in postcss.config.js.“

1
2
3
4
# nuxt.config.js
build: {
postcss: null,
}