Next.js 是怎么做预渲染的
前言
打開next.js官網(wǎng),首先映入眼簾的是它的 Slogan 和介紹:
The React Framework for Production
Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.
Next.js 提供了生產(chǎn)環(huán)境所需的所有功能以及最佳實(shí)踐,包括構(gòu)建時(shí)預(yù)渲染、服務(wù)端渲染、路由預(yù)加載、智能打包、零配置等。其中,Next.js 以其優(yōu)秀的構(gòu)建時(shí)渲染和服務(wù)端渲染能力,成為當(dāng)今 React 生態(tài)中最受歡迎的框架之一。本文將介紹 Next.js 提供的三種預(yù)渲染模式以及混合渲染模式,來(lái)看看 Next.js 是怎么做預(yù)渲染的。
三種預(yù)渲染模式
普通的單頁(yè)應(yīng)用只有一個(gè) HTML,初次請(qǐng)求返回的 HTML 中沒(méi)有任何頁(yè)面內(nèi)容,需要通過(guò)網(wǎng)絡(luò)請(qǐng)求 JS bundle 并渲染,整個(gè)渲染過(guò)程都在客戶端完成,所以叫客戶端渲染(CSR)。這種渲染方式雖然在后續(xù)的頁(yè)面切換速度很快,但是也明顯存在兩個(gè)問(wèn)題:
白屏?xí)r間過(guò)長(zhǎng):在 JS bundle 返回之前,頁(yè)面一直是空白的。假如 bundle 體積過(guò)大或者網(wǎng)絡(luò)條件不好的情況下,體驗(yàn)會(huì)更不好
SEO 不友好:搜索引擎訪問(wèn)頁(yè)面時(shí),只會(huì)看 HTML 中的內(nèi)容,默認(rèn)是不會(huì)執(zhí)行 JS,所以抓取不到頁(yè)面的具體內(nèi)容
而 Next.js 提供的三種預(yù)渲染模式,均在 CSR 開始前,在服務(wù)端預(yù)先渲染出頁(yè)面內(nèi)容,避免出現(xiàn)白屏?xí)r間過(guò)長(zhǎng)和 SEO 不友好的問(wèn)題。
SSR
為了解決上面出現(xiàn)的兩個(gè)問(wèn)題,SSR(Server Side Rendering)誕生了。相信大家對(duì) SSR 不會(huì)陌生,它是在服務(wù)端直接實(shí)時(shí)同構(gòu)渲染當(dāng)前用戶訪問(wèn)的頁(yè)面,返回的 HTML 包含頁(yè)面具體內(nèi)容,提高用戶的體驗(yàn)。React 從框架層面直接提供支持,只需要調(diào)用renderToString(Component)函數(shù)即可得到 HTML 內(nèi)容。
Next.js 提供getServerSideProps異步函數(shù),以在 SSR 場(chǎng)景下獲取額外的數(shù)據(jù)并返回給組件進(jìn)行渲染。getServerSideProps可以拿到每次請(qǐng)求的上下文(Context),舉個(gè)例子:
export default function FirstPost(props) {
// 在 props 中拿到數(shù)據(jù)
const { title } = props;
return (
<Layout>
<h1>{title}</h1>
</Layout>
)
}
export async function getServerSideProps(context) {
console.log('context', context.req);
// 模擬獲取數(shù)據(jù)
const title = await getTitle(context.req);
// 把數(shù)據(jù)放在 props 對(duì)象中返回出去
return {
props: {
title
}
}
}
SSR 方案雖然解決了 CSR 帶來(lái)的兩個(gè)問(wèn)題,但是同時(shí)又引入另一個(gè)問(wèn)題:需要一個(gè)服務(wù)器承載頁(yè)面的實(shí)時(shí)請(qǐng)求、渲染和響應(yīng),這無(wú)疑會(huì)增大服務(wù)端開發(fā)和運(yùn)維的成本。另外對(duì)于一些較為靜態(tài)場(chǎng)景,比如博客、官網(wǎng)等,它們的內(nèi)容相對(duì)來(lái)說(shuō)比較確定,變化不頻繁,每次通過(guò)服務(wù)端渲染出來(lái)的內(nèi)容都是一樣的,無(wú)疑浪費(fèi)了很多沒(méi)必要的服務(wù)器資源。這時(shí),有沒(méi)有一種方案可以讓這些頁(yè)面變得靜態(tài)呢?這時(shí),靜態(tài)站點(diǎn)生成(SSG,也叫構(gòu)建時(shí)預(yù)渲染)誕生了。
SSG
SSG(Static Site Generation) 是指在應(yīng)用編譯構(gòu)建時(shí)預(yù)先渲染頁(yè)面,并生成靜態(tài)的 HTML。把生成的 HTML 靜態(tài)資源部署到服務(wù)器后,瀏覽器不僅首次能請(qǐng)求到帶頁(yè)面內(nèi)容的 HTML ,而且不需要服務(wù)器實(shí)時(shí)渲染和響應(yīng),大大節(jié)約了服務(wù)器運(yùn)維成本和資源。
Next.js 默認(rèn)為每個(gè)頁(yè)面開啟 SSG。對(duì)于頁(yè)面內(nèi)容需要依賴靜態(tài)數(shù)據(jù)的場(chǎng)景,允許在每個(gè)頁(yè)面中export一個(gè)getStaticProps異步函數(shù),在這個(gè)函數(shù)中可以把該頁(yè)面組件所需要的數(shù)據(jù)收集并返回。當(dāng)getStaticProps函數(shù)執(zhí)行完成后,頁(yè)面組件就能在props中拿到這些數(shù)據(jù)并執(zhí)行靜態(tài)渲染。舉個(gè)在靜態(tài)路由中使用 SSG 的例子:
// pages/posts/first-post.js
function Post(props) {
const { postData } = props;
return <div>{postData.title}</div>
}
export async function getStaticProps() {
// 模擬獲取靜態(tài)數(shù)據(jù)
const postData = await getPostData();
return {
props: { postData }
}
}
對(duì)于動(dòng)態(tài)路由的場(chǎng)景,Next.js 是如何做 SSG 的呢?Next.js 提供getStaticPaths異步函數(shù),在這個(gè)方法中,會(huì)返回一個(gè)paths數(shù)組,這個(gè)數(shù)組包含了這個(gè)動(dòng)態(tài)路由在構(gòu)建時(shí)需要預(yù)渲染的頁(yè)面數(shù)據(jù)。舉個(gè)例子:
// pages/posts/[id].js
function Post(props) {
const { postData } = props;
return <div>{postData.title}</div>
}
export async function getStaticPaths() {
// 返回該動(dòng)態(tài)路由可能會(huì)渲染的頁(yè)面數(shù)據(jù),比如 params.id
const paths = [
{
params: { id: 'ssg-ssr' }
},
{
params: { id: 'pre-rendering' }
}
]
return {
paths,
// 命中尚未生成靜態(tài)頁(yè)面的路由直接返回 404 頁(yè)面
fallback: false
}
}
export async function getStaticProps({ params }) {
// 使用 params.id 獲取對(duì)應(yīng)的靜態(tài)數(shù)據(jù)
const postData = await getPostData(params.id)
return {
props: {
postData
}
}
}
當(dāng)我們執(zhí)行nextjs build后,可以看到打包結(jié)果包含pre-rendering.html和ssg-ssr.html兩個(gè) HTML 頁(yè)面,也就是說(shuō)在執(zhí)行 SSG 時(shí),會(huì)對(duì)getStaticPaths函數(shù)返回的paths數(shù)組進(jìn)行循環(huán),逐一預(yù)渲染頁(yè)面組件并生成 HTML。
├── server
| ├── chunks
| ├── pages
| | ├── api
| | ├── index.html
| | ├── index.js
| | ├── index.json
| | └── posts
| | ├── [id].js
| | ├── first-post.html
| | ├── first-post.js
| | ├── pre-rendering.html # 預(yù)渲染生成 pre-rendering 頁(yè)面
| | ├── pre-rendering.json
| | ├── ssg-ssr.html # 預(yù)渲染生成 ssg-ssr 頁(yè)面
| | └── ssg-ssr.json
SSG 雖然很好解決了白屏?xí)r間過(guò)長(zhǎng)和 SEO 不友好的問(wèn)題,但是它僅僅適合于頁(yè)面內(nèi)容較為靜態(tài)的場(chǎng)景,比如官網(wǎng)、博客等。面對(duì)頁(yè)面數(shù)據(jù)更新頻繁或頁(yè)面數(shù)量很多的情況,它似乎顯得有點(diǎn)束手無(wú)策,畢竟在靜態(tài)構(gòu)建時(shí)不能拿到最新的數(shù)據(jù)和無(wú)法枚舉海量頁(yè)面。這時(shí),就需要增量靜態(tài)再生成(Incremental Static Regeneration)方案了。
ISR
Next.js 推出的 ISR(Incremental Static Regeneration) 方案,允許在應(yīng)用運(yùn)行時(shí)再重新生成每個(gè)頁(yè)面 HTML,而不需要重新構(gòu)建整個(gè)應(yīng)用。這樣即使有海量頁(yè)面,也能使用上 SSG 的特性。一般來(lái)說(shuō),使用 ISR 需要getStaticPaths和getStaticProps同時(shí)配合使用。舉個(gè)例子:
// pages/posts/[id].js
function Post(props) {
const { postData } = props;
return <div>{postData.title}</div>
}
export async function getStaticPaths() {
const paths = await fetch('https://.../posts');
return {
paths,
// 頁(yè)面請(qǐng)求的降級(jí)策略,這里是指不降級(jí),等待頁(yè)面生成后再返回,類似于 SSR
fallback: 'blocking'
}
}
export async function getStaticProps({ params }) {
// 使用 params.id 獲取對(duì)應(yīng)的靜態(tài)數(shù)據(jù)
const postData = await getPostData(params.id)
return {
props: {
postData
},
// 開啟 ISR,最多每10s重新生成一次頁(yè)面
revalidate: 10,
}
}
在應(yīng)用編譯構(gòu)建階段,會(huì)生成已經(jīng)確定的靜態(tài)頁(yè)面,和上面 SSG 執(zhí)行流程一致。
在getStaticProps函數(shù)返回的對(duì)象中增加revalidate屬性,表示開啟 ISR。在上面的例子中,指定revalidate = 10,表示最多10秒內(nèi)重新生成一次靜態(tài) HTML。當(dāng)瀏覽器請(qǐng)求已在構(gòu)建時(shí)渲染生成的頁(yè)面時(shí),首先返回的是緩存的 HTML,10s 后頁(yè)面開始重新渲染,頁(yè)面成功生成后,更新緩存,瀏覽器再次請(qǐng)求頁(yè)面時(shí)就能拿到最新渲染的頁(yè)面內(nèi)容了。
對(duì)于瀏覽器請(qǐng)求構(gòu)建時(shí)未生成的頁(yè)面時(shí),會(huì)馬上生成靜態(tài) HTML。在這個(gè)過(guò)程中,getStaticPaths返回的fallback字段有以下的選項(xiàng):
fallback: 'blocking':不降級(jí),并且要求用戶請(qǐng)求一直等到新頁(yè)面靜態(tài)生成結(jié)束,靜態(tài)頁(yè)面生成結(jié)束后會(huì)緩存
fallback: true:降級(jí),先返回降級(jí)頁(yè)面,當(dāng)靜態(tài)頁(yè)面生成結(jié)束后,會(huì)返回一個(gè) JSON 供降級(jí)頁(yè)面 CSR 使用,經(jīng)過(guò)二次渲染后,完整頁(yè)面出來(lái)了
在上面的例子中,使用的是不降級(jí)方案(fallback: 'blocking'),實(shí)際上和 SSR 方案有相似之處,都是阻塞渲染,只不過(guò)多了緩存而已。
If fallback is 'blocking', new paths not returned by getStaticPaths will wait for the HTML to be generated, identical to SSR (hence why blocking), and then be cached for future requests so it only happens once per path.
也不是所有場(chǎng)景都適合使用 ISR。對(duì)于實(shí)時(shí)性要求較高的場(chǎng)景,比如新聞資訊類的網(wǎng)站,可能 SSR 才是最好的選擇。
混合渲染模式
Next.js 不僅支持 SSR、SSG、CSR、ISR,還支持渲染模式的混合使用。下面將介紹三種混合渲染模式。
SSR + CSR
上面已經(jīng)提及過(guò),SSR 似乎已經(jīng)解決了 CSR 帶來(lái)的問(wèn)題,那是不是 CSR 完全沒(méi)有用武之地呢?其實(shí)并不是。使用 CSR 時(shí),頁(yè)面切換無(wú)需刷新,無(wú)需重新請(qǐng)求整個(gè) HTML 的內(nèi)容。既然如此,可以各取所長(zhǎng),各補(bǔ)其短,于是就有 SSR + CSR 的方案:
首次加載頁(yè)面走 SSR:保證首屏加載速度的同時(shí),并且滿足 SEO 的訴求
頁(yè)面切換走 CSR:Next.js 會(huì)發(fā)起一次網(wǎng)絡(luò)請(qǐng)求,執(zhí)行getServerSideProps函數(shù),拿到它返回的數(shù)據(jù)后,進(jìn)行頁(yè)面渲染
二者的有機(jī)結(jié)合,大大減少后端服務(wù)器的壓力和成本的同時(shí),也能提高頁(yè)面切換的速度,進(jìn)一步提升用戶的體驗(yàn)。除了 Next.js,還有其他的框架也使用 SSR + CSR 方案,比如ice.js等。
SSG + CSR
在上面已提及過(guò),SSR 需要較高的服務(wù)器運(yùn)維成本。對(duì)于某些靜態(tài)網(wǎng)站或者實(shí)時(shí)性要求較低的網(wǎng)站來(lái)說(shuō),是沒(méi)有必要使用 SSR 的。假如用 SSG 代替 SSR,使用 SSG + CSR 方案,是不是會(huì)更好:
靜態(tài)內(nèi)容走 SSG:對(duì)于頁(yè)面中較為靜態(tài)的內(nèi)容,比如導(dǎo)航欄、布局等,可以在編譯構(gòu)建時(shí)預(yù)先渲染靜態(tài) HTML
動(dòng)態(tài)內(nèi)容走 CSR:一般會(huì)在useEffect中請(qǐng)求接口獲取動(dòng)態(tài)數(shù)據(jù),然后進(jìn)行頁(yè)面重新渲染
雖然從體驗(yàn)來(lái)說(shuō),動(dòng)態(tài)內(nèi)容需要頁(yè)面重新渲染后才能出現(xiàn),體驗(yàn)上沒(méi)有 SSR 好,但是避免 SSR 帶來(lái)的高額服務(wù)器成本的同時(shí),也能保證首屏渲染時(shí)間不會(huì)太長(zhǎng),相比純 CSR 來(lái)說(shuō),還是提升了用戶體驗(yàn)。
SSG + SSR
在上面介紹的 ISR 方案時(shí)提及過(guò),ISR 的實(shí)質(zhì)是 SSG + SSR:
靜態(tài)內(nèi)容走 SSG:編譯構(gòu)建時(shí)把相對(duì)靜態(tài)的頁(yè)面預(yù)先渲染生成 HTML,瀏覽器請(qǐng)求時(shí)直接返回靜態(tài) HTML
動(dòng)態(tài)內(nèi)容走 SSR:瀏覽器請(qǐng)求未預(yù)先渲染的頁(yè)面,在運(yùn)行時(shí)通過(guò) SSR 渲染生成頁(yè)面,然后返回到瀏覽器,并緩存靜態(tài) HTML,下次命中緩存時(shí)直接返回
ISR 相比于 SSG + CSR 來(lái)說(shuō),動(dòng)態(tài)內(nèi)容可以直接直出,進(jìn)一步提升了首次訪問(wèn)頁(yè)面時(shí)的體驗(yàn);相比于 SSR + CSR 來(lái)說(shuō),減少?zèng)]必要的靜態(tài)頁(yè)面渲染,節(jié)省了一部分后端服務(wù)器成本。
總結(jié)
本文首先介紹了 Next.js 提供的三種預(yù)渲染模式:SSR、SSG、ISR,并分別說(shuō)明了它們的優(yōu)缺點(diǎn)以及可能適用于哪些場(chǎng)景。后面介紹了 Next.js 目前支持的三種混合渲染模式,并和其他的渲染模式進(jìn)行對(duì)比。
總的來(lái)說(shuō),沒(méi)有十全十美的渲染方案,都需要根據(jù)實(shí)際場(chǎng)景進(jìn)行權(quán)衡和取舍。
參考鏈接:
Next.js 官方文檔
《從 Next.js 看企業(yè)級(jí)框架的 SSR 支持》
《新一代 Web 建站技術(shù)展的演進(jìn):SSR、SSG、ISR、DPR 都在做什么?》
編輯于 08-03
漫思
總結(jié)
以上是生活随笔為你收集整理的Next.js 是怎么做预渲染的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: DbEntry在Vs2012里的配置
- 下一篇: JavaScript ES6函数式编程(