使用 Next.js 的單頁應用程式
Next.js 完全支援建置單頁應用程式 (SPA)。
這包括透過預先抓取進行快速路由轉換、用戶端資料抓取、使用瀏覽器 API、與第三方用戶端函式庫整合、建立靜態路由等等。
如果您有現有的 SPA,您可以遷移到 Next.js,而無需對程式碼進行大幅變更。然後,Next.js 允許您根據需要逐步新增伺服器功能。
什麼是單頁應用程式?
SPA 的定義各不相同。我們將「嚴格 SPA」定義為
- 用戶端渲染 (CSR):應用程式由一個 HTML 檔案(例如
index.html
)提供。每個路由、頁面轉換和資料抓取都由瀏覽器中的 JavaScript 處理。 - 沒有完整頁面重新載入:用戶端 JavaScript 不是為每個路由請求新文件,而是操作目前頁面的 DOM 並根據需要抓取資料。
嚴格的 SPA 通常需要載入大量的 JavaScript 才能使頁面具有互動性。此外,用戶端資料瀑布可能難以管理。使用 Next.js 建置 SPA 可以解決這些問題。
為什麼要為 SPA 使用 Next.js?
Next.js 可以自動程式碼分割您的 JavaScript 捆綁包,並為不同的路由產生多個 HTML 進入點。這避免在用戶端載入不必要的 JavaScript 程式碼,從而減少捆綁包大小並加快頁面載入速度。
next/link
元件會自動預先抓取路由,讓您擁有嚴格 SPA 的快速頁面轉換,但具有將應用程式路由狀態持久保存到 URL 以進行連結和分享的優勢。
Next.js 可以從靜態網站甚至嚴格的 SPA 開始,其中所有內容都在用戶端渲染。如果您的專案成長,Next.js 允許您根據需要逐步新增更多伺服器功能(例如React 伺服器元件、伺服器動作等等)。
範例
讓我們探索用於建置 SPA 的常見模式,以及 Next.js 如何解決這些模式。
在 Context Provider 中使用 React 的 use
我們建議在父元件(或版面配置)中抓取資料、傳回 Promise,然後在用戶端元件中使用 React 的 use
Hook來解包值。
Next.js 可以在伺服器上提早開始資料抓取。在此範例中,它是根版面配置,也就是您應用程式的進入點。伺服器可以立即開始將回應串流到用戶端。
透過將您的資料抓取「提升」到根版面配置,Next.js 會在伺服器上提早開始指定的請求,早於您應用程式中的任何其他元件。這消除了用戶端瀑布,並防止在用戶端和伺服器之間進行多次往返。它還可以顯著提高效能,因為您的伺服器更接近(理想情況下是共置)您的資料庫所在的位置。
例如,更新您的根版面配置以呼叫 Promise,但不要等待它。
import { UserProvider } from './user-provider'
import { getUser } from './user' // some server-side function
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
let userPromise = getUser() // do NOT await
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
雖然您可以延遲並將單個 Promise 作為 prop 傳遞到用戶端元件,但我們通常看到此模式與 React Context Provider 配對使用。這可以使用自訂 React Hook 更輕鬆地從用戶端元件進行存取。
您可以將 Promise 轉發到 React Context Provider
'use client';
import { createContext, useContext, ReactNode } from 'react';
type User = any;
type UserContextType = {
userPromise: Promise<User | null>;
};
const UserContext = createContext<UserContextType | null>(null);
export function useUser(): UserContextType {
let context = useContext(UserContext);
if (context === null) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
export function UserProvider({
children,
userPromise
}: {
children: ReactNode;
userPromise: Promise<User | null>;
}) {
return (
<UserContext.Provider value={{ userPromise }}>
{children}
</UserContext.Provider>
);
}
最後,您可以在任何用戶端元件中呼叫 useUser()
自訂 Hook 並解包 Promise
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise)
return '...'
}
使用 Promise 的元件(例如,上面的 Profile
)將被暫停。這啟用了部分水合作用。您可以在 JavaScript 完成載入之前看到串流和預先渲染的 HTML。
搭配 SWR 的 SPA
SWR是一個熱門的 React 函式庫,用於資料抓取。
使用 SWR 2.3.0(和 React 19+),您可以逐步採用伺服器功能,同時使用現有的基於 SWR 的用戶端資料抓取程式碼。這是上述 use()
模式的抽象。這表示您可以在用戶端和伺服器端之間移動資料抓取,或同時使用兩者
- 僅限用戶端:
useSWR(key, fetcher)
- 僅限伺服器:
useSWR(key)
+ RSC 提供的資料 - 混合:
useSWR(key, fetcher)
+ RSC 提供的資料
例如,使用 <SWRConfig>
和 fallback
包裝您的應用程式
import { SWRConfig } from 'swr'
import { getUser } from './user' // some server-side function
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<SWRConfig
value={{
fallback: {
// We do NOT await getUser() here
// Only components that read this data will suspend
'/api/user': getUser(),
},
}}
>
{children}
</SWRConfig>
)
}
由於這是伺服器元件,getUser()
可以安全地讀取 Cookie、標頭或與您的資料庫通訊。不需要單獨的 API 路由。<SWRConfig>
下方的用戶端元件可以使用相同的 key 呼叫 useSWR()
來檢索使用者資料。具有 useSWR
的元件程式碼不需要對您現有的用戶端抓取解決方案進行任何變更。
'use client'
import useSWR from 'swr'
export function Profile() {
const fetcher = (url) => fetch(url).then((res) => res.json())
// The same SWR pattern you already know
const { data, error } = useSWR('/api/user', fetcher)
return '...'
}
fallback
資料可以預先渲染並包含在初始 HTML 回應中,然後立即在使用 useSWR
的子元件中讀取。SWR 的輪詢、重新驗證和快取仍然僅在用戶端執行,因此它保留了您依賴於 SPA 的所有互動性。
由於初始 fallback
資料由 Next.js 自動處理,您現在可以刪除先前檢查 data
是否為 undefined
所需的任何條件邏輯。當資料正在載入時,最接近的 <Suspense>
邊界將被暫停。
SWR | RSC | RSC + SWR | |
---|---|---|---|
SSR 資料 | |||
串流處理時進行 SSR | |||
重複資料刪除請求 | |||
用戶端功能 |
搭配 React Query 的 SPA
您可以在用戶端和伺服器上將 React Query 與 Next.js 一起使用。這使您可以建置嚴格的 SPA,並利用 Next.js 中與 React Query 配對的伺服器功能。
在React Query 文件中瞭解更多資訊。
僅在瀏覽器中渲染元件
用戶端元件在 next build
期間會預先渲染。如果您想要停用用戶端元件的預先渲染,並且僅在瀏覽器環境中載入它,您可以使用 next/dynamic
import dynamic from 'next/dynamic'
const ClientOnlyComponent = dynamic(() => import('./component'), {
ssr: false,
})
這對於依賴瀏覽器 API(如 window
或 document
)的第三方函式庫非常有用。您也可以新增 useEffect
,檢查這些 API 是否存在,如果不存在,則傳回 null
或將被預先渲染的載入狀態。
用戶端上的淺層路由
如果您是從嚴格的 SPA(如Create React App 或 Vite)遷移,您可能具有現有的程式碼,該程式碼會進行淺層路由以更新 URL 狀態。這對於在應用程式中的視圖之間進行手動轉換(不使用預設的 Next.js 檔案系統路由)非常有用。
Next.js 允許您使用原生 window.history.pushState
和 window.history.replaceState
方法來更新瀏覽器的歷史記錄堆疊,而無需重新載入頁面。
pushState
和 replaceState
呼叫整合到 Next.js 路由器中,讓您可以與 usePathname
和 useSearchParams
同步。
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
瞭解更多關於 路由和導航 在 Next.js 中如何運作。
在用戶端元件中使用伺服器動作
您可以逐步採用伺服器動作,同時仍然使用用戶端元件。這讓您可以移除樣板程式碼來呼叫 API 路由,而是使用 React 功能(如 useActionState
)來處理載入和錯誤狀態。
例如,建立您的第一個伺服器動作
'use server'
export async function create() {}
您可以從用戶端匯入和使用伺服器動作,類似於呼叫 JavaScript 函式。您不需要手動建立 API 端點
'use client'
import { create } from './actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
瞭解更多關於使用伺服器動作變更資料。
靜態匯出 (選用)
Next.js 也支援產生完全靜態網站。這比嚴格的 SPA 有一些優勢
- 自動程式碼分割:Next.js 不是運送單個
index.html
,而是為每個路由產生一個 HTML 檔案,因此您的訪客可以更快地取得內容,而無需等待用戶端 JavaScript 捆綁包。 - 改進的使用者體驗: 您取得的是每個路由的完整渲染頁面,而不是所有路由的最小骨架。當使用者在用戶端導航時,轉換仍然是即時且類似 SPA 的。
若要啟用靜態匯出,請更新您的設定
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
}
export default nextConfig
在執行 next build
後,Next.js 將建立一個 out
資料夾,其中包含您應用程式的 HTML/CSS/JS 資源。
注意: 靜態匯出不支援 Next.js 伺服器功能。瞭解更多。
將現有專案遷移到 Next.js
您可以按照我們的指南逐步遷移到 Next.js
如果您已經在使用 Pages Router 的 SPA,您可以瞭解如何逐步採用 App Router。
這有幫助嗎?