Next.js 是怎麼作預渲染的

做者:逸恆html

前言

打開 next.js 官網,首先映入眼簾的是它的 Slogan 和介紹:git

The React Framework for Productiongithub

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.json

Next.js 提供了生產環境所需的全部功能以及最佳實踐,包括構建時預渲染、服務端渲染、路由預加載、智能打包、零配置等。其中,Next.js 以其優秀的構建時渲染和服務端渲染能力,成爲當今 React 生態中最受歡迎的框架之一。本文將介紹 Next.js 提供的三種預渲染模式以及混合渲染模式,來看看 Next.js 是怎麼作預渲染的。後端

三種預渲染模式

普通的單頁應用只有一個 HTML,初次請求返回的 HTML 中沒有任何頁面內容,須要經過網絡請求 JS bundle 並渲染,整個渲染過程都在客戶端完成,因此叫客戶端渲染(CSR)。這種渲染方式雖然在後續的頁面切換速度很快,可是也明顯存在兩個問題:api

  1. 白屏時間過長:在 JS bundle 返回以前,頁面一直是空白的。假如 bundle 體積過大或者網絡條件很差的狀況下,體驗會更很差
  2. SEO 不友好:搜索引擎訪問頁面時,只會看 HTML 中的內容,默認是不會執行 JS,因此抓取不到頁面的具體內容

image.png

而 Next.js 提供的三種預渲染模式,均在 CSR 開始前,在服務端預先渲染出頁面內容,避免出現白屏時間過長和 SEO 不友好的問題。數組

SSR

爲了解決上面出現的兩個問題,SSR(Server Side Rendering)誕生了。相信你們對 SSR 不會陌生,它是在服務端直接實時同構渲染當前用戶訪問的頁面,返回的 HTML 包含頁面具體內容,提升用戶的體驗。React 從框架層面直接提供支持,只須要調用 renderToString(Component) 函數便可獲得 HTML 內容。瀏覽器

image.png

Next.js 提供 getServerSideProps 異步函數,以在 SSR 場景下獲取額外的數據並返回給組件進行渲染。getServerSideProps 能夠拿到每次請求的上下文(Context),舉個例子:緩存

export default function FirstPost(props) {
  // 在 props 中拿到數據
  const { title } = props;
  return (
    <Layout> <h1>{title}</h1> </Layout>
  )
}

export async function getServerSideProps(context) {
  console.log('context', context.req);
  // 模擬獲取數據
  const title = await getTitle(context.req);
  // 把數據放在 props 對象中返回出去
  return {
    props: {
      title
    }
  }
}
 
複製代碼

SSR 方案雖然解決了 CSR 帶來的兩個問題,可是同時又引入另外一個問題:須要一個服務器承載頁面的實時請求、渲染和響應,這無疑會增大服務端開發和運維的成本。另外對於一些較爲靜態場景,好比博客、官網等,它們的內容相對來講比較肯定,變化不頻繁,每次經過服務端渲染出來的內容都是同樣的,無疑浪費了不少不必的服務器資源。這時,有沒有一種方案可讓這些頁面變得靜態呢?這時,靜態站點生成(SSG,也叫構建時預渲染)誕生了。服務器

SSG

SSG(Static Site Generation) 是指在應用編譯構建時預先渲染頁面,並生成靜態的 HTML。把生成的 HTML 靜態資源部署到服務器後,瀏覽器不只首次能請求到帶頁面內容的 HTML ,並且不須要服務器實時渲染和響應,大大節約了服務器運維成本和資源。

image.png

Next.js 默認爲每一個頁面開啓 SSG。對於頁面內容須要依賴靜態數據的場景,容許在每一個頁面中 export 一個 getStaticProps 異步函數,在這個函數中能夠把該頁面組件所須要的數據收集並返回。當 getStaticProps 函數執行完成後,頁面組件就能在 props 中拿到這些數據並執行靜態渲染。舉個在靜態路由中使用 SSG 的例子:

// pages/posts/first-post.js
function Post(props) {
	const { postData } = props;
  
  return <div>{postData.title}</div>
}

export async function getStaticProps() {
  // 模擬獲取靜態數據
	const postData = await getPostData();
  return {
  	props: { postData }
  }
}
複製代碼

對於動態路由的場景,Next.js 是如何作 SSG 的呢?Next.js 提供 getStaticPaths 異步函數,在這個方法中,會返回一個 paths 數組,這個數組包含了這個動態路由在構建時須要預渲染的頁面數據。舉個例子:

// pages/posts/[id].js
function Post(props) {
	const { postData } = props;
  
  return <div>{postData.title}</div>
}

export async function getStaticPaths() {
  // 返回該動態路由可能會渲染的頁面數據,好比 params.id
  const paths = [
    {
      params: { id: 'ssg-ssr' }
    },
    {
      params: { id: 'pre-rendering' }
    }
  ]
  return {
    paths,
    // 命中還沒有生成靜態頁面的路由直接返回 404 頁面
    fallback: false
  }
}

export async function getStaticProps({ params }) {
  // 使用 params.id 獲取對應的靜態數據
  const postData = await getPostData(params.id)
  return {
    props: {
      postData
    }
  }
}
複製代碼

當咱們執行 nextjs build 後,能夠看到打包結果包含 pre-rendering.htmlssg-ssr.html 兩個 HTML 頁面,也就是說在執行 SSG 時,會對 getStaticPaths 函數返回的 paths 數組進行循環,逐一預渲染頁面組件並生成 HTML。

├── server
|  ├── chunks
|  ├── pages
|  |  ├── api
|  |  ├── index.html
|  |  ├── index.js
|  |  ├── index.json
|  |  └── posts
|  |     ├── [id].js
|  |     ├── first-post.html
|  |     ├── first-post.js
|  |     ├── pre-rendering.html       # 預渲染生成 pre-rendering 頁面
|  |     ├── pre-rendering.json
|  |     ├── ssg-ssr.html             # 預渲染生成 ssg-ssr 頁面
|  |     └── ssg-ssr.json
複製代碼

SSG 雖然很好解決了白屏時間過長和 SEO 不友好的問題,可是它僅僅適合於頁面內容較爲靜態的場景,好比官網、博客等。面對頁面數據更新頻繁頁面數量不少的狀況,它彷佛顯得有點一籌莫展,畢竟在靜態構建時不能拿到最新的數據和沒法枚舉海量頁面。這時,就須要增量靜態再生成(Incremental Static Regeneration)方案了。

ISR

image.png

Next.js 推出的 ISR(Incremental Static Regeneration) 方案,容許在應用運行時再從新生成每一個頁面 HTML,而不須要從新構建整個應用。這樣即便有海量頁面,也能使用上 SSG 的特性。通常來講,使用 ISR 須要 getStaticPathsgetStaticProps 同時配合使用。舉個例子:

// 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,
    // 頁面請求的降級策略,這裏是指不降級,等待頁面生成後再返回,相似於 SSR
    fallback: 'blocking'
  }
}

export async function getStaticProps({ params }) {
  // 使用 params.id 獲取對應的靜態數據
  const postData = await getPostData(params.id)
  return {
    props: {
      postData
    },
    // 開啓 ISR,最多每10s從新生成一次頁面
    revalidate: 10,
  }
}
複製代碼

在應用編譯構建階段,會生成已經肯定的靜態頁面,和上面 SSG 執行流程一致。

getStaticProps 函數返回的對象中增長 revalidate 屬性,表示開啓 ISR。在上面的例子中,指定 revalidate = 10,表示最多10秒內從新生成一次靜態 HTML。當瀏覽器請求已在構建時渲染生成的頁面時,首先返回的是緩存的 HTML,10s 後頁面開始從新渲染,頁面成功生成後,更新緩存,瀏覽器再次請求頁面時就能拿到最新渲染的頁面內容了。

對於瀏覽器請求構建時未生成的頁面時,會立刻生成靜態 HTML。在這個過程當中,getStaticPaths 返回的 fallback 字段有如下的選項:

  • fallback: 'blocking':不降級,而且要求用戶請求一直等到新頁面靜態生成結束,靜態頁面生成結束後會緩存
  • fallback: true:降級,先返回降級頁面,當靜態頁面生成結束後,會返回一個 JSON 供降級頁面 CSR 使用,通過二次渲染後,完整頁面出來了

在上面的例子中,使用的是不降級方案(fallback: 'blocking'),實際上和 SSR 方案有類似之處,都是阻塞渲染,只不過多了緩存而已。

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.

也不是全部場景都適合使用 ISR。對於實時性要求較高的場景,好比新聞資訊類的網站,可能 SSR 纔是最好的選擇。

混合渲染模式

Next.js 不只支持 SSR、SSG、CSR、ISR,還支持渲染模式的混合使用。下面將介紹三種混合渲染模式。

SSR + CSR

上面已經說起過,SSR 彷佛已經解決了 CSR 帶來的問題,那是否是 CSR 徹底沒有用武之地呢?其實並非。使用 CSR 時,頁面切換無需刷新,無需從新請求整個 HTML 的內容。既然如此,能夠各取所長,各補其短,因而就有 SSR + CSR 的方案:

  • 首次加載頁面走 SSR:保證首屏加載速度的同時,而且知足 SEO 的訴求
  • 頁面切換走 CSR:Next.js 會發起一次網絡請求,執行 getServerSideProps 函數,拿到它返回的數據後,進行頁面渲染

兩者的有機結合,大大減小後端服務器的壓力和成本的同時,也能提升頁面切換的速度,進一步提高用戶的體驗。除了 Next.js,還有其餘的框架也使用 SSR + CSR 方案,好比 ice.js 等。

SSG + CSR

在上面已說起過,SSR 須要較高的服務器運維成本。對於某些靜態網站或者實時性要求較低的網站來講,是沒有必要使用 SSR 的。假如用 SSG 代替 SSR,使用 SSG + CSR 方案,是否是會更好:

  • 靜態內容走 SSG:對於頁面中較爲靜態的內容,好比導航欄、佈局等,能夠在編譯構建時預先渲染靜態 HTML
  • 動態內容走 CSR:通常會在 useEffect 中請求接口獲取動態數據,而後進行頁面從新渲染

雖然從體驗來講,動態內容須要頁面從新渲染後才能出現,體驗上沒有 SSR 好,可是避免 SSR 帶來的高額服務器成本的同時,也能保證首屏渲染時間不會太長,相比純 CSR 來講,仍是提高了用戶體驗。

SSG + SSR

在上面介紹的 ISR 方案時說起過,ISR 的實質是 SSG + SSR:

  • 靜態內容走 SSG:編譯構建時把相對靜態的頁面預先渲染生成 HTML,瀏覽器請求時直接返回靜態 HTML
  • 動態內容走 SSR:瀏覽器請求未預先渲染的頁面,在運行時經過 SSR 渲染生成頁面,而後返回到瀏覽器,並緩存靜態 HTML,下次命中緩存時直接返回

ISR 相比於 SSG + CSR 來講,動態內容能夠直接直出,進一步提高了首次訪問頁面時的體驗;相比於 SSR + CSR 來講,減小不必的靜態頁面渲染,節省了一部分後端服務器成本。

總結

本文首先介紹了 Next.js 提供的三種預渲染模式:SSR、SSG、ISR,並分別說明了它們的優缺點以及可能適用於哪些場景。後面介紹了 Next.js 目前支持的三種混合渲染模式,並和其餘的渲染模式進行對比。

總的來講,沒有十全十美的渲染方案,都須要根據實際場景進行權衡和取捨。

參考連接:

相關文章
相關標籤/搜索