最近在一個資訊類的項目中用了 Next.js 服務(wù)端渲染,體驗了一把服務(wù)端渲染的速度,首屏直出奢浑,渲染速度666虏杰。
服務(wù)端渲染
在之前前后端沒分離的時候讥蟆,前端簡單寫一下 HTML 模版,后端通過諸如 Php纺阔、Java 等各種模版引擎把靜態(tài)頁面處理成動態(tài)模版渲染出來瘸彤,那個時候就是服務(wù)端渲染了。但這樣好麻煩笛钝,歷史車輪滾滾向前质况,單頁面應(yīng)用的時代,后端只提供接口玻靡,不再負責(zé)模板的處理结榄。再后來為了解決 SEO 的問題,順帶首屏渲染的問題囤捻,總不能再回到前后端不分離的年代吧臼朗,所以就是前端er自己選擇的路,跪著也要走下蝎土,Vue 的 Nuxt.js视哑,React 的 Next.js 等 SSR 框架應(yīng)運而生。
我們平常廣泛使用 Vue誊涯、React 在客戶端渲染挡毅,服務(wù)端返回了一個空的 HTML 模版,然后內(nèi)部加載JS暴构,生成并操作 Dom跪呈,最后由瀏覽器渲染出頁面段磨,這樣一系列的動作下來首屏加載會顯的慢了不少。另外由于是個空的頁面庆械,爬蟲無法識別薇溃,不利于 SEO,在瀏覽器中右鍵查看源碼缭乘,可以看到頁面是個空的 HTML沐序。
服務(wù)端渲染(SSR),服務(wù)端直接返回了 HTML堕绩,瀏覽器顯示即可策幼,無需等待 JavaScript 完成下載且執(zhí)行才顯示內(nèi)容,不僅渲染速度大大加快奴紧,更利于搜索引擎的爬取特姐,右鍵查看源碼可以看到密密麻麻的 HTML 標簽。
優(yōu)點
- 更快的首屏加載速度
- 更友好的 SEO
缺點
- 增加了維護成本
- 項目部署比單頁面應(yīng)用復(fù)雜
Next.js 基本使用
路由系統(tǒng)
pages 目錄路由
Next.js 的路由系統(tǒng)基于文件路徑自動映射黍氮,pages 目錄內(nèi)的文件會被自動處理成路由唐含,所以通常 pages 下我們只放置頁面路由的文件,其它組件不要放到 pages 目錄下面沫浆。比如在 pages 目錄下面創(chuàng)建了 login.js 和 register.js捷枯,那么路由就對應(yīng) /login 和 /register。
同樣专执,多級路由也是類似的處理淮捆。例如在 pages 下創(chuàng)建了 user 目錄,user 下創(chuàng)建 login 和 register 文件本股。/user/login 對應(yīng) login 文件攀痊,/user/register 對應(yīng) register 文件。
Next.js 的路由系統(tǒng)使我們不用再去關(guān)心路由拄显,合理在 pages 建立目錄苟径。
動態(tài)路由
Next.js 也支持支持動態(tài)路由,在文件前攜帶 [param]躬审,例如
pages/post/[pid].js
涩笤,會匹配到海報詳情頁面 /post/pid。-
預(yù)定義的 API 路由優(yōu)先于動態(tài) API 路由
- pages/post/create.js盒件, 將匹配 /post/create
- pages/post/[pid].js`,將匹配 /post/1,但不匹配 /post/create
路由跳轉(zhuǎn)與傳參
next 提供了兩種方式舱禽,分別是導(dǎo)航式路由 next/link 和 編程式 next/router
-
Link
href 為必須屬性炒刁,可傳遞對象
<Link href="/about?name=jackylin"> <Link href={{ pathname: '/article', query: { type: active } }}>
-
編程式導(dǎo)航 next/router
和 react hooks 中的 useHistory 用法一樣
import { useRouter } from 'next/router' const router = useRouter() //: 1 router.push(`/article/${c.queueId}`) //: 2 router.push({ pathname: '/publish', query: { contentId: c.contentId, status: active } })
路由參數(shù)獲取
Next.js 只能通過 query 來傳遞參數(shù),不能使用 params誊稚。
useRouter 或 getServerSideProps 方法內(nèi)都可以拿到 query 參數(shù)
import { useRouter } from 'next/router'
const { query } = useRouter()
query.cid //: 獲取 cid 參數(shù)
這種動態(tài)路由的參數(shù)通過 query 可以獲取到翔始,在 getServerSideProps 方法內(nèi)也可以通過 params 獲取
router.push(`/article/${c.queueId}`)
css
Next.js 支持 Css Module 和 Css-in-JS 這兩種方式罗心,二者自帶樣式隔離。
動態(tài)導(dǎo)入
Next.js 同樣支持和 React 客戶端一樣的 ES2020 import() 語法來實現(xiàn)導(dǎo)入城瞎,在 React 單頁面項目里面渤闷,Webpack 解析到該語法時會自動進行代碼分割。在 Next.js 里面脖镀, 還可以使用next/dynamic
來動態(tài)導(dǎo)入組件飒箭,它們將在客戶端懶加載。通過動態(tài)導(dǎo)入蜒灰,對于一些不需要在服務(wù)端渲染的組件可以使用 dynamic 來處理弦蹂。
const BreadCrumb = dynamic(() => import('@/components/ui/BreadCrumb'))
服務(wù)端請求 getServerSideProps
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
getServerSideProps,主要用于在服務(wù)端請求數(shù)據(jù)强窖,比如我們列表的的首屏數(shù)據(jù)凸椿,像列表下一頁的數(shù)據(jù)我們則可以放到客戶端去獲取。context 參數(shù)里面包含路由參數(shù)等對象翅溺。
由于 getServerSideProps 是在服務(wù)端進行的請求脑漫,所以相關(guān)的 log 信息在終端才能看到,Network 面板里面是看不到請求情況包括 console 信息咙崎。
getServerSideProps 返回數(shù)據(jù)后优幸,Next.js 會把這些數(shù)據(jù)寫到 HTML源 碼里面,即
window.__NEXT_DATA__.props
叙凡,這樣就實現(xiàn)了把數(shù)據(jù)傳送到客戶端劈伴,客戶端有了這些數(shù)據(jù),比如可以拿著window.__NEXT_DATA__.props
里面的數(shù)據(jù)初始化 React 組件的 props 等握爷。在 console 里輸入__NEXT_DATA__
,你就能看到 Next.js 在頁面中封裝了什么數(shù)據(jù)跛璧。
其它
- 全面兼容最新 React 17版本
- 鏈接跳轉(zhuǎn)使用 Link,避免使用編程式導(dǎo)航新啼,有利于 SEO追城。
- Next.js 自帶 Image 組件,自動優(yōu)化圖像燥撞,極大改善用戶體驗座柱。
- 可以自定義 document 頁面 404頁面、500頁面等物舒。當 Next.js 捕獲到錯誤時色洞,不論是接口錯誤還是代碼運行時的錯誤,Next.js 服務(wù)內(nèi)部會統(tǒng)一轉(zhuǎn)化并拋出500異常冠胯。
- 官方文檔
服務(wù)端渲染常見問題
最后呢總結(jié)一下 Next.js 使用中遇到的問題火诸,歡迎各路俠客補充。
useEffect 的注意點
服務(wù)端渲染時拿不到屏幕荠察、元素寬高等尺寸信息置蜀,不要讓元素的顯示依賴于 useEffect 里面的設(shè)備寬高奈搜、 dom 位置計算。
useEffect(() => {
//:錯誤示例
//:服務(wù)端渲染的時候拿不到設(shè)備寬度deviceWidth 所以isDesktop的值不會改變
deviceWidth > 960 && (isDesktop = true)
, [])
return (
<>
{isDesktop ? (
<div>pc</div>
) : null}
</>
)
一個響應(yīng)式的頁面盯荤,pc 端顯示 HeaderBar 組件馋吗,m 端不顯示,如何處理秋秤?
在客戶端渲染的情況下宏粤,判斷一下設(shè)備類型即可。但是在服務(wù)端渲染航缀,在哪里判斷設(shè)備類型呢商架?useEffect?不能在里面判斷芥玉。
方案有二:
- 采用客戶端渲染的處理方式蛇摸。使用 next/dynamic 動態(tài)導(dǎo)入 HeaderBar 組件,這樣 HeaderBar 組件將在客戶端進行渲染,其它元素依然還是在服務(wù)端進行渲染。
- 依然采用服務(wù)端渲染的處理方式挟憔。通過 css 的媒體查詢,在 m 端對 HeaderBar 進行
display:none
饿肺,這也是一種處理辦法。