翻譯筆記 - Next.js: 網頁渲染的終極 Cheat Sheet

翻譯自 https://medium.com/@gdumais/next-js-the-ultimate-cheat-sheet-to-page-rendering-46e4f82ce1ee

Next.js 有四種渲染策略

  • Static-Site Generation (SSG)
  • Server-Side Rendering (SSR)
  • Incremental Static Regeneration (ISR)
  • Client-Side Rendering (CSR)

此外,這 Cheat Sheet 清楚地展示每種策略會如何影響 Core Web Vitals 指標:TTFB、FP、FCP、LCP、TBT 和 TTI。

Next.js: The Ultimate Cheat Sheet to Page Rendering

從 Cheat Sheet 的左側可以發現從 Build 到渲染給 Client 端,會經過六個步驟。右側的話可以知道不同步驟在 Server 或 Client 的處理流程中發生的位置。每個策略列可以看到不同的步驟中的執行內容。還能知道每個步驟中看到稱成的內容(HTML - React/CSS/JavaScript/Data)。

在此篇文章中,作者有提供一個 Live Demo 的網頁。(但有些我看不太出來差異,要搭配開發者工具去比較比較清楚)

Static-Site Generation (SSG)

此策略是 Next.js 的預設策略。Next.js 推薦使用此策略達到最好的效能,產生可讓 CDN 使用的靜態內容,而達到更快的 Time to First Byte(TTFB)

Step 1

Next.js 在 HTML 中生成一個 Single Page App 以及伺服器上的 .css.js。此步驟稱為 pre-rendering,只會在下一個 build 指令執行時,執行一次。在此步驟中,所有網頁的程式(HTML) 都會生成,包括 React、CSS、JavaScript 以及所需的資料(如果網頁使用任何資料取得方法,例如 getStaticProps()getStaticPaths()

Step 2

Client 從 CDN 請求 Single Page App

Step 3

Client 從 CDN 下載 Single Page App

Step 4

Client 解析並渲染 Single Page App 到瀏覽器中。 這是觸發三個 Core Web Vitals 指標的地方(First Paint、First Contentful Paint 和 Largest Contentful Paint)。因為網頁的 HTML 已經在伺服器渲染,所以 Client 瀏覽器只需要按原樣載入和渲染。而且由於網頁的編碼方式允許瀏覽器使用最少的 JavaScript 呈現,因此可以將 render-blocking 的影響減少到最低限度,從而得到很好的效能。

Step 5

React(JavaScript)程式碼會以 (re)Hydrate 網頁。即使網頁已經 pre-built,也需要這個額外的步驟來讓 React 將 JavaScript 添加或啟用到 Virtual DOM 使其網頁有互動性。因為此時執行 JavaScript,所以所有的渲染阻塞時間都受此額外步驟影響。此外,由於瀏覽器必須等待 hydration 過程完成,因此互動時間也會受到影響

TypeScript 範例

// Next.js libraries
import Head from 'next/head';

// Custom Components
import BackToHome from 'components/BackToHome';

// Page component
export default function StaticSideGeneration({ jsonData }) {
    return (
        <>
            <Head>
                <title>Static-Site Generation (SSG) • Guy Dumais</title>
                <meta
                    name='description'
                    content='Example page using Static-Site Generation (SSG) with Next.js 11 and React 17'
                />
                <meta
                    name='viewport'
                    content='initial-scale=1.0, width=device-width'
                />
            </Head>
            <BackToHome />
            <h1>Static-Site Generation (SSG)</h1>
            <p>
                Data fetched at build-time on the server-side before sending to
                the client.
            </p>
            <ul>
                {jsonData.data.map((e) => (
                    <li key={e.id}>{e.email}</li>
                ))}
            </ul>
        </>
    );
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
    const res = await fetch('https://reqres.in/api/users?page=2');
    const jsonData = await res.json();

    return {
        props: {
            jsonData, // will be passed to the page component as props
        },
    };
}

Server-Side Rendering (SSR)

Server-Side Rendering 是 Next.js 提供的第二種產生網頁的策略。Next.js 建議盡可能地避免使用此策略,因為靜態內容只針對每個請求 built(pre-rendered)和 distributed。所以建制過程需要額外的時間,Time to First Byte (TTFB) 指標會增加從而導致不好的結果。

Step 1

在此步驟 Next.js 不會產生(pre-render)任何網頁

Step 2

Client 向伺服器請求 Single Page App,Next.js 在 HTML 檔案中產生(pre-render)一個 Single Page App 和其所需的 .css.js。在此步驟中,所有的網頁程式碼(HTML)都會產生,包括 React、CSS、JavaScript 以及所需的資料(如果網頁使用 getServerSideProps() 來獲取資料)

Step 3

Client 從伺服器下載 Single Page App

Step 4

和 SSG 第四步驟一樣

Step 5

和 SSG 第五步驟一樣

TypeScript 範例

// Next.js libraries
import Head from 'next/head';

// Custom Components
import BackToHome from 'components/BackToHome';

// Page component
export default function ServerSideRendering({ jsonData }) {
    return (
        <>
            <Head>
                <title>Server-Side Rendering (SSR) • Guy Dumais</title>
                <meta
                    name='description'
                    content='Example page using Server-Side Rendering (SSR) with Next.js 11 and React 17'
                />
                <meta
                    name='viewport'
                    content='initial-scale=1.0, width=device-width'
                />
            </Head>
            <BackToHome />
            <h1>Server-Side Rendering (SSR)</h1>
            <p>
                Data fetched on the server-side at <b>each</b> request before
                sending to the client.
            </p>
            <ul>
                {jsonData.data.map((e) => (
                    <li key={e.id}>{e.email}</li>
                ))}
            </ul>
        </>
    );
}

export async function getServerSideProps() {
    const res = await fetch('https://reqres.in/api/users?page=2');
    const jsonData = await res.json();

    return {
        props: {
            jsonData, // will be passed to the page component as props
        },
    };
}

Incremental Static Regeneration (ISR)

Next.js 的第三個策略,此策略除了可以在網頁更新時 rebuilt 之外,其餘的都跟 Static Site Generation 一樣。

Step 1

與 SSG 第一步驟一樣

Step 2

Client 從 CDN 請求 Single Page App。此外,如果網頁使用取得資料方法 getStaticProps() 來和 revalidate 選項結合使用。如果取得回傳的資料並更新,則會重新產生網頁。Next.js 建議使用此方法在超過 1,000 個網頁的大型網站。因爲 pre-render 每個網頁都需要時間,所以使用此方法只會在第一次請求或是網頁內容更新時 pre-render。

Step 3

與 SSG 第三步驟一樣

Step 4

與 SSG 第四步驟一樣

Step 5

與 SSG 第五步驟一樣

TypeScript 範例

// Next.js libraries
import Head from 'next/head';

// Custom Components
import BackToHome from 'components/BackToHome';

// Page component
export default function IncrementalStaticGeneration({ jsonData }) {
    return (
        <>
            <Head>
                <title>
                    Incremental Static Regeneration (ISR) • Guy Dumais
                </title>
                <meta
                    name='description'
                    content='Example page using Incremental Static Regeneration (ISR) with Next.js 11 and React 17'
                />
                <meta
                    name='viewport'
                    content='initial-scale=1.0, width=device-width'
                />
            </Head>
            <BackToHome />
            <h1>Incremental Static Regeneration (ISR)</h1>
            <p>
                Data fetched at build-time on the server-side and rebuilt when
                data updated.
            </p>
            <ul>
                {jsonData.data.map((e) => (
                    <li key={e.id}>{e.email}</li>
                ))}
            </ul>
        </>
    );
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
    const res = await fetch('https://reqres.in/api/users?page=2');
    const jsonData = await res.json();

    return {
        props: {
            jsonData, // will be passed to the page component as props
        },

        // Next.js will attempt to re-generate the page:
        // - When a request comes in
        // - At most once every second
        revalidate: 100, // In seconds
    };
}

Client-Side Rendering (CSR)

Next.js 的第四個策略,除了部分內容可以在 Client 建立之外,其餘的都跟 Static Site Generation 一樣。

Step 1

與 SSG 第一步驟一樣,不同的地方在於內容(資料) 既不 pre-render 也不包含在 static bunch。這將產生更小的檔案大小,因此可能縮短下載時間。

Step 2

與 SSG 第二步驟一樣,但是不包含內容(資料)

Step 3

與 SSG 第三步驟一樣,但是不包含內容(資料)

Step 4

與 SSG 第四步驟一樣

Step 5

與 SSG 第五步驟一樣,但是不包含內容(資料)

Step 6

Client 獲得內容(data)並且 React 更新 UI。此方法很有用,例如,當想要顯示具有靜態內容的骨架網頁,然後將資料逐步注入到網頁,需要長時間等待資料回傳。

TypeScript 範例

// React
import { useEffect, useState } from 'react'

// Next.js
import Head from 'next/head'
import Link from 'next/link'

// Custom Components
import BackToHome from 'components/BackToHome'

// Page component
export default function ClientSideRendered() {

    const [state, setState] = useState([] as any)

    const getData = async () => {
        const res = await fetch('https://reqres.in/api/users?page=2')
        const jsonData = await res.json()
        setState(jsonData)
    }

    useEffect(() => {
        getData()
    }, [])

    return (
        <>
            <Head>
                <title>Client-Side Rendering (CSR) • Guy Dumais</title>
                <meta name="description" content="Example page using Client-Side Rendering (CSR) with Next.js 11 and React 17" />
                <meta name="viewport" content="initial-scale=1.0, width=device-width" />
            </Head>
            <BackToHome />
            <h1>Client-Side Rendering (CSR)</h1>
            <p>Data fetched on the client-side only.</p>
            <ul>
                {
                    state.data?.map((e) => (
                        <li key={e.id}>{e.email}</li>
                    ))
                }
            </ul>
        </>
    )
}

Did you find this article valuable?

Support 攻城獅 by becoming a sponsor. Any amount is appreciated!