用 Nuxt.js 搭建一個服務端渲染(SSR)應用
客戶端渲染(CSR)的含義
客戶端渲染模式下,服務端把渲染的靜態(tài)文件給到客戶端,客戶端拿到服務端發(fā)送過來的文件自己跑一遍 JS,根據(jù) JS運行結(jié)果,生成相應 DOM,然后渲染給用戶。
前端渲染的方式起源于 JavaScript 的興起,ajax 的大熱更是讓前端渲染更加成熟,前端渲染真正意義上的實現(xiàn)了前后端分離,前端只專注于 UI 的開發(fā),后端只專注于邏輯的開發(fā),前后端交互只通過約定好的API來交互,后端提供 json 數(shù)據(jù),前端循環(huán) json 生成 DOM 插入到頁面中去。
大多數(shù)平臺采用的是客戶端渲染,查看首頁的源代碼會發(fā)現(xiàn)代碼里的 html 結(jié)構(gòu)只有簡單的幾句。當請求首頁面時,返回的 body 為空,之后執(zhí)行 js 將 html 結(jié)構(gòu)注入到 body 里,結(jié)合 css 顯示出來;
- <body>
- <div id=app></div>
- <script type=text/javascript src=/static/js/manifest.9476fbe0d0f0fe7c5038.js></script>
- </body>
客戶端渲染(CSR)的優(yōu)缺點
- 優(yōu)點:網(wǎng)絡傳輸數(shù)據(jù)量小、減少了服務器壓力、前后端分離、局部刷新,無需每次請求完整頁面、交互好可實現(xiàn)各種效果
- 缺點:不利于SEO、爬蟲看不到完整的程序源碼、首屏渲染慢(渲染前需要下載一堆js和css等)
服務端渲染(SSR)的含義
服務端渲染: 當用戶第一次請求頁面時,由服務器把需要的組件或頁面渲染成 HTML 字符串,然后把它返回給客戶端??蛻舳四玫绞值模强梢灾苯愉秩救缓蟪尸F(xiàn)給用戶的 HTML 內(nèi)容,不需要為了生成 DOM 內(nèi)容自己再去跑一遍 JS 代碼。使用服務端渲染的網(wǎng)站,可以說是“所見即所得”,頁面上呈現(xiàn)的內(nèi)容,我們在 html 源文件里也能找到。如下,我們查看網(wǎng)頁源碼的時候,可以看到全部內(nèi)容。
服務端渲染(SSR)的優(yōu)缺點
優(yōu)點:首屏渲染快、利于SEO、可以生成緩存片段,生成靜態(tài)化文件、節(jié)能(對比客戶端渲染的耗電)
缺點:服務端壓力較大
什么情況下使用服務端渲染
通過服務端渲染的概念以及它的兩個特點:首屏加載速度快、SEO優(yōu)化。我們知道,服務端渲染其實就是由瀏覽器做的一些事情,我們放到了服務端去做。關于在 server 端還是在 browser 端渲染的選擇,更多的是要看業(yè)務場景。
常用框架介紹
服務端渲染框架應用有Nuxt.js 、Beidou(北斗) 等。
Nuxt.js 是一個基于 Vue.js 的輕量級應用框架,可用來創(chuàng)建服務端渲染 (SSR) 應用,也可充當靜態(tài)站點引擎生成靜態(tài)站點應用,具有優(yōu)雅的代碼結(jié)構(gòu)分層和熱加載等特性。
Beidou(北斗) 是 NodeJS & React 同構(gòu)框架,基于Egg.js開發(fā)。
嘗試了這兩個框架,對比覺得Nuxt.js更簡單易上手,下面就用Nuxt.js搭建一個服務端渲染應用來介紹下 Nuxt.js 的用法。
創(chuàng)建一個 SSR 項目
為了快速入門,Nuxt.js團隊創(chuàng)建了腳手架工具 create-nuxt-app。
- npx create-nuxt-app nuxtdemo
它會讓你進行一些選擇,比如集成的服務器端框架、喜歡的UI框架、測試框架、添加 axios、Eslint、 Prettier 等。根據(jù)項目需求進行選擇就好了。這里以服務器框架選擇None (Nuxt默認服務器),UI框架選擇Element UI為例進行講解。
勾選完畢后,它將安裝所有依賴項,因此下一步是直接啟動項目:
- cd nuxtdemo
- npm run dev
這時候我們可以看到一個默認簡易的項目搭建完成啦,如下所示:
接下來,我們來看下整個項目的目錄結(jié)構(gòu):
- ├── assets 未編譯的靜態(tài)資源如 LESS、SASS 或 JavaScript
- ├── components 組件,不會像頁面組件那樣有 asyncData 方法的特性
- ├── layouts 布局目錄 layouts 用于組織應用的布局組件
- ├── middleware 用于存放應用的中間件
- ├── nuxt.config.js 用于組織Nuxt.js 應用的個性化配置,以便覆蓋默認配置
- ├── package.json 用于描述應用的依賴關系和對外暴露的腳本接口
- ├── pages 用于組織應用的路由及視圖
- ├── plugins 存放需要在根vue.js應用實例化之前需要運行的JS插件
- ├── static 用于存放應用的靜態(tài)文件(不會被webpack編譯處理)
- ├── store 應用的 Vuex 狀態(tài)樹
了解了每個文件的作用,我們來用Nuxt.js搭一個簡單的網(wǎng)站吧。用一個簡單的網(wǎng)站,講解下 Nuxt.js 的基礎用法。
Nuxt.js 入門
我們用 Nuxt.js 來搭一個常用的網(wǎng)頁框架,包括公共頭部、底部、動態(tài)路由、嵌套路由,錯誤頁面,以及在 Nuxt.js 框架下如何引用公共樣式、公共方法、路由校驗等。先放上網(wǎng)站成品圖:
下載鏈接:
- git clone git@code.aliyun.com:echomaps/nuxtdemo.git
這是個簡易的網(wǎng)站,包括公共頭部跟尾部。首頁是一個文章列表,采用了動態(tài)路由,點進去可以跳到對應的文章。人員介紹頁面采用了嵌套路由。在左側(cè)點擊人員,右側(cè)可以相應出來人員的信息。好,讓我們來開始吧。
布局
一般網(wǎng)站都有公共的頭部、底部。在之前的項目中,我們都得手動去引入頭部、尾部組件。如下:
- import header from '@/publicResource/components/header.vue'
- import footer from '@/publicResource/components/footer.vue'
- export default {
- components: {
- 'v-header': header,
- 'v-footer': footer
- }
- } :
但在 Nuxt.js 中就不用這么麻煩。我們直接在 layout 目錄下創(chuàng)建自定義的布局。修改 layouts/default.vue 文件來擴展應用的默認布局:
- <template>
- <div>
- <v-header></v-header>
- <nuxt />
- <v-footer></v-footer>
- </div>
- </template>
- <script>
- import Header from '~/components/Header.vue'
- import Footer from '~/components/Footer.vue'
- export default {
- components: {
- 'v-header': Header,
- 'v-footer': Footer
- },
- data () {
- return { }
- }
- }
- </script>
<nuxt/> 組件用于顯示頁面的主體內(nèi)容。這樣所有的頁面都會自動帶上頭部、尾部,不用特意聲明與引入。如果有些頁面布局不需要頭部、尾部,這也很簡單,我們只需要告訴頁面使用哪個自定義布局即可。
- <template>
- <!-- Your template -->
- </template>
- <script>
- export default {
- layout: 'blog'
- // page component definitions
- }
- </script>
錯誤頁面
我們也可以通過編輯 layouts/error.vue 文件來定制化錯誤頁面。這個布局文件不需要包含 <nuxt/> 標簽。可以把這個布局文件當成是顯示應用錯誤(404,500等)的組件。
- <template>
- <div class="error-wrap">
- <p v-if="error.statusCode === 404" class="info">頁面不存在</p>
- <p class="info" v-else>應用發(fā)生錯誤異常</p>
- <p><nuxt-link to="/">首 頁</nuxt-link></p>
- </div>
- </template>
- <script>
- export default {
- props: ['error'],
- }
- </script>
基礎路由
Nuxt.js中不用編寫路由配置文件,只需要按照API規(guī)定命名與存放文件,即可自動生成路由配置文件。例如,我們需要新增一個人員介紹頁面users. 只需要在pages下新增users頁面,就可以自動生成路由。假設 pages 的目錄結(jié)構(gòu)如下:
- pages/
- --| users.vue
- --| index.vue
那么,Nuxt.js 自動生成的路由配置如下:
- router: {
- routes: [
- {
- name: 'index',
- path: '/',
- component: 'pages/index.vue'
- },
- {
- name: 'users',
- path: '/users',
- component: 'pages/users.vue'
- }
- ]
- }
其它頁面引用的時候,直接用nuxt-link即可。
- <nuxt-link to="/users">人員介紹</nuxt-link>
同樣地,我們也可以通過框架規(guī)定的命名、存放文件。無需配置路由,可生成動態(tài)路由、嵌套路由的配置文件。
動態(tài)路由
在 Nuxt.js 里面定義帶參數(shù)的動態(tài)路由,需要創(chuàng)建對應的以下劃線作為前綴的 Vue 文件 或 目錄。如下所示:
- ├── pages
- ├────── blogs
- │ └─── _blog.vue 博客的詳情頁
- ├────── index.vue 首頁
假如我們在index.vue中編寫一個文章列表并鏈接到對應的文章頁面,如下:
- <template>
- <div class="container">
- <div class="bm-sider">
- {{content}}
- </div>
- <div class="bm-con">
- <ul>
- <li><nuxt-link to="blogs/1">這是文章1</nuxt-link></li>
- <li><nuxt-link to="blogs/2">這是文章2</nuxt-link></li>
- <li><nuxt-link to="blogs/3">這是文章3</nuxt-link></li>
- </ul>
- </div>
- </div>
- </template>
pages/blogs/_blog.vue:
- <template>
- <div class="container">
- 這是內(nèi)容{{$route.params.blog}}
- </div>
- </template>
- <script>
- export default {
- components: {},
- data () {
- return { }
- },
- validate ({ params }) {
- return !isNaN(+params.blog)
- }
- }
- </script>
這樣,默認首頁的展示如下:
當點擊具體文章時候,展示如下:
我們還可以添加 validate 配置一個校驗方法用于校驗動態(tài)路由參數(shù)的有效性。如果校驗方法返回的值不為 true 或 Promise 中 resolve 解析為 false 或拋出 Error , Nuxt.js 將自動加載顯示 404 錯誤頁面或 500 錯誤頁面。這里我們設置只有數(shù)字可以正常訪問,其它路由將跳到錯誤頁面。如下所示:
嵌套路由
創(chuàng)建內(nèi)嵌子路由,需要添加一個 Vue 文件,同時添加一個與該文件同名的目錄用來存放子視圖組件。在父組件(.vue文件) 內(nèi)增加用于顯示子視圖內(nèi)容。
人員介紹頁面采用了嵌套路由。點擊左側(cè)的人員名單,將出現(xiàn)對應的人員信息,效果如下:
實現(xiàn)這一效果,我們需要在 pages下添加人員介紹頁面 users.vue:
- <template>
- <div class="container">
- <div class="bm-sider">
- <ul class="players">
- <li v-for="user in users" :key="user.id">
- <NuxtLink :to="'/users/'+user.id">
- {{ user.name }}
- </NuxtLink>
- </li>
- </ul>
- </div>
- <div class="bm-con">
- <NuxtChild :key="$route.params.id" />
- </div>
- </div>
- </template>
同時添加一個與該文件同名的目錄用來存放子視圖組件。文件如下命名:
- ├── users
- │ ├── _id.vue 點擊人員后對應的人員信息組件
- │ └── index.vue 默認的視圖組件
- └── users.vue 人員介紹頁面
users/index.vue:
- <template>
- <h2>Please select an user.</h2>
- </template>
users/_id.vue:
- <template>
- <div class="player">
- <h1>#{{ number }}</h1>
- <h2>{{ name }}</h2>
- </div>
- </template>
- <script>
- export default {
- validate ({ params }) {
- return !isNaN(+params.id)
- },
- asyncData ({ params, env, error }) {
- const user = env.users.find(user => String(user.id) === params.id)
- if (!user) {
- return error({ message: 'User not found', statusCode: 404 })
- }
- return user
- },
- head () {
- return {
- title: this.name
- }
- }
- }
- </script>
這樣,當我們未點擊人員時候,人員介紹默認頁面是這樣的:
點擊人員后,人員介紹頁面將展示對應的人員信息內(nèi)容:
全局 css
在 Nuxt 中添加全局 css 也是非常簡單的。我們在 assets 下新建一個 css 文件 base.css 。然后在 nuxt.config.js 中引用即可。
- css: [
- '~assets/base.css',
- ],
全局方法
將內(nèi)容注入 Vue 實例,避免重復引入,在 Vue 原型上掛載注入一個函數(shù),所有組件內(nèi)都可以訪問。
- import Vue from 'vue'
- Vue.prototype.$myInjectedFunction = (string) => console.log("This is an example", string)
這樣,我們就可以在所有Vue組件中使用該函數(shù)。
- export default {
- mounted(){
- this.$myInjectedFunction('test')
- }
- }
總結(jié)
Nuxt.js 是使用 Webpack 和 Node.js 進行封裝的基于 Vue 的 SSR 框架,使用它,你可以不需要自己搭建一套 SSR 程序,而是通過其約定好的文件結(jié)構(gòu)和API就可以實現(xiàn)一個首屏渲染的 Web 應用。整體上,Nuxt.js 通過各個文件夾和配置文件的約束來管理我們的程序,而又不失擴展性。