Nuxt.js 从搭建到发布

一个基于 Vue.js的服务端渲染应用框架 ,nuxt 解决了SPA的通病(首屏白屏,SEO)

1. 搭建Nuxt
1.1 快速入门

​ 为了快速入门,Nuxt.js团队创建了脚手架工具 create-nuxt-app

​ 确保安装了npx(npx在NPM版本5.2.0默认安装了),详细查询官网教程

$ npx create-nuxt-app <项目名>

1.2 资源目录

项目完整资源目录

在这里插入图片描述

​ api:express 目录

​ assets:公共资源目录(.js .css .jpg)

​ components:公共组件目录

​ layouts:页面入口目录(default.vue 类似vue里的App.vue)

​ locales:国际化语言包文件目录

​ middleware:中间件目录

​ pages:页面级目录

​ plugins:公共插件目录

​ static:静态资源目录

​ store:vuex目录

​ .eslintrc.js:ESlint 代码检测工具配置文件

​ .gitignore:git上传规则配置文件

​ nuxt.config.js:nuxt 规则配置文件

​ package.json:npm 依赖包配置文件

1.3 生命周期
  • nuxt 生命周期
    在这里插入图片描述

  • nuxt context 思维导图

    在这里插入图片描述

2. 配置Nuxt
2.1 引入UI框架
  1. UI框架使用的是ant-design-vue: 支持自定义主题颜色和组件国际化等

    $ npm install --save ant-design-vue
    
  2. 创建插件目录plugins,添加antd.js:Vue全局引入ant-design-vue,开发时可直接使用框架组件

    在这里插入图片描述

    // antd.js
    import Vue from 'vue'
    import antd from 'ant-design-vue'
    
    Vue.use(antd)
    
  3. 配置nuxt.config.js:引入antd.js,可自定义ant-design-vue框架主题颜色

    // nuxt.configs.js
    module.exports = {
        /**
         * 第三方插件
         */
        plugins: [
            {src: '~/plugins/antd.js', ssr: true},
        ],
        /**
         * 编译配置
         */
        build: {
            extend (config, ctx) {
                /**
                 * 自定义 ant-design-vue 主题颜色
                 */
                config.module.rules.push({
                    test: /\.less$/,
                    use: [{
                        loader: 'less-loader',
                        options: {
                            modifyVars: {
                                'primary-color': '#2EA9DF',
                                'link-color': '#2EA9DF',
                                'border-radius-base': '4px'
                            },
                            javascriptEnabled: true
                        }
                    }]
                })
            }
        }
    }
    
    
2.2 引入中间件

中间件包含路由守卫、国际化等

  1. 创建中间件目录middleware,添加route.js

    // route.js
    export default ({req}) => {
        // 服务端渲染时
        if (process.server) {
           // 业务逻辑
        }
         // 客户端渲染时
        if (process.client) {
            // 添加路由守卫,动态改变路由的跳转
            app.router.beforeEach((to, from, next) => {
    			// 业务逻辑
            })
        }
    }
    
    
  2. 配置nuxt.config.js,引入route.js

    // nuxt.configs.js
    module.exports = {
        /**
         * 中间件拦截器
         */
        router: {
            middleware: ['route']
        }
        /**
         * 第三方插件
         */
        plugins: [
            {src: '~/plugins/antd.js', ssr: true},
        ],
        /**
         * 编译配置
         */
        build: {
            extend (config, ctx) {
                /**
                 * 自定义 ant-design-vue 主题颜色
                 */
                config.module.rules.push({
                    test: /\.less$/,
                    use: [{
                        loader: 'less-loader',
                        options: {
                            modifyVars: {
                                'primary-color': '#2EA9DF',
                                'link-color': '#2EA9DF',
                                'border-radius-base': '4px'
                            },
                            javascriptEnabled: true
                        }
                    }]
                })
            }
        }
    }
    
2.3 引入国际化

界面静态文字根据国际化动态切换语言,附上官网教程

  1. 创建目录 locales,添加index.js和需要的国际化 *.json

    在这里插入图片描述

    // index.js
    export default () => ['zh_TW', 'zh_CN', 'en_US']
    
  2. 创建目录store,添加index.js:nuxt默认集成vuex,会自动检测store是否存在

    在这里插入图片描述

    // index.js
    import Locale from '~/locales'
    
    /**
     * 全局变量
     * @returns {{locales, locale: *}}
     */
    export const state = () => ({
        locales: Locale(),
        locale: Locale()[0],
    })
    
    export const mutations = {
        /**
         * @param locale 当前选中的国际化标识
         * @constructor
         */
        SET_LANG (state, locale) {
            if (state.locales.indexOf(locale) !== -1) {
                state.locale = locale
            }
        }
    }
    
    export const actions = {
        /**
         * @param commit 国际化修改
         * @param val 国际化标识
         */
        updateLang ({commit}, val) {
            commit('SET_LANG', val)
        },
    }
    
    
  3. 添加中间件 i18n.js:校验客服端传递的国际化标识,动态渲染界面文字

    在这里插入图片描述

    // i18n.js
    import Tool from '~/assets/utils/tool'  // 服务端从request获取Cookie的工具
    export default function ({ isHMR, app, store, route, params, error, redirect, req }) {
        let cookies = Tool.getcookiesInServer(req)
        let languageCookie = cookies.language ? cookies.language : null
        const defaultLocale = app.i18n.fallbackLocale
        // If middleware is called from hot module replacement, ignore it
        if (isHMR) return
        // Get locale from params
        const locale = params.lang || defaultLocale
        if (store.state.locales.indexOf(locale) === -1) {
            return error({ message: 'This page could not be found.', statusCode: 404 })
        }
        // Set locale
        store.commit('SET_LANG', store.state.locale)
        app.i18n.locale = languageCookie || store.state.locale
        // If route is /<defaultLocale>/... -> redirect to /...
        if (locale === defaultLocale && route.fullPath.indexOf('/' + defaultLocale) === 0) {
            const toReplace = '^/' + defaultLocale + (route.fullPath.indexOf('/' + defaultLocale + '/') === 0 ? '/' : '')
            const re = new RegExp(toReplace)
            return redirect(
                route.fullPath.replace(re, '/')
            )
        }
    }
    
    
    
  4. 添加插件 i18n.js:Vue全局引入i18n,可以在DOM中使用{{$t(...)}},JS中使用this.$t(...)获取国际化文字

    在这里插入图片描述

    // i18n.js
    import Vue from 'vue'
    import VueI18n from 'vue-i18n'
    import Cookie from 'js-cookie'
    
    Vue.use(VueI18n)
    
    export default ({ app, store }) => {
        let data = {}
        let Locale = store.state.locales
        for (let i = 0; i < Locale.length; i++) {
            data[Locale[i]] = require(`~/locales/${Locale[i]}.json`)
        }
        // Set i18n instance on app
        // This way we can use it in middleware and pages asyncData/fetch
        app.i18n = new VueI18n({
            locale: Cookie.get('language') || store.state.locale,
            fallbackLocale: Cookie.get('language') || store.state.locale,
            messages: data
        })
        // 自定义页面跳转方法
        app.i18n.path = (link) => {
            return `/${app.i18n.locale}/${link}`
        }
    }
    
    
    
  5. 配置Nuxt.config.js:引入插件i18n.js

    // nuxt.configs.js
    module.exports = {
        /**
         * 中间件拦截器
         */
        router: {
            middleware: ['route','i18n']
        }
        /**
         * 第三方插件
         */
        plugins: [
            {src: '~/plugins/antd.js', ssr: true},
         	{src: '~/plugins/i18n.js', ssr: true}
        ],
        /**
         * 编译配置
         */
        build: {
            extend (config, ctx) {
                /**
                 * 自定义 ant-design-vue 主题颜色
                 */
                config.module.rules.push({
                    test: /\.less$/,
                    use: [{
                        loader: 'less-loader',
                        options: {
                            modifyVars: {
                                'primary-color': '#2EA9DF',
                                'link-color': '#2EA9DF',
                                'border-radius-base': '4px'
                            },
                            javascriptEnabled: true
                        }
                    }]
                })
            }
        }
    }
    
    
2.4 引入第三方插件
  • 全局引入:配置nuxt.config.js,添加资源文件的路径

    // nuxt.config.js
    module.exports = {
      head: {
        script: [
          { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js' }
        ],
          // 不对<script>标签中内容做转义处理
        __dangerouslyDisableSanitizers: ['script'],
        link: [
          { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
        ]
      }
    }
    
    
  • 局部引入:下载第三方插件至assets中,在组件中使用

    // *.vue
    import jquery from '~/assets/utils/jquery'
    
    
2.5 引入axios.js
  1. 安装axios.js

    $ npm install --save axios
    
    
  2. 插件目录添加 axios.js

    在这里插入图片描述

    // axios.js
    import * as axios from 'axios'
    
    let devBaseUrl = '/api' // 开发环境 公共接口前缀 
    let testBaseUrl = '' // 测试环境 公共接口前缀 
    let proBaseUrl = '' // 上线环境 公共接口前缀 
    let options = {
        baseURL: devBaseUrl,
        timeout: 30000 // 请求超时时间5分钟
    }
    
    // 创建axios实例
    let services = axios.create(options)
    
    // 全局拦截请求
    services.interceptors.request.use()
    
    // 全局拦截响应
    services.interceptors.response.use()
    
    export default services
    
    
    
  3. *.vue组件中使用axios.js

    // *.vue
    <script>
        import tool from '~/assets/utils/tool'
        import axios from '~/plugins/axios'
    
        export default {
            data () {
                return {
                    // ....
                }
            },
            methods: {
                // 获取图形验证码
                getCaptcha () {
                    let data = {
                        traceId: tool.UUID()
                    }
                    axios.post('/user/image/verifiycode/get', data)
                        .then(res => {
                        	// ...
                        })
                        .catch(err => {
                            this.$message.error(this.$t(err.message))
                        })
                }
            }
        }
    </script>
    
    
2.6 加载静态资源

nuxt默认将assets里面小于1KB的图片转base64,大于1KB的图片应该放在static静态资源目录进行加载:加载静态资源

  • 加载assets的静态资源

    // *.css 文件中
    
    .class {
        background-image: url('~assets/image.png')
    }
    
    
    // *.vue 组件中
    
    <template>
      <img src="~/assets/image.png">
    </template>
    
    
  • 加载static的静态资源

    // *.css 文件中
    
    .class {
        background-image: url('../static/my-image.png')
    }
    
    
    // *.vue 组件中
    
    <template>
    	<img src="/my-image.png"/>
    </template>
    
    
3. 开发Nuxt

开发流程与Vue类似,遵循Vue规范即可:Nuxt学习教程

4. 调试Nuxt
4.1 界面调试

Nuxt 集成了热更新,修改样式会自动同步至浏览器

  • 方式一:vue-devtools 开发者工具:查看Vue Dom层级、Vuex、事件、路由等信息

    在这里插入图片描述

  • 方式二:浏览器自带调试器

    在这里插入图片描述

4.2 JS调试

需要了解Nuxt在运行时的生命周期

  • 客服端生命周期:使用浏览器断点调试
    在这里插入图片描述

  • 服务端生命周期:使用console.log() 打印控制台调试

    export default {
        /**
         * 服务端请求数据,仅限页面级组件
         */
        asyncData ({req, error}) {
            if (process.server) {
                return axios.post(`url`, data)
                    .then(res => {
                    console.log(res)
                })
                    .catch(e => {
                    error({ statusCode: 500, message: e.toString() })
                })
            }
        }
    }
    
    
4.3 代理跨域

开发环境时,请求接口需要跨域代理

线上发布时,如果采用了Nginx反向代理,则不需要跨域代理

  • 配置nuxt.config.js:使用@nuxtjs/axios代理跨域

    module.exports = {
        /**
         * 跨域代理
         */
        modules: ['@nuxtjs/axios'],
        axios: {
            proxy: process.env.NODE_ENV === 'development', // 是否使用代理
            // prefix: '',
            credentials: true
        },
        proxy: {
            // 全局拦截代理, '/api' 为 axios.js的baseURL
            '/api': {
                target: (process.env.NODE_ENV === 'development') ? 'http://192.168.91.200' : '',
                changeOrigin: true,
                pathRewrite: {'': ''}
            }
        }
    }
    
    
5. 性能优化方案

先看一组移动端网页优化前后的对比

网页评分工具(需翻墙): https://developers.google.com/speed/pagespeed/insights/

  • 移动端优化前的评分:25
    在这里插入图片描述

  • 移动端优化后的评分:98
    在这里插入图片描述

5.1 UI框架按需引入

nuxt默认集成打包分析器,便于针对性优化:webpack-bundle-analyzer

  • 执行打包命令,查询优化前框架资源大小

    $ nuxt build -a
    
    
  • 优化前:vendors.app.js 1.93MB

    在这里插入图片描述

  1. 配置antd.js:全局引入改为按需引入

    // antd.js
    import Vue from 'vue'
    import {Button} from 'ant-design-vue'
    
    import 'ant-design-vue/lib/button/style/index.less'
    
    // Vue只引入了Button组件,webpack在打包ant-design-vue时
    // 只会把Button组件打进vendors.app.js资源包
    Vue.use(Button)
    
    
  2. 配置nuxt.config.js:moment.js去掉多余语言包,默认加载了全部国际化语言

    // nuxt.config.js
    module.exports = {
        /**
         * 编译配置
         */
        build: {
            plugins: [
                // 默认英语,语言包只引入简体繁体
                new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|zh-tw/)
            ]
        }
    }
    
    
  • 优化后:vendors.app.js 1.5MB(内含30多个UI组件)

    在这里插入图片描述

5.2 服务端渲染数据

在服务端生命周期请求接口数据:SEO优化,减少页面白屏

// *.vue 例子
export default {
        /**
         * 服务端渲染
         * @param params 项目ID
         * @returns {{pagination, data: Array, publicInfo: string}}
         */
        asyncData ({req, params}) {
            if (process.server) {
                /**
                 * 用户购买历史
                 */
                let p1 = new Promise((resolve, reject) => {
                    let pageData = {
                        traceId: tool.UUID(),
                        data: {
                            pageNum: 1,
                            pageSize: 10,
                            filter: {
                                projectId: params.id
                            }
                        }
                    }
                    axios.post(`${tool.getAddress(req)}/pto/invest/project/list`, pageData)
                        .then(res => {
                            resolve(res)
                        })
                        .catch(err => {
                            reject(err)
                        })
                })
                /**
                 * 公示信息
                 */
                let p2 = new Promise((resolve, reject) => {
                    let params = {
                        traceId: tool.UUID(),
                        data: {
                            projectId: params.id
                        }
                    }
                    axios.post(`${tool.getAddress(req)}/pto/project/publicity/get`, params)
                        .then(res => {
                            resolve(res)
                        })
                        .catch(err => {
                            reject(err)
                        })
                })
                let data = []
                let pagination = {}
                let publicInfo = ''
                return Promise.all([p1, p2]).then((array) => {
                    if (array[0].data.code.toString() === '0') {
                        data = array[0].data.data.list
                        pagination.total = array[0].data.data.total
                    }
                    if (array[1].data.code.toString() === '0' && array[1].data.data) {
                        publicInfo = array[1].data.data.publicContent
                    }
                    return {data, pagination, publicInfo}
                })
            }
        },

5.3 组件异步加载
<script>
    // 组件阻塞加载
    import banner from '~/pages/computer/HomeComponents/HomeBanner'

    // 组件异步加载
    const HowFund = () => import('~/pages/computer/HomeComponents/HowFund')
    
    export default {
            components: {
            banner,
            HowFund
        }
    }


5.4 JS按需加载
// nuxt.config.js

module.exports = {
    /**
     * JS按需加载
     */
    render: {
        resourceHints: false
    }
}

5.5 其它优化方案
  • 服务端 Nginx 开启 gzip 压缩传输

  • 静态资源添加CDN

  • 减少资源请求数量,减少静态资源文件大小

  • 避免首屏 DOM树 规模过大

  • 优先加载首屏数据,懒加载首屏外图片/数据

  • 减少单个资源包太大:资源分包splitChunks

5.6 完整的nuxt.config.js
const webpack = require('webpack')

module.exports = {
    /*
      ** Headers of the page
      */
    head: {
        title: 'PTOHome',
        meta: [
            {charset: 'utf-8'},
            {
                name: 'viewport',
                content: 'maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0'
            }
        ],
        // 不对<script>标签中内容做转义处理
        __dangerouslyDisableSanitizers: ['script']
    },
    /**
     * 第三方插件
     */
    plugins: [
        {src: '~/plugins/antd.js', ssr: true},
        {src: '~/plugins/i18n.js', ssr: true}
    ],
    vue: {
        config: {
            productionTip: false, // 阻止 vue 在生产启动时生成提示
            devtools: true // 开启调试工具
        }
    },
    /**
     * JS按需加载
     */
    render: {
        resourceHints: false
    },
    /**
     * 编译配置
     */
    build: {
        analyze: true,
        vendor: ['axios'],
        extend (config, ctx) {
            /*
             ** Run ESLINT on save
             */
            if (ctx.isDev && ctx.isClient) {
                config.module.rules.push({
                    enforce: 'pre',
                    test: /\.(js|vue)$/,
                    loader: 'eslint-loader',
                    exclude: /(node_modules|.nuxt)/
                })
            }
            /**
             * 自定义 ant-design-vue 主题颜色
             */
            config.module.rules.push({
                test: /\.less$/,
                use: [{
                    loader: 'less-loader',
                    options: {
                        modifyVars: {
                            'primary-color': '#2EA9DF',
                            'link-color': '#2EA9DF',
                            'border-radius-base': '4px'
                        },
                        javascriptEnabled: true
                    }
                }]
            })
        },
        postcss: {
            preset: {
                // 更改postcss-preset-env 设置
                autoprefixer: {
                    grid: true
                }
            }
        },
        plugins: [
            // 默认英语,语言包只引入简体繁体
            new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|zh-tw/)
        ]
    },
    /**
     * API middleware 中间件
     */
    serverMiddleware: [
        '~/api/index.js'
    ],
    /**
     * 中间件拦截器
     */
    router: {
        middleware: ['i18n', 'route']
    },
    /**
     * 加载进度条风格
     */
    loading: {
        color: '#41B883'
    },
    /**
     * APP启动端口
     * 默认: 3000
     */
    server: {
        port: 80,
        host: '0.0.0.0'
    },
    /**
     * 跨域代理
     */
    modules: ['@nuxtjs/axios'],
    axios: {
        baseUrl: 'http://192.168.91.200',
        proxy: process.env.NODE_ENV === 'development', // 是否使用代理
        // prefix: '',
        credentials: true
    },
    proxy: {
        // 全局拦截代理
        '/api/v2': {
            target: (process.env.NODE_ENV === 'development') ? 'http://192.168.91.200' : 'http://192.168.91.183',
            changeOrigin: true,
            pathRewrite: {'': ''}
        }
    }
}


6. 发布Nuxt
6.1 打包
  • 执行打包命令,获取生产资源:.nuxt目录

    // 或 npm run build
    $ nuxt build 
    
    
6.2 发布

服务器需要安装node环境,配置工具Pm2或Nginx

  1. 复制目录文件 .nuxt 、api、static、nuxt.config.js、package.json 到发布的服务器目录

  2. 执行安装命令,安装依赖环境

    $ npm install
    
    
  3. Pm2的方式:执行项目启动命令,nuxt.config.js需要配置跨域代理

    $ pm2 start ./node_modules/nuxt/bin/nuxt.js --name projectName -- start
    
    

    在这里插入图片描述

  4. Nginx的方式:执行项目启动命令,nuxt.config.js不需要配置跨域代理

    $ pm2 start ./node_modules/nuxt/bin/nuxt.js --name projectName -- start
    
    
    // Nginx 配置参考
    #
    # The default server
    #
    
    server {
        listen       80;
        server_name  localhost;
        root         /usr/share/nginx/html;
        index       index.html;
        # Load configuration files for the default server block.
        client_max_body_size    1024m;
        client_header_timeout 120s;
        client_body_timeout 120s; 
        client_body_buffer_size 256k;  
    
        # 后端转发
        location /api/v2/ {
            rewrite  ^/api/v2/(.*)$ /$1 break;
            proxy_read_timeout 300; # Some requests take more than 30 seconds.
            proxy_connect_timeout 300; # Some requests take more than 30 seconds.
            proxy_send_timeout      600;
            proxy_set_header X-Real-IP $remote_addr;  # 获取客户端真实IP
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 获取代理者的真实ip
            proxy_buffer_size       32k;
            proxy_buffers           32 256k;
            proxy_busy_buffers_size 512k;
            proxy_temp_file_write_size 512k;
            proxy_pass http://172.19.0.24:8080;
        }
        
        # 页面转发
        location / {
            proxy_read_timeout 300; # Some requests take more than 30 seconds.
            proxy_connect_timeout 300; # Some requests take more than 30 seconds.
            proxy_send_timeout      600;
            proxy_set_header X-Real-IP $remote_addr;  # 获取客户端真实IP
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 获取代理者的真实ip
            proxy_buffer_size       32k;
            proxy_buffers           32 256k;
            proxy_busy_buffers_size 512k;
            proxy_temp_file_write_size 512k;
            proxy_pass http://172.19.0.7;
        }
    
        error_page 404 /404.html;
            location = /40x.html {
        }
    
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    
    }
    
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值