
文章說明:
1. 之前想搭建個人博客,由于學習的是react技術棧,所以就到處搜羅資料學了nextjs,配合koa就把博客搭起來了。該系列文章基于我的學習筆記,重新整理了一遍,如果有錯誤之處,還請指正。
2. 個人能力有限,我更注重的是使用,所以對于許多原理也只是簡單理解,并未深究。如果是想研究源碼,或者追求性能深度優化的老鐵,可以不用往下看了
3. 轉載請注明出處
各工具版本:npm v6.10,node v12.13,react v16.12,next v9.1
可能需要的預備知識:React項目經驗、nodejs開發經驗,webpack、npm等常用工具使用經驗。最好會koa(部分地方可能會用到)
一、Nextjs與服務端渲染
Nextjs的官方解釋:Next.js 是一個輕量級的 React 服務端渲染應用框架。
服務端渲染,顧名思義,就是在服務器上就把網頁渲染好了,你請求時,直接發給你渲染好的頁面。知道了原理,ssr的優勢劣勢也就很明顯了:
與客戶端渲染比較
優勢:
1. 更利于SEO,便于搜索引擎收錄。因為大多數爬蟲只會爬源碼,不會爬腳本,當服務端返回渲染好的數據后爬蟲便能直接爬取了。
2. 利于首屏加載。這個簡單,服務端發過來的就是渲染好的,客戶端就省事了,加載也就快了。
劣勢就是:1. 服務器的壓力大了(多了活要干);2. 對開發人員要求也高了
(深有體會,量發而行!這已經不屬純正血統的前端范疇了,因為要成功部署的話,你還不得不心甘情愿地去學點服務器知識、Linux運維、nginx部署等)
特別注意
- 當服務端渲染時,服務端是沒有window、document對象的(瀏覽器端才有),直接調用會報錯。想用這倆對象的話最好放在didAmount周期函數里,等組件掛載后再調用(其實從這一點來說,這只能算是半服務端渲染......扯遠了)
- 另外,Nextjs自帶的服務器專注于處理ssr部分,但后端往往還需要處理文件、連接數據庫等功能,此時還需要借助其他的node服務器,我這選用了koa2,然后讓nextjs作為koa的一個專門負責ssr的中間件。
二、搭建Nextjs項目
實踐出真知。介紹完了,作為正經的程序員,先上手一個再慢慢研究。我們按照官網的節奏,一步步搭建項目,再一步步解釋每個文件和目錄的作用
1. 搭建環境
- 隨便創建個項目目錄,如:demo
- 進入該目錄,npm初始化生成package.json:
npm init
- npm安裝nextjs和其依賴:
npm install next react react-dom
- 之后再修改package.json的scripts模塊:
{"scripts": {"dev": "next","build": "next build","start": "next start"}
}
說明:和react項目類似,這里的命令build即部署時的打包命令,start運行打包后的文件命令,dev則是開發環境下啟動nextjs服務器。
到這里,環境就算搭建好了。
2. 創建pages目錄
Nextjs的路由系統非常簡單,所有的路由頁面全部存放在pages目錄下,nextjs會自動對應page目錄的文件路徑生成對應路由。
如,我們在pages創建demo.js:
export default () => <div>This is the demo page</div>
然后啟動next服務器:npm run dev
命令行顯示如下時表示啟動成功了
D:webdemo>npm run dev> next_test@1.0.0 dev D:webdemo
> next[ wait ] starting the development server ...
[ info ] waiting on http://localhost:3000 ...
此時打開瀏覽器,進入localhost:3000/demo,便能看到頁面了

3._app.js和 _document.js
特別的,pages下有兩個特殊文件:_app.js和_document.js。這兩個文件默認是隱藏的,新建的話就會覆蓋之前的文件。他們分別用來自定義APP和自定義Document
自定義?什么意思?有什么用?簡單來說就是用來定義一些頁面共用的屬性(如設置全局css,通用布局等),或者定義一些通用的動作(如獲取、傳遞數據等), 這個結合nextjs的getInitialProps函數會更好說明,就留著后續講getInitialProps數據獲取和頁面布局的時候再一并解釋吧。
這篇文章我們主要關注路由系統。
三、路由系統
保證pages目錄的干凈
對于現在的組件化開發,我們通常會有兩種組件,一種作為某個獨立功能的小組件,一種是作為頁面顯示的頁面組件(通常結合了多個小組件),Nexjs同樣適用, 但小組件不能存儲在pages目錄下,這會導致路由系統混亂。我們可以新建一個components目錄(根據自己喜好自定義名字)來存儲小組件,在需要時從該目錄下import使用即可。
多級路由:
如果路由有多級,直接在pages下創立父級目錄,再把最終路由文件放入目錄下即可實現多級路由。如在pages目錄下創建user目錄,user下再創建index.js和home.js,那么路由/user就對應index.js文件,/user/home就對應home.js文件
路由跳轉
Nextjs官方推薦了兩種跳轉方式,一種是Link組件包裹,一種使用Router,我個人是不推薦Link的,原理也是用Router實現的,使用也簡單,但用起來總感覺很冗余。我這里主要介紹Router,想了解Link的老鐵得麻煩移步官網了。
Nextjs提供了一個'next/router'的包,專門用來處理路由。Router便是其中一個對象,Router.push('url')進行跳轉。
實踐一下:
1. pages目錄下,創建index.js文件
import React from 'react'
import Router from 'next/router'export default () => {return(<><button onClick={()=>Router.push('/demo')} >送我去demo頁</button><div>這里是主頁</div></>)
}
2. 修改demo.js文件
import React from 'react'
import Router from 'next/router'export default () => {return(<><button onClick={()=>Router.push('/')} >送我去主頁</button><div>這里是demo頁</div></>)
}
3. 運行結果

點擊“送我去demo頁”按鈕后

路由參數
注意!注意!Nextjs不能使用params傳參數!只能通過query!
像這樣
Router.push('url?id=1')
等價
Router.push({pathname:'url',query:{id:1}})
另外,前面說過,服務端渲染時沒有window對象的,自然不能通過傳統途徑獲取url參數,這里'next/router'里提供了一個withRouter對象,用它包裹組件后,組件會多出router的參數,通過router就能獲取query參數了
import React from 'react'
import Router,{ withRouter } from 'next/router'const Demo = (props) => {return(<><button onClick={()=>Router.push('/')} >送我去主頁</button><div>這里是demo頁</div><div>{props.router.query.id}</div></>)
}
export default withRouter(Demo);

路由映射
我們常看到的url是這樣的/url?id=1,而路由映射可以幫我們顯示成為這樣 /url/1,比較美觀友好(其實也就好一點點),這小節講解下路由映射,會涉及到koa使用,不感興趣的小伙伴直接跳過吧,畢竟也不是什么特別重要的
"表面上"的實現方法:
1. Router.push({ pathname: '/demo', query: { id: 1 } },'/demo/1')Router.push的第三個參數即可指定別名
2. Link組件中的as屬性<Link href='/demo?id=1' as='/demo/1'>
這樣看上去的確可以了,初始時也能訪問,但頁面一刷新就會404,為什么?因為當我們點擊按鈕在瀏覽器端跳轉時,是瀏覽器去找頁面,它通過路由映射可以找到。而刷新的時候,是服務器去找,而我們的pages頁面里面沒有/demo/1的文件,所以就報404了。
解決辦法,利用koa處理:
router.get('/demo/:id',async (ctx)=>{cosnt id = ctx.params.id await handle(ctx.req,ctx.res,{pathname:'/demo',query: {id}}),ctx.respond = false
})
其實就是在服務器處又將路由轉換回來而已。
路由鉤子
Router中還定義了幾個鉤子函數用來獲取路由轉變時的狀態,方便我們在轉換路由時進行操作
// routeChangeStart history模式路由改變剛開始
// routeChangeComplete history模式路由改變結束
// routeChangeError 路由改變失敗
// hashChangeStart hash模式路由改變剛開始
// hashChangeComplete hash模式路由改變結束Router.events.on(event,func()) //event即監聽的事件(以上5種),func回調函數
Router.events.off(event,func()) //取消監聽
好了,路由系統就講解到這了,下一篇文章會講解到nextjs的布局和getInitialProps()函數,順帶會把這期遺留的_app.js和_document.js一并解釋了。
喜歡的話歡迎分享,歡迎討論
歡迎關注我的其他平臺賬號: 【知乎】均遠 【公眾號】佛系前端 【個人博客】http://xujunyuan.com 【GitHub】JunYuanHub