SEO的出现
1、搜索引擎-在信息大爆炸的时代,为广大用户提供最精确,最优质的搜索结果,搜索引擎,放出一个虚拟用户,收录各服务中html信息
2、前端框架的兴起,v8运行js还是快的,主体html结构都压缩在js中,用户可看不懂js啊,拜拜,你这个服务器下啥子都没有,不来啦。
seo(技术层面占3-4成)目前来说,前端能做什么
先做本质的东西,什么其他优化啊,友好啊,先不提,基座都定不了,怎么开展其他工作呢
1、基础目标:构建出静态页面
- 前后端不分离开发(主流seo排行高页面大部分为前后端不分离项目)
- nuxt next 等node服务构建框架 同理的ssr服务端渲染
- 预渲染 (如果真想做把seo做起来,不太建议该方式)
2、基座遇到的问题
- 前后端不分离 -- 渐渐不被提倡 (由繁到简,在简到繁)
- node服务都没有线上部署过,怎么配合运维线上部署,后期怎么维护、、、、
- ssr 没有去实践过,俺就不谈论 思想是一样的,搭建node中间服务去渲染页面,并返回给浏览器已经构建好的html文件
优缺点对比
1、前后端不分离
优点:
- 符合seo标准的静态页面
- 页面结构清晰,页面内容可控制比例高,定制化方便
- 开发风险性较低,代码可读性高
缺点: - 开发与迭代所需工时长
- 项目较重,DOM操作频繁
- 各类插件维护版本
2、nuxt / ssr-server
优点:
- 符合seo标准的静态页面
- 项目打包轻,数据驱动比DOM轻,快
- 缺点:
- 前期开发成本高,node服务错误较难排查
- node服务,如何维护,高并发访问量如何处理(稳定性有待查看)
3、预渲染
优点:
- 开发快,在原有项目改动方便
缺点: - 动态数据维护较差,seo效果一般
nuxt开发实践
开发前请通读官方文档与API
本文档只会聊使用到的技术点,其他还是多看看官方文档
嘻嘻--俺也没有仔细阅读官方文档,这样不太好哦,写完文档,俺得回去在看看(第一遍看有什么,第二遍看为什么,第三遍看怎么实现)
一、搭建项目
官网脚手架 / 当然你也可以自己搭建,安装所需npm包即可
npx create-nuxt-app <项目名>
yarn create nuxt-app <项目名>
这里我使用了nuxt模版提供选择的express,因为在后期我想通过express框架,对node服务进行个性化管理与配置
目录结构
├── .nuxt --------------------------- 打包后项目主体文件
├── api ----------------------------- 说明文件
├── asyncData ------------------- node服务端请求接口
├── syncData -------------------- 浏览器请求接口
├── assets -------------------------- 项目静态资源文件目录(图片、公共样式、字体等)
├── components ---------------------- 组件
├── layouts ------------------------- 布局模式
├── middleware ---------------------- 中间件
├── pages --------------------------- 页面(该文件夹下也对应着项目路由路径)
├── plugins ------------------------- 插件
└── main.js --------------------- 等同于vue的main.js(vue对象挂载其他插件)
└── index.js -------------------- 引用当前文件夹的总出口
├── server
└── index.js -------------------- node服务启动文件
├── public -------------------------- 入口文件
├── static -------------------------- 组件
├── store --------------------------- 布局模式
├── utils --------------------------- 中间件
├── app.html ------------------------ html入口文件
├── nuxt.config.js ------------------- webpack配置入口
├── package.json -------------------- 项目配置
使用脚手架新建项目,大部分目录都会自动生成
现在项目有了,先不要急着写,整理出页面所需要的基础配置与依赖--感谢来自佳宁支援
- 搭建webpack
- node服务配置
- 路由
- layout布局
- 重新封装axios请求(异步数据处理)
- 插件依赖
- vuex状态管理
- 最后开始写页面或改造已有项目
基本目标确定,开始工作workwork
二、搭建webpack
nuxt.config.js
mode
module.exports = {
mode: 'universal'
}
mode有两个模式,默认是universal,另一个是spa
- spa是单页,所以只有一个入口文件,sitemap也就只有一个url,这会导致网站辛辛苦苦搭建的服务端渲染最多只被搜索引擎收录一个页面。
- 而universal则能实现所有网站路径完全被收录,这才是最初我们使用nuxt的初衷
env
module.exports = {
env: {
NODE_ENV: process.env.NODE_ENV // 申明执行环境
}
}
head
module.exports = {
head: {
title: 'TeeMo',
meta: [
],
link: [
],
script: [
]
}
}
- 全局页面的head头部信息,当然nuxt也支持你在vue中使用head对象,来定制不同页面的head信息
完整版
loading
module.exports = {
loading: false,
}
- 在页面切换的时候,Nuxt.js 使用内置的加载组件显示加载进度条。你可以定制它的样式,禁用或者创建自己的加载组件
- 类型: Boolean 或 Object 或 String
css
module.exports = {
css: [
'swiper/css/swiper.css'
]
}
- 在 Nuxtjs 里配置全局的 CSS 文件、模块、库。
- 如果你引入less,scss样式文件,你只需要安装对应的less-loader,sass-loader,Nuxt.js 会自动识别被导入文件的扩展名,之后,webpack 会使用相应的预处理器进行处理。前提是,你安装了对应预处理器。
css
module.exports = {
cache: false //默认false,后期时候可以做优化处理做标记
}
- 对于部分网页进行服务端的缓存,可以获得更好的渲染性能,但是缓存又涉及到一个数据的及时性的问题,所以在及时性和性能之间要有平衡和取舍。
完整版
build
module.exports = {
build: {
extractCSS: { allChunk: true },
extend (config, { isDev, isClient }) {
if (isDev && isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
}
}
- build顾名思义,打包构建,其中api众多,细看链接
- extractCSS对所有样式提取压缩
- extend 对js,vue进行eslint语法检测,并压缩文件
plugins
module.exports = {
plugins: [
{ src: '@/assets/styles/index.less', ssr: false },
{ src: '@/plugins/index', ssr: false }
]
}
- 加载我们的插件
完整版
module.exports = {
mode: 'universal',
env: {å
NODE_ENV: process.env.NODE_ENV
},
head: {
title: 'TeeMo',
meta: [
],
link: [
],
script: [
]
},
loading: false,
css: [
'swiper/css/swiper.css'
],
cache: false,
build: {
extractCSS: { allChunk: true },
extend (config, { isDev, isClient }) {
if (isDev && isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
},
plugins: [
{ src: '@/assets/styles/index.less', ssr: false },
{ src: '@/plugins/index', ssr: false }
]
}
三、node服务配置
server/index.js
const express = require('express')
const consola = require('consola')
const { Nuxt } = require('nuxt')
const { Builder } = require('nuxt')
const app = express()
const port = process.env.PORT || 8080
app.set('port', port)
// 读取nuxt配置文件
const config = require('../nuxt.config.js')
config.dev = (process.env.NODE_ENV === 'dev')
async function start () {
// 初始化nuxt对象
const nuxt = new Nuxt(config)
// 只在本地开发启动热更新
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
// express api 使用渲染树构建服务
app.use(nuxt.render)
// 监听端口
app.listen(port)
consola.ready({
message: `Server listening on http://localhost:${port}`,
badge: true
})
}
start()
四、路由
nuxt对vue-router进行封装,让我们不用关心与维护路由文件(router.js),那他的路由路径怎么对应,其实就是对应着pages下面的文件路径,即是页面路径。所以我们在写页面的时候,规范组件抽离至component文件夹,也不要有其他不是单独页面的文件
copy官网介绍
基础路由
假设 pages 的目录结构如下:
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
那么,Nuxt.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'
}
]
}
动态路由与动态嵌套路由
- 在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
目录结构:
pages/
--| _category/
-----| _subCategory/
--------| _id.vue
--------| index.vue
-----| _subCategory.vue
-----| index.vue
--| _category.vue
--| index.vue
Nuxt.js 自动生成的路由配置如下:
router: {
routes: [
{
path: '/',
component: 'pages/index.vue',
name: 'index'
},
{
path: '/:category',
component: 'pages/_category.vue',
children: [
{
path: '',
component: 'pages/_category/index.vue',
name: 'category'
},
{
path: ':subCategory',
component: 'pages/_category/_subCategory.vue',
children: [
{
path: '',
component: 'pages/_category/_subCategory/index.vue',
name: 'category-subCategory'
},
{
path: ':id',
component: 'pages/_category/_subCategory/_id.vue',
name: 'category-subCategory-id'
}
]
}
]
}
]
}
五、页面
模版
- 你可以定制化 Nuxt.js 默认的应用模板,定制化默认的 html 模板,只需要在src文件夹下(默认是应用根目录)创建一个 app.html 的文件。
默认模板为:
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
布局
- Nuxt.js 允许你扩展默认的布局,或在 layout 目录下创建自定义的布局。
- 可通过添加 layouts/default.vue 文件来扩展应用的默认布局。
注意:必须在布局文件中添加 <nuxt/> 组件用于显示页面的主体内容 - 错误页面:layouts/error.vue 文件来定制化默认错误页面
页面
<template>
<h1 class="red">Hello {{ name }}!</h1>
</template>
<script>
export default {
asyncData (context) {
return { name: 'TeeMo' }
},
fetch () { },
head () { },
layout : '',
loading: xxx,
transition: xxx,
scrollToTop: bool,
validate: reg,
middleware: 'xxx.js'
}
</script>
<style>
</style>
提供的对象
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
- 指定页面的中间件,中间件会在页面渲染之前被调用
六、重新封装axios请求(服务端环境区分)
requset.js
- 按照需要封装axios,该文件的console会打印到pm2 logs日志文件
/* eslint-disable eqeqeq */
import axios from 'axios'
import config from '@/api/asyncData/asyncConfig'
import qs from 'qs'
const apiConfig = axios.create({
// 设置超时时间
timeout: 15000,
// 请求的baseUrl
baseURL: '',
// 请求头部信息
headers: {
},
withCredentials: true,
// 请求参数转换
transformRequest: [function (data) {
return qs.stringify(data)
}]
})
// 请求拦截器
apiConfig.interceptors.request.use(request => {
if (config.IS_RELEASE) {
console.log(
`${new Date().toLocaleString()}【 Url=${request.url} 】Params=`,
request.params || request.data
)
}
return request
}, error => {
return Promise.reject(error)
})
export default async (options = { method: 'POST' }) => {
let isdata = true
if (
options.method.toUpperCase() !== 'POST' && options.method.toUpperCase() !== 'PUT' && options.method.toUpperCase() !== 'PATCH'
) {
isdata = false
}
const res = await apiConfig({
method: options.method,
url: options.url,
data: isdata ? options.data : null,
params: !isdata ? options.data : null
})
if (config.IS_RELEASE) {
console.log(
`${new Date().toLocaleString()}【接口响应】:`,
res.data.code + ' ------ ' + res.data.message
)
}
if (res.data.code !== '2000') {
const errorData = {
code: 500,
data: {
list: []
},
message: '服务器异常'
}
return errorData
} else {
return res.data
}
}
asyncConfig.js
- 环境区分,打包时候使用cross-env来改变NODE_ENV环境变量,或直接在服务端自己修改NODE_ENV
// 不同环境的请求配置
const prodConfigs = {
dev: {
apiHost: 'http://dev',
IS_RELEASE: true
},
test: {
apiHost: 'http://test',
IS_RELEASE: true
},
test2: {
apiHost: 'http://test2',
IS_RELEASE: true
},
pre: {
apiHost: 'https://pre',
IS_RELEASE: true
},
pro: {
apiHost: 'https://pro',
IS_RELEASE: false
},
production: {
apiHost: 'https://pro',
IS_RELEASE: true
}
}
// console.log(process.env.NODE_ENV)
// 打包时候默认NODE_ENV环境变量为production
const hostConfig = prodConfigs[process.env.NODE_ENV]
export default hostConfig
index.js
- 暴露接口
import request from './request'
import hostConfig from './asyncConfig'
// 轮播图
export const getBanners = (params) => {
return request({
url: `${hostConfig.apiHost}/xxx`,
method: 'GET',
data: params
})
}
插件
统一写在plugins文件夹下,可以使用index来管理插件总路口,这样你就不用在nuxt.config.js中plugins输入多个模块
nuxt.config.js
plugins: [
{ src: '@/plugins/index', ssr: false }
]
index.js
import './main.js'
import 'xxxxx'
main.js
- 这就相当于vue中的全局挂载文件入口
import Vue from 'vue'
import apis from '@/api/syncData/index'
import reg from '@/utils/validator.js'
import { Button, Cell, Toast, ActionSheet, Lazyload, PullRefresh, List, Popup } from 'vant' // 常用组件
import 'vant/lib/index.css'
import vueDirectiveImagePreviewer from 'vue-directive-image-previewer'
import 'vue-directive-image-previewer/dist/assets/style.css'
import VueAwesomeSwiper from 'vue-awesome-swiper'
Vue.use(Button)
Vue.use(Cell)
Vue.use(Toast)
Vue.use(List)
Vue.use(ActionSheet)
Vue.use(PullRefresh)
Vue.use(Popup)
Vue.use(Lazyload, {
lazyComponent: true
})
Vue.use(vueDirectiveImagePreviewer)
Vue.use(VueAwesomeSwiper)
// 全局配置Toast
Toast.setDefaultOptions({
forbidClick: true,
duration: 1500
})
// 挂载到Vue身上
Vue.prototype.$Toast = Toast
Vue.prototype.$apis = apis
Vue.prototype.$reg = reg
vuex状态管理
还没有使用过,但请不要使用客户端对象
使用方式与在vue中大同小异,提供钩子
fetch
- fetch 方法会在渲染页面前被调用,作用是填充状态树 (store) 数据,与 asyncData 方法类似,不同的是它不会设置组件的数据。
nuxtServerInit
- 如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第2个参数传给它(服务端调用时才会执行)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。
最后开始写页面或改造已有项目
1、初始化数据都在asyncData方法中完成,并return出来
2、created生命周期及其之前不要使用客户端对象,实在要写执行函数,就加一个判断,用process.client判断一下环境再执行
if (process.client) {
window.xxxx
)
3、路由跳转,我使用的是<nuxt-link :to="{path:'xxx'}"></nuxt-link>,nuxtLink组件渲染完毕之后实际上就是a标签
思考
过程:客户端打开页面请求node服务,此时node服务执行nuxt,再请求java接口渲染页面,页面渲染完毕返回客户端
一、每一个用户打开页面,node服务都会执行构建页面吗?
二、都说node面对高并发有很好的效果,真的是这样吗?
nodejs的两大牛逼依赖
v8:是google开发的一套高效javascript运行时,nodejs能够高效执行 js 代码的很大原因主要在它。
libuv:是用C语言实现的一套异步功能库,nodejs高效的异步编程模型很大程度上归功于libuv的实现。
部署方案
服务器需安装node环境与pm2
第一步、服务器拉区git项目地址
第二步、以此执行前端命令
- npm install // 下载前端依赖文件
- npm run build // 构建nuxt前后端启动文件
- npm start // 启动服务,服务器代理该服务
因环境问题,每个环境构建命令与启动命令不一致,每个环境都需要单独打包
构建
- 开发环境 npm run build:dev
- 测试一套环境 npm run build:test
- 测试二套环境 npm run build:test2
- 预发环境 npm run build:pre
- 线上环境 npm run build:pro
启动
- 开发环境 npm run start:dev
- 其他环境 npm start
package.json
"scripts": {
"build:dev": "cross-env NODE_ENV=dev nuxt build ",
"build:test": "cross-env NODE_ENV=test nuxt build ",
"build:test2": "cross-env NODE_ENV=test2 nuxt build ",
"build:pre": "cross-env NODE_ENV=pre nuxt build ",
"build:pro": "cross-env NODE_ENV=production nuxt build ",
"start:dev": "cross-env NODE_ENV=all pm2 start server/index.js --node-args='--harmony' --name 'nuxt'",
"start": "cross-env NODE_ENV=all pm2-docker start server/index.js --node-args='--harmony' --name 'nuxt'",
}