iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

30 天淺入淺出 Next.js 13系列 第 9

Day9 - Server Component - Next13 的渲染策略

  • 分享至 

  • xImage
  •  

以下以 RSC 代稱 React Server Component

Next13 的渲染策略與 RSC 有著密切的關係,今天會探討 RSC, fetch 如何影響 Next13 的渲染策略,以及我們可以如何優化。

看完這篇文章預期你會有以下收穫:

  • 知道 RSC 是什麼
  • RSC 如何被渲染,當使用者造訪頁面時
  • RSC 的渲染策略 - Static, dynamic, streaming
  • 這些 RSC 的渲染策略會對route渲染有什麼影響

RSC 是什麼

RSC 有兩大特點:

  1. 運行在 server
  2. 可以被 cache (符合條件的話)

RSC 就是運行在server 的 component,擁有運用 server 的能力,可以直接 fetch data, 與資料庫溝通等等。

另外一個非常重要的特性就是RSC 的結果可以被 cache,而且這是 server-level 的 cache,所以另一個 user 在也能在後續使用到這份 cache 的結果,不會侷限於某一個 user 才能使用。

RSC 還有很多優點,但這篇主要探討 Next13 的渲染策略,優點就先列在下方,可以先跳過這段,有興趣再看就好。

  • Data Fetching
    可以在 server fetch data、與資料庫溝通或其他 server 做得到的事。

  • 安全性
    隱藏敏感資訊(ex: token)及商業邏輯。
    後面會講到 RSC 只會傳送結果到 client,中間的過程只會運行在 server。

  • 快取cache
    server-level 的快取。
    慢~就給第一個人慢就好了,後續的人可以爽爽訪問。

  • Bundle Sizes
    過去 CSR 最不能解決的問題就是載入過多的 Javascript。
    特別是某些特別肥大的第三方套件無法做 tree shaking 或 splitting 時,這個狀況幾乎解決不了。
    但 RSC 在 server 上運行完後只會返回 UI 的渲染結果,大大減少了過往 CSR 的 javascript bundle 大小。

  • 畫面首次渲染速度
    直接傳送 html 到 client,讓使用者先看到畫面。
    相比於 CSR 需要傳送超肥 javascript bundle,還要在 client 組 html。使用 pre-rendering 的方式,畫面首次渲染的速度會比 CSR 快上許多。

  • SEO
    網頁爬蟲在爬到有做 pre-rendering 的網頁時可以看到組好的 html,這有助於 SEO。

  • Streaming
    RSC 與 Suspense 搭配,讓頁面可以分區載入,大幅提升使用者體驗。

RSC 如何被渲染?

首先,我們要理解 RSC 的渲染單位 - chunk。

預設同一個路由底下所有 RSC 是一個 chunk。同一個 chunk 裡的結果會一起被送到 client 端,如果當中有 async RSC 需要等待,那整個 chunk 會等所有 RSC 都渲染完成,才會將結果傳送到 client 端。

底下以一個 about 的頁面為例,AboutPage 是一個 RSC,裡面包含了兩個 RSC 組件。在預設的情況下這個 router 的頁面會等到所有 RSC 渲染完成,才將結果傳到 client。

// app/about/page.tsx
// 整個 Router 是一個 chunk,會等到 AboutPage, MyFamily, MyDog 這三個 component 都渲染完成才會一起傳到 client 端顯示

import MyFamily from "./MyFamily"; 
import MyDog from "./MyDog; 

export default async function AboutPage() {
    return (
        <>
            <h1>關於小熊</h1>
            <MyFamily />
            <MyDog />
        </>
    )
}

使用 Suspense 切分 chunk

上面這種渲染方式與 Next12 的 page router 有著驚人的相似,需要等待所有非同步的結果都取得後,才能將這次渲染的畫面一次傳到 client。

我們可以利用 Suspense 包住 RSC,這樣被 <Suspense></Suspense> 組件包起來的部分就會成為一個新的 chunk。

以剛才同樣的範例,為 <MyDog /> 組件加上 <Suspense></Suspense>,把 <MyDog /> 從原本的 router chunk 切分出來,成為一個新的 chunk。

Suspense 是 React 提供的 Component,只要包住 async component,傳入 fallback UI,就可以在 async component 渲染完成前顯示 fallback UI。

// app/about/page.tsx

import { Suspense } from "react"
import MyFamily from "./MyFamily"; 
import MyDog from "./MyDog; 

export default async function AboutPage() {
    return (
        <>
            <h1>關於小熊</h1>
            <MyFamily />
            <Suspense fallback={<div>loading...</div>}>
                <MyDog />
            </Suspense>
        </>
    )
}

簡單來說同一個 chunk 會被當成一個渲染單位,當同一個 chunk 裡有一個 RSC 的 cache 資料過期了,那整個 chunk 都要重新渲染。

更詳細的渲染過程可以看這篇 Next 的官方講解文章

RSC 的渲染策略

RSC 有三種渲染策略:

  1. Static Rendering 靜態渲染
  2. Dynamic Rendering 動態渲染
  3. Streaming 流式渲染(gpt翻的)

RSC 使用的渲染策略連帶也會影響到 Router 的渲染策略。

fetch

這裡要先講到 fetch 這個 Next 內建的 function,它除了繼承了 Web API - fetch 的所有功能外,它還與 cache 集成,讓我們可以設定 cache 和 revalidation data。

透過 data 被 cache 的狀態,也會影響到 RSC 的渲染方式。

fetch 預設是 cache: 'force-cache',如果參數一樣就會直接返回 cache 裡的結果。

// 預設就是 force-cache
fetch('https://jsonplaceholder.typicode.com/users', { cache: 'force-cache' })

// 設定 revalidation data 
// 底下的 data 如果參數一樣,超過 60 秒會過期
fetch('https://...', { next: { revalidate: 60 } })

Static Rendering

比較不常變動的資料,可以在 build time 就先把 RSC 的結果 cache 起來。

或是設定 data revalidation,觸發條件才會重新 fetch 新資料,否則可以直接使用 cache 的結果。

// 啥都不加就是 'force-cache',RSC Result 會被 cache
fetch('https://jsonplaceholder.typicode.com/users')

// 加上驗證條件,超過時間才會重新建立 RSC Result
fetch('https://...', { next: { revalidate: 60 } })

Static Rendering 很適合用在部落格文章或不常更動的資料。

Dynamic Rendering

需要時常更新的資料,或是需要透過 URL search params, cookie 才能取得的資料。

如果 RSC 內使用 'next/headers' 相關的方法,或是 fetch 設定 cache: 'no-store',這都自動將 RSC 由 Static Rendering 轉換為 Dynamic Rendering。

  • 使用到 header 資料或是 URL search params
import { cookies } from 'next/headers'
 
export default function Page() {
  const cookieStore = cookies(); // 偵測到使用 header 相關的方法,自動轉換成 dynamic rendering
  const theme = cookieStore.get('theme')
  return '...'
}
  • fetch 設定 cache: 'no-store'
import { cookies } from 'next/headers'
 
export default async function Page() {
  const result = await fetch('https://jsonplaceholder.typicode.com/users', { cache: 'no-store' }); // 不使用 cache,每次都會取得最新料
  return '...'
}

Streaming

Streaming 其實就是上面提到的,利用 Suspense 將頁面切分成不同的 chunk,這樣可以讓先渲染好的 chunk 先傳到客戶端渲染,增加使用者的體驗。

另外實作 Streaming 時可以使用兩種 pattern 實作:

  1. Sequential
  2. Parallel

這裡就不展開,好奇的可以看從 Next.js 13 認識 React Server Components,裡面有這兩種 pattern 的實作方式

RSC 如何影響 Router 層級的渲染策略

如果 RSC 都是使用 static rendering,完全沒有使用到 dynamic rendering,那這個頁面就是 SSG(Static Site Generation) 或 ISR(Incremental Site Rendering),端看 fetch 有沒有設定 revalidation data,有的話就是 ISR,沒有的話就是 SSG。

接下來 RSC dynamic rendering 就需要稍微討論一下。

定義上只要有任何一個 RSC 是 dynamic rendering,那這整個 router 都會算成是 dynamic rendering,但昨天也有提到,大部分的網頁都會同時包含 dynamic 與 static 的部分。

In most websites, routes are not fully static or fully dynamic - it's a spectrum. For example, you can have e-commerce page that uses cached product data that's revalidated at an interval, but also has uncached, personalized customer data.

而 RSC 也能輕鬆實踐這個理念,我們不必糾結 router 要整頁 Static 還是整頁 Dynamic。這兩個可以混合著使用,在 dynamic rendering router 中使用 static RSC,再搭配 Streaming 的作法,讓靜態的內容先給使用者看到,再逐步顯示 dynamic 的部分,整個使用者體驗大加分!

參考文章


上一篇
Day8 - 使用 Next13 建立 SSR, SSG, ISR 頁面 (上)
下一篇
Day10 - Client Component
系列文
30 天淺入淺出 Next.js 1321
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言