串流 (Streaming)
在前一章節中,您已了解 Next.js 的不同渲染方法。我們也討論了緩慢的資料獲取如何影響應用程式的效能。現在讓我們看看當遇到緩慢的資料請求時,如何改善使用者體驗。
什麼是串流?
串流是一種資料傳輸技術,可讓您將路由拆分成較小的「區塊 (chunks)」,並在它們準備就緒時逐步從伺服器串流傳輸到客戶端。

透過串流,您可以防止緩慢的資料請求阻擋整個頁面。這讓使用者能在所有資料載入完成前,先看到並與頁面的部分內容互動。

串流與 React 的元件模型配合良好,因為每個元件都可視為一個「區塊」。
在 Next.js 中有兩種實現串流的方式:
- 在頁面層級,使用
loading.tsx
檔案(它會自動為您建立<Suspense>
)。 - 在元件層級,使用
<Suspense>
進行更細粒度的控制。
讓我們看看具體如何運作。
使用 loading.tsx
串流整個頁面
在 /app/dashboard
資料夾中,建立一個名為 loading.tsx
的新檔案:
重新整理 http://localhost:3000/dashboard,您現在應該會看到:

這裡發生了幾件事:
loading.tsx
是 Next.js 基於 React Suspense 的特殊檔案,它允許您建立替代 UI 在頁面內容載入時顯示。- 由於
<SideNav>
是靜態的,它會立即顯示。使用者可以在動態內容載入時與<SideNav>
互動。 - 使用者無需等待頁面完全載完即可導航離開(這稱為可中斷導航 interruptable navigation)。
恭喜!您已實現了串流。但我們還能進一步改善使用者體驗。讓我們顯示載入骨架 (loading skeleton) 而非「載入中...」文字。
新增載入骨架
載入骨架是 UI 的簡化版本。許多網站使用它們作為佔位符(或後備內容)來向使用者表示內容正在載入。您在 loading.tsx
中新增的任何 UI 都會作為靜態檔案的一部分嵌入,並首先發送。然後,其餘的動態內容會從伺服器串流到客戶端。
在您的 loading.tsx
檔案中,匯入一個名為 <DashboardSkeleton>
的新元件:
然後,重新整理 http://localhost:3000/dashboard,您現在應該會看到:

使用路由群組 (route groups) 修正載入骨架錯誤
目前,您的載入骨架會套用到發票頁面。
由於 loading.tsx
在檔案系統中的層級高於 /invoices/page.tsx
和 /customers/page.tsx
,它也會套用到這些頁面。
我們可以使用路由群組 (Route Groups) 來改變這一點。在 dashboard 資料夾中建立一個名為 /(overview)
的新資料夾。然後,將您的 loading.tsx
和 page.tsx
檔案移動到此資料夾中:

現在,loading.tsx
檔案僅套用到您的儀表板概覽頁面。
路由群組允許您將檔案組織成邏輯群組,而不影響 URL 路徑結構。當您使用括號 ()
建立新資料夾時,該名稱不會包含在 URL 路徑中。因此 /dashboard/(overview)/page.tsx
會變成 /dashboard
。
在這裡,您使用路由群組確保 loading.tsx
僅套用到儀表板概覽頁面。但您也可以使用路由群組將應用程式分成不同部分(例如 (marketing)
路由和 (shop)
路由),或為大型應用程式按團隊劃分。
串流單個元件
到目前為止,您已串流整個頁面。但您也可以更細粒度地使用 React Suspense 串流特定元件。
Suspense 允許您延遲渲染部分應用程式,直到滿足某些條件(例如資料已載入)。您可以將動態元件包裝在 Suspense 中,然後傳遞一個後備元件 (fallback component) 在動態元件載入時顯示。
如果您還記得那個緩慢的資料請求 fetchRevenue()
,正是這個請求拖慢了整個頁面。與其阻擋整個頁面,您可以使用 Suspense 僅串流此元件,並立即顯示頁面其餘部分的 UI。
為此,您需要將資料獲取移動到元件中,讓我們更新程式碼看看會是什麼樣子:
從 /dashboard/(overview)/page.tsx
中刪除所有 fetchRevenue()
的實例及其資料:
然後,從 React 匯入 <Suspense>
,並用它包裝 <RevenueChart />
。您可以傳遞一個名為 <RevenueChartSkeleton>
的後備元件。
最後,更新 <RevenueChart>
元件以自行獲取資料,並移除傳遞給它的 prop:
現在重新整理頁面,您應該會幾乎立即看到儀表板資訊,同時為 <RevenueChart>
顯示後備骨架:

練習:串流 <LatestInvoices>
現在輪到您了!練習您剛剛學到的內容,串流 <LatestInvoices>
元件。
將 fetchLatestInvoices()
從頁面移動到 <LatestInvoices>
元件。用 <Suspense>
邊界包裝該元件,並使用名為 <LatestInvoicesSkeleton>
的後備元件。
完成後,展開切換以查看解決方案程式碼:
群組元件
很好!您快完成了,現在您需要用 Suspense 包裝 <Card>
元件。您可以為每個單獨的卡片獲取資料,但這可能會導致卡片載入時出現「彈出 (popping)」效果,這對使用者來說視覺上會很突兀。
那麼,如何解決這個問題?
為了創造更「交錯 (staggered)」的效果,您可以使用包裝元件 (wrapper component) 來群組卡片。這意味著靜態的 <SideNav/>
會先顯示,接著是卡片等。
在您的 page.tsx
檔案中:
- 刪除您的
<Card>
元件。 - 刪除
fetchCardData()
函數。 - 匯入一個新的包裝元件
<CardWrapper />
。 - 匯入一個新的骨架元件
<CardsSkeleton />
。 - 用 Suspense 包裝
<CardWrapper />
。
然後,進入檔案 /app/ui/dashboard/cards.tsx
,匯入 fetchCardData()
函數,並在 <CardWrapper/>
元件內部呼叫它。確保取消註解此元件中的任何必要程式碼。
重新整理頁面,您應該會看到所有卡片同時載入。當您希望多個元件同時載入時,可以使用此模式。
決定 Suspense 邊界的位置
您放置 Suspense 邊界的位置將取決於幾個因素:
- 您希望使用者在頁面串流時的體驗。
- 您希望優先顯示的內容。
- 元件是否依賴資料獲取。
看看您的儀表板頁面,您會採取不同的做法嗎?
別擔心,這沒有標準答案。
- 您可以像我們使用
loading.tsx
那樣串流整個頁面...但如果其中一個元件有緩慢的資料獲取,可能會導致更長的載入時間。 - 您可以單獨串流每個元件...但這可能會導致 UI 在準備就緒時「彈出」到螢幕上。
- 您也可以透過串流頁面區段來創造「交錯」效果。但您需要建立包裝元件。
您放置 Suspense 邊界的位置會根據應用程式而有所不同。一般來說,將資料獲取下移到需要它的元件,然後用 Suspense 包裝這些元件是個好做法。但如果您的應用程式需要,串流區段或整個頁面也沒有問題。
不要害怕嘗試 Suspense 並找出最適合的方式,它是一個強大的 API,可以幫助您創造更愉悅的使用者體驗。
展望未來
串流和伺服器元件 (Server Components) 為我們提供了處理資料獲取和載入狀態的新方法,最終目標是改善終端使用者體驗。
在下一章中,您將了解「部分預渲染 (Partial Prerendering)」,這是一個基於串流設計的新 Next.js 渲染模型。