SSR 目的是为了解决单页面应用的SEO的问题,对于一般网站影响不大,但是对于论坛类,内容类网站来说是致命的,搜索引擎无法抓取页面相关内容,也就是用户搜不到此网站的相关信息。
原理:
将html在服务端渲染,合成完整的html文件再输出到浏览器。
适用场景:
客户端的网络比较慢
客户端运行在老的或者直接没有JavaScript引擎上
nuxt
作用就是在node.js上进一步封装,然后省去我们搭建服务端环境的步骤,只需要遵循这个库的一些规则就能轻松实现SSR。
可以作为一个node.js应用跑在服务器上,也可以把整站直接编译为静态HTML。另外,这个框架支持自动生成路由,用来写展示型的页面时非常不错的选择。
nuxt能为我们做什么?
nuxt.js会监听pages目录下的改变,添加新page的时候不需要重启服务器
为什么选择nuxt来做SSR
运行create-nuxt-app
为了快速入门,nuxt.js团队创建了脚手架工具create-nuxt-app
资源目录
资源目录assets用于组织未编译的静态资源如less sass 或JavaScript
组件目录
组件目录components用于组织应用的vue.js组件。nuxt.js不会扩展增强该目录下vue.js组件,即这些组件不会像页面组件那样有asyncData方法的特性。
布局目录
布局目录layouts用于组织应用的布局组件。该目录名为nuxt.js保留的,不可更改。
中间件目录
middleware目录用于存放应用的中间件
页面目录
页面目录pages用于组织应用的路由及视图。nuxt.js框架读取该目录下所有的.vue文件并自动生成对应的路由配置。
该目录名为nuxt.js保留的,不可更改。
插件目录
插件目录plugins用于组织那些需要在根vue.js应用实例化之前需要运行得JavaScript插件。
静态文件目录
静态文件目录static用于存放应用的静态文件,此类文件不会被nuxt.js调用webpack进行构建编译处理。服务器启动的时候,该目录下的文件会映射至应用的根路径/下
举个例子:/static/robots.txt
该目录名为nuxt.js保留的,不可更改。
store目录
store目录用于组织应用的vuex状态树文件。nuxt.js框架集成了vuex状态树的相关功能配置,在store目录下创建一个index.js文件可激活这些配置。
该目录名为nuxt.js保留的,不可更改。
nuxt.config.js文件
nuxt.config.js文件用于组织nuxt.js应用的个性化配置,以便覆盖默认配置。该文件名为nuxt.js保留的,不可更改。
package.json 文件
package.json文件用于描述应用的依赖关系和对外暴露的脚本接口。该文件名为nuxt.js保留的,不可更改。
nuxt.js默认的配置涵盖了大部分使用情形,可通过nuxt.config.js来覆盖默认的配置。
build
nuxt.js允许你在自动生成的vendor.bundle.js文件中添加一些模块,以减少应用bundle的体积。如果你的应用依赖第三方模块,这个配置项是十分实用的。
cache
该配置项让你开启组件缓存策略以提升渲染性能。
CSS
该配置项用于定义应用的全局(所有页面均需引用的)样式文件、模块或第三方库。
dev
该配置项用于配置nuxt.js应用是开发还是生产模式。
env
该配置项用于定义应用客户端和服务端的环境变量。
generate
该配置项用于定义每个动态路由的参数,nuxt.js依据这些路由配置生成对应目录结构的静态文件。
head
该配置项用于配置应用默认的meta标签。
loading
该配置项用于个性化定制nuxt.js使用的加载组件。
modules
该配置项允许您将nuxt模块添加到项目中。
modulesDir
该配置项允许您定义nuxt.js应用程序的node_modules文件夹
plugins
该配置项用于配置那些需要在根vue.js应用实例化之前需要运行得JavaScript插件。
rootDir
该配置项用于配置nuxt.js应用的根目录
router
该配置项可用于覆盖nuxt.js默认的vue-router配置。
srcDir
该配置项用于配置应用的源码目录路径
transition
该配置项用于个性化配置应用过渡效果属性的默认植。
nuxt.js依据pages目录结构自动生成vue-router模块的路由配置
首页
假设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/
--| _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:'page/_slug/comments.vue
},
]
}
你会发现名称为users-id的路由路径带有:id?参数,表示该路由是可选的,如果你想将它设置为必选的路由,需要在users/_id目录内创建一个index.vue文件。
:API Configuration generate
警告:generate命令会忽略动态路由
路由参数校验
nuxt.js可以让你在动态路由组件中定义参数校验方法。
举个例子:pages/users/_id.vue
export default {
validate ({params}){
//必须是number类型
return /^\d+$/.test(params.id)
}
}
如果validate方法未返回true或promise解析为true或抛出error,则nuxt.js将在出现错误时自动加载404错误页面或500错误页面。
嵌套路由
你可以通过vue-router的子路由创建nuxt.js应用的嵌套路由。
创建内嵌子路由,你需要添加一个vue文件,同时添加一个与该文件同名的目录用来存放子视图组件。
warning:别忘了在父组件(.vue文件)内增加用于显示子视图内容。
假设文件结构如:
pages/
–| users/
-----| _id.vue
-----| index.vue
–| users.vue
nuxt.js自动生成的路由配置如下:
router:{
routes:[
{
path:’/users’,
component:‘pages/users.vue’,
children:[
{
path:’’,
component:‘pages/users/index.vue’,
name:‘users’
},
{
path:‘id’,
component:‘pages/users/_id.vue’,
name:‘users-id’
}
]
}
]
}
动态嵌套路由
这个应用场景比较少见,但是nuxt.js仍然支持:在动态路由下配置动态子路由。
假设文件结构如下:
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'
}
]
}
]
}
]
}
SPA fallback
您也可以为动态路由启用SPA fallback。在使用mode:'spa’模式下,nuxt.js将输出一个与index.html相同的额外文件。如果没有文件匹配,大多数静态托管服务可以配置为使用SPA模板。生成文件不包含头信息或任何HTML,但它仍将解析并加载API中的数据。
我们在nuxt.config.js文件中启用它:
export default {
generate:{
fallback:true,//if you want to use '404.html'
fallback:'my-fallback/file.html'//if your hosting needs a custom location
}
}
nuxt.js使用vue.js的组件来实现路由切换时的过渡动效。
全局过渡动效设置
提示:nuxt.js默认使用过渡效果名称为page
如果想让每一个页面的切换都有淡出(fade)效果,我们需要创建一个所有路由共用的css文件,所以我们可以在assets/目录下创建这个文件:
在全局样式文件 assets/main.css里添加一下样式:
.page-enter-active,.page-leave-active {
transition:opacity:.5s;
}
.page-enter,.page-leave-active {
opacity:0;
}
然后我们将页面组件中的transition属性的值设置为test即可:
export default {
transition:'test'
}
前端指的是网页开发,通常负责网站数据与用户的交互
后端指的是网站数据的收集及处理保存,通常负责数据这块
中间件指的一般是对外或者对内的一些不涉及具体业务的中间服务,例如:用户数据过滤 消息等列等;
中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。
每一个中间件应放置在middleware/目录。文件名的名称将成为中间件名称(middleware/auth.js将成为auth中间件)。
一个中间件接受context作为第一个参数:
export default function (context){
context.userAgent = context.isServer ? context.req.headers['user-agent'] : navigator.userAgent
}
中间件执行流程顺序:
中间件可以异步执行,只需要返回一个promise或使用第2个callback作为第一个参数:
middleware/start.js
import axios from 'axios'
export default function ({route}){
return axios.post('http://my-stats-api.com',{
url:route.fullPath
})
}
然后在你的nuxt.js layouts或者pages中使用中间件:
nuxt.config.js
module.exports={
router:{
middleware:'stats'
}
}
stats 中间件将在每个路由改变时被调用。
本章节的内容阐述了如何在nuxt.js应用中为指定的路由配置数据和视图,包括应用模板、页面、布局和HTML头部等内容。
模板
你可以定制化nuxt.js默认的应用模板
定制化默认的HTML模板,只需要在应用根目录下创建一个app.html的文件。
默认模板为:
{{HEAD}}
{{APP}}
举个例子,你可以修改模板添加IE的条件表达式:
{{ HEAD }}
{{ APP }}
布局
nuxt.js允许你扩展默认的布局,或在layout目录下创建自定义的布局。
默认布局
可以通过添加layouts/default.vue文件来扩展应用的默认布局。
别忘了在布局文件中添加组件用于显示页面的主体内容。
默认布局的源码如下:
错误页面
你可以通过编辑layouts/error.vue 文件来定制化错误页面
这个布局文件不需要包含标签。你可以把这个布局文件当初是显示应用错误(404 500等)的组件。
举一个个性化错误页面的例子 layouts/error.vue
页面不存在
应用发生错误异常
首页
个性化布局
layouts根目录下的所有文件都属于个性化布局文件,可以在页面组件中利用layout属性来引用
请确保在布局文件里面增加组件用于显示页面非布局内容。
举个例子 layouts/blog.vue:
这里是博客导航
在pages/posts.vue里,可以指定页面组件使用blog布局。
页面
页面组件实际上是vue组件,只不过nuxt.js为这些组件添加了一些特殊的配置项(对应nuxt.js提供的功能特性)以便你能快速开发通用应用
{{name}}!
HTML头部
nuxt.js使用了vue-meta更新应用的头部标签(head)and html属性。
nuxt.js使用以下参数配置vue-meta:
{
keyName:'head',
attribute:'n-head',
ssrAttribute:'n-head-ssr',
tagIDKeyName:'hid'
}
默认meta标签
nuxt.js允许你在nuxt.config.js里面定义应用所需的所有默认meta标签,在head字段里配置就可以了:一个使用自定义viewport和谷歌字体的配置示例:
head:{
meta:[
{charset:'utf-8'},
{name:'viewport',content:''}
],
link:[
{rel:'stylesheet',href:'https://XXX'}
]
}
nuxt.js扩展了vue.js,增加了一个 叫asyncData的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。
asyncData方法
asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务器端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用asyncData方法来获取数据,nuxt.js会将asyncData返回数组融合组件data方法返回的数据一并返回给当前组件。
注意:由于asyncData方法是在组件初始化前被调用的,所以在方法内是没办法通过this来引用组件的实例对象。
nuxt.js提供了几种不同的方法来使用asyncData方法,你可以选择自己熟悉的一种来用:
1、返回一个promise,nuxt.js会等待该promise被解析之后才会设置组件的数据,从而渲染组件
2、使用async或await
3、为第二个参数指定一个回调函数。注:该回调函数需符合通用的nodejs回到函数的形式:callback(err,data)
我们使用axios重构http请求,我们强烈建议您使用axios模块用于您的nuxt项目中。
返回Promise
export default {
asyncData({params}){
return axios.get(`https://my-api/posts/${params.id}`).then((res)=>{
return { title: res.data.title}
})
}
}
**使用async或await
export default {
async asyncData({ params }){
let { data } = await axios.get(`https://my-api/posts/${params.id}`);
return {title:data.title}
}
}
使用回调函数
export default{
asyncData ({params},callback){
axios.get(`https://my-api/posts/${params.id}`).then((res)=>{
callback(null,{title:res.data.title});
})
}
}
返回对象
如果组件的数据不需要异步获取或处理,可以直接返回指定的字面对象作为组件的数据。
export default{
data(){
return {foo:'bar'}
}
}
数据的展示
asyncData方法返回的数据融合data方法返回的数据后,一并返回给模板进行展示,如:
{{title}}
上下文对象
可通过APIcontext来了解该对象的所有属性和方法
访问动态路由数据
您可以使用注入asyncData属性的context对象来访问动态路由数据。例如,可以使用配置它的文件或文件夹的名称访问动态路径参数。所以,如果你定义一个名为_slug.vue的文件,您可以通过context.params.slug来访问它。
监听query参数改变
默认情况下,query的改变不会调用asyncData方法。如果要监听这个行为,例如,在构建分页组件时,您可以设置应通过页面组件的watchQuery属性监听参数。
错误处理
nuxt.js在上下文对象context中提供了一个error(params)方法,你可以通弄过调用该方法来显示错误信息页面。params.statusCode可用于指定服务端返回的请求状态码。
以返回promise的方法举个例子:
export default{
asyncData({params,error}){
return axios.get(`https://my-api/posts/${params.id}`)
.then((res)=>{
return { title:res.data.title }
}) .catch((e)=>{
error({statusCode:404,message:'post not found'})
})
}
}
如果你使用回调函数的方式,你可以将错误的信息对象直接传给该回调函数,nuxt.js内部会自动调用error方法:
export default {
asyncData({params},callback){
axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
callback(null,{title:res.data.title})
})
.catch((e) => {
callback({statusCode:404, message : 'Post not found'})
})
}
}
默认情况下nuxt使用vue-loader file-loader url-loader 这几个webpack加载器来处理文件的加载和引用。对于不需要通过webpack处理的静态资源文件,可以放置在static目录中。
webpack构建
默认情况下,vue-loader自动使用css-loader和vue模板编译器来编译处理vue文件中的样式和模板。在此编译过程中,所有的资源URL例如 background:url(…)和css中的@import 均会被解析成模板通过require引用。
举个例子,假设我们有以下文件目录结构:
-| assets/
----| image.png
-| pages/
----| index.vue
如果我们在CSS代码中使用 url(’~assets/image.png’), 那么编译后它将被转换 require(’~/assets/image.png’)。
请注意,由于css-loader升级,从Nuxt 2.0开始,你应该在CSS文件中使用assets(没有斜杠),例如background:url("assets/banner.svg")
又或者如果我们在pages/index.vue中使用一下代码引用图片资源:
那么编译后会被转换成:
createElement('img', { attrs: { src: require('~/assets/image.png') }})
.png并非JavaScript文件,因此nuxt.js通过配置webpack使用file-loader和url-loader这两个加载器来处理此类引用。
这样做的好处有:
实际上,nuxt.js默认的加载器配置如下:
[
{
test:/\.(png|jpe?g|gif|svg)$/,
loader:'url-loader',
query:{
limit:1000, //1KB
name:'img/[name].[hash:7].[ext]'
}
},
{
test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader:'url-loader',
query:{
limit:1000, //1KB
name:'fonts/[name].[hash:7].[ext]'
}
}
]
也即文件(图片或字体)的尺寸小于1K的时候,它将会被转换成Base-64 data URL 来内联引用;否则它将被拷贝至指定的子目录(在.nuxt目录下),并被重命名(加上7位哈希码作为版本标识)以实现更好的缓存策略。
当用nuxt命令运行我们的应用时,pages/index.vue中的模板代码
将被编译生成:
静态文件
如果你的静态资源文件需要webpack做构建编译处理,可以放到assets目录,否则可以放到static目录中去。
nuxt服务器启动的时候,该目录下的文件会映射至应用的根路径/下,像robots.txt或sitemap.xml这种类型的文件就很适合放到static目录中。
你可以再代码中使用根路径/结合资源相对路径来引用静态资源:
你可以配置需要在根vue.js应用实例化之前需要运行的JavaScript插件,可以是你自己写的库或者第三方模块。
需要注意的是,在任何vue组件的声明周期内,只有beforeCreate和created这两个钩子方法会在客户端和服务端均被调用。其他钩子方法仅在客户端被调用。
使用第三方模块
我们可以在应用中使用第三方模块,一个典型的例子是在客户端和服务端使用axios做http请求。
首先我们需要安装npm包:
npm instal --save axios
然后在页面内可以这样使用:
{{title}}
有一个值得注意的问题是,如果我们在另外一个页面内也引用了axios,那么在应用打包发布的时候axios会被打包两次,而实际上我们只需打包一次。这个问题可以通过在nuxt.config.js里面配置build.vendor来解决:
module.exports = {
build: {
vendor:['axios']
}
}
在vendor数组里面添加的模块会被打包到vendor bundle里,之后在组件内对该模块的引用将不会被打包到组件对应的文件内了,这样说感觉有点抽象,举个例子说明一下:
我们在build vendor里面添加了axios模块:
module.exports = {
build: {
vendor:['axios']
}
}
经过上面的配置后,我们可以在任何页面里面引入axios而不用担心它会被重复打包
使用vue插件
假如我们想使用vue-notifications显示应用的通知信息,我们需要在程序运行前配置好这个插件。
首先增加文件plugins/vue-notifications.js:
import vue from 'vue'
import vueNotifications from 'vue-notifications'
vue.use(vueNotifications)
然后,在nuxt.config.js内配置plugins如下:
module.exports = {
plugins:['~/plugins/vue-notifications']
}
实际上,vue-notifications会被打包至应用的脚本代码里,但是它属于第三方库,我们理应将它打包至库文件里以获得更好的缓存效果。(译者注:应用代码比库文件修改频繁,应尽量将第三方库打包至单独的文件中去)。
我们可以更新nuxt.config.js文件,在vendor构建配置项里添加vue-notifications:
module.exports = {
build:{
vendor:['~/plugins/vue-notifications']
},
plugins:['~/plugins/vue-notifications']
}
有时您希望在应用程序中提供功能或参数值。您可以将这些变量注入vue实例(客户端),context(服务器端)甚至vuex存储中。在nuxt.js中使用前缀$为这些函数添加注入。
将内容注入vue实例,避免重复引入,在vue原型上挂载注入一个函数,所有组件内都可以访问(不包含服务器端)。
plugins/vue-inject.js:
import vue from 'vue'
Vue.prototype.$myInjectedFunction = (string) => console.log('this is an example',string)
nuxt.config.js:
export default {
plugins: ['~/plugins/vue-inject.js']
}
这时,您现在可以在所有vue组件中使用该函数。
example-component.vue:
export default {
mounted(){
this.$myInjectedFunction('test');
}
}
注入context
将内容注入context,避免重复引入,在app上挂载注入一个函数,所有组件内都可以访问(不包含客户端)。
plugins/ctx-inject.js :
export default ({app},inject) => {
//set the function directly on the context.app object
app.myInjectedFunction = (string) => console.log('okay, another function', string);
}
nuxt.config.js:
export default {
plugins :['~/plugins/ctx-inject.js']
}
只要您有权访问context,该函数现在就可用(例如在asyncData和fetch中)。
ctx-example-component.vue:
export default {
asyncData(context){
context.app.myInjectedFunction('ctx!')
}
}
联合注入
如果您需要在context中,vue实例,甚至可能在vuex store中,您可以使用inject方法,这个方法接受两个参数,$将自动添加到该函数中。
plugins/combined-inject.js:
export default ({app},inject) => {
inject('myInjectedFunction',(string) => console.log('that was easy', string))
}
nuxt.config.js:
export default {
plugins: ['~/plugins/combined-inject.js']
}
现在您就可以在context,或者Vue实例中使用this,或者在Vuex中的actions/mutations调用此方法。
ctx-example-component.vue:
export default {
mounted(){
this.$myInjectedFunction('works in mounted')
},
asyncData(context){
context.app.$myInjectedFunction('works with context')
}
}
store/index.js:
export const state = () => ({
someValue: ''
})
export const mutations = {
changeSomeValue(state, newValue) {
this.$myInjectedFunction('accessible in mutations')
state.someValue = newValue
}
}
export const actions = {
setSomeValueToWhatever ({ commit }) {
this.$myInjectedFunction('accessible in actions')
const newValue = "whatever"
commit('changeSomeValue', newValue)
}
}