應用程式路由器漸進式採用指南
本指南將協助您
升級
Node.js 版本
最低 Node.js 版本現在是 v18.17。更多資訊請參閱 Node.js 文件。
Next.js 版本
要更新到 Next.js 版本 13,請使用您慣用的套件管理器執行以下指令
npm install next@latest react@latest react-dom@latest
ESLint 版本
注意事項:您可能需要在 VS Code 中重新啟動 ESLint 伺服器才能使 ESLint 變更生效。開啟指令面板(Mac 上為
cmd+shift+p
;Windows 上為ctrl+shift+p
),然後搜尋ESLint: 重新啟動 ESLint 伺服器
。
後續步驟
更新後,請參閱以下章節了解後續步驟
- 升級新功能:協助您升級至新功能的指南,例如改良的圖片和連結元件。
- 從
pages
目錄遷移到app
目錄:逐步指南,協助您逐步從pages
目錄遷移到app
目錄。
升級新功能
Next.js 13 推出了新的 應用程式路由器 (App Router),其中包含新的功能和慣例。新的路由器可在 app
目錄中使用,並與 pages
目錄共存。
升級到 Next.js 13 並**不**需要使用新的 應用程式路由器 (App Router)。您可以繼續使用 pages
目錄,並使用兩個目錄皆適用的新功能,例如更新的 圖片元件、連結元件、Script 元件 和 字體最佳化。
<Image/>
元件 next-image-to-legacy-image
程式碼修改器 :安全且自動地將 next/image
導入重新命名為 next/legacy/image
。現有的元件將維持相同的行為。
next-image-to-legacy-image
程式碼修改器next-image-experimental
程式碼修改器:危險地新增內嵌樣式並移除未使用的屬性。這會將現有元件的行為變更為符合新的預設值。要使用此程式碼修改器,您需要先執行 next-image-to-legacy-image
程式碼修改器。<Link>
元件 <Link>
元件 ) 不再需要手動添加 <a>
標籤作為子元素。此行為在 12.2 版 中作為實驗性選項加入,現在則成為預設行為。在 Next.js 13 中,<Link>
永遠會渲染 <a>
,並且允許您將屬性 (props) 傳遞到底層標籤。
<Link>
元件例如:
import Link from 'next/link'
// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
About
</Link>
要將您的連結升級到 Next.js 13,您可以使用 new-link
codemod。
<Script>
元件
next/script
的行為已更新,以支援 pages
和 app
目錄,但需要進行一些更改以確保順利遷移。
- 將您先前包含在
_document.js
中的任何beforeInteractive
腳本移至根佈局文件 (app/layout.tsx
)。 - 實驗性的
worker
策略目前在app
目錄中無法運作,使用此策略的腳本必須移除或修改為使用不同的策略(例如lazyOnload
)。 onLoad
、onReady
和onError
處理程序在伺服器元件中無法運作,因此請確保將它們移至客戶端元件或完全移除它們。
字型最佳化
先前,Next.js 透過內嵌字型 CSS 來協助您最佳化字型。13 版引入了新的 next/font
模組,讓您能夠自訂字型載入體驗,同時確保優異的效能和隱私性。next/font
在 pages
和 app
目錄中都受到支援。
雖然內嵌 CSS 仍然可以在 pages
目錄中運作,但在 app
目錄中則無法運作。您應該改用 next/font
。
請參閱字型最佳化頁面,以了解如何使用 next/font
。
從 pages
遷移到 app
🎥 觀看影片:瞭解如何逐步採用 App Router → YouTube (16 分鐘) 。
遷移到 App Router 可能會是您第一次使用 Next.js 建立在其之上的 React 功能,例如伺服器元件、Suspense 等等。當與新的 Next.js 功能(例如特殊檔案和佈局)結合使用時,遷移意味著需要學習新的概念、心智模型和行為變化。
我們建議您將遷移分解成更小的步驟,以降低這些更新的綜合複雜性。app
目錄特意設計成可與 pages
目錄同時運作,以便逐頁逐步遷移。
app
目錄支援巢狀路由 *和* 佈局。深入瞭解。
- 使用巢狀資料夾來定義路由,並使用特殊的
page.js
檔案讓路由區段可公開存取。深入瞭解。
- 特殊檔案命名慣例用於為每個路由區段建立 UI。最常見的特殊檔案是
page.js
和 layout.js
。
- 使用
page.js
定義特定路由的 UI。
- 使用
layout.js
定義跨多個路由共用的 UI。
- 特殊檔案可以使用
.js
、.jsx
或 .tsx
副檔名。
- 您可以在
app
目錄中共同放置其他檔案,例如元件、樣式、測試等等。深入瞭解。
- 像
getServerSideProps
和 getStaticProps
這樣的資料提取函式已被 app
內的新 API 取代。getStaticPaths
已被generateStaticParams
取代。
pages/_app.js
和 pages/_document.js
已被單個 app/layout.js
根佈局取代。深入瞭解。
pages/_error.js
已被更精細的 error.js
特殊檔案取代。深入瞭解。
pages/404.js
已被not-found.js
檔案取代。
pages/api/*
API 路由已被route.js
(路由處理程式)特殊檔案取代。
步驟 1:建立 app
目錄
更新至最新版 Next.js(需要 13.4 或更高版本)
npm install next@latest
然後,在專案的根目錄(或 src/
目錄)中建立一個新的 app
目錄。
步驟二:建立根佈局
在 app
目錄內建立一個新的 app/layout.tsx
檔案。這是一個根佈局,它將套用於 app
內的所有路由。
app/layout.tsxexport default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
app
目錄**必須**包含一個根佈局。
- 根佈局必須定義
<html>
和 <body>
標籤,因為 Next.js 不會自動建立它們。
- 根佈局會取代
pages/_app.tsx
和 pages/_document.tsx
檔案。
- 佈局檔案可以使用
.js
、.jsx
或 .tsx
副檔名。
要管理 <head>
HTML 元素,您可以使用內建的 SEO 支援。
app/layout.tsximport type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
遷移 _document.js
和 _app.js
如果您現有的專案中有 _app
或 _document
檔案,您可以將其內容(例如:全域樣式)複製到根佈局 (app/layout.tsx
)。app/layout.tsx
中的樣式**不會**套用至 pages/*
。您應該在遷移過程中保留 _app
/_document
,以防止 pages/*
路由失效。完成遷移後,您可以安全地刪除它們。
如果您正在使用任何 React Context providers,則需要將它們移至客戶端元件。
將 getLayout()
模式遷移到佈局(選用) 將屬性添加到頁面元件 以實現每頁佈局。此模式可以替換為 app
目錄中巢狀佈局的原生支援。
查看前後範例
遷移前
components/DashboardLayout.jsexport default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
pages/dashboard/index.jsimport DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
遷移後
-
從 pages/dashboard/index.js
中移除 Page.getLayout
屬性,並按照遷移頁面的步驟將其移至 app
目錄。
app/dashboard/page.jsexport default function Page() {
return <p>My Page</p>
}
-
將 DashboardLayout
的內容移至新的客戶端元件以保留 pages
目錄的行為。
app/dashboard/DashboardLayout.js'use client' // this directive should be at top of the file, before any imports.
// This is a Client Component
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
-
將 DashboardLayout
導入 app
目錄內新的 layout.js
檔案。
app/dashboard/layout.jsimport DashboardLayout from './DashboardLayout'
// This is a Server Component
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
}
-
您可以將 DashboardLayout.js
(客戶端元件)的非互動部分逐步移至 layout.js
(伺服器元件),以減少傳送到客戶端的元件 JavaScript 數量。
步驟 3:遷移 next/head
在 pages
目錄中,React 元件 next/head
用於管理 <head>
HTML 元素,例如 title
和 meta
。在 app
目錄中,next/head
已被新的內建 SEO 支援取代。
遷移前
pages/index.tsximport Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
遷移後
app/page.tsximport type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
步驟 4:遷移頁面
app
目錄中的頁面預設為伺服器元件。這與 pages
目錄不同,在 pages
目錄中,頁面是客戶端元件。
app
中的資料擷取已變更。getServerSideProps
、getStaticProps
和 getInitialProps
已被更簡單的 API 取代。
app
目錄使用巢狀資料夾來定義路由,並使用特殊的 page.js
檔案讓路由區段公開存取。
-
pages
目錄app
目錄路由 index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[slug]/page.js
/blog/post-1
我們建議將頁面遷移分成兩個主要步驟
- 步驟 1:將預設匯出的頁面元件移至新的客戶端元件。
- 步驟 2:將新的客戶端元件匯入
app
目錄內新的 page.js
檔案。
注意事項:這是最簡單的遷移路徑,因為它的行為與 pages
目錄最為相似。
步驟 1:建立新的客戶端元件
- 在
app
目錄內建立一個新的獨立檔案(例如 app/home-page.tsx
或類似檔案),匯出一個客戶端元件。要定義客戶端元件,請在檔案頂端(任何匯入之前)新增 'use client'
指令。
- 與 Pages Router 類似,有一個最佳化步驟可在初始頁面載入時將客戶端元件預渲染為靜態 HTML。
- 將預設匯出的頁面元件從
pages/index.js
移至 app/home-page.tsx
。
app/home-page.tsx'use client'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
步驟 2:建立新的頁面
-
在 app
目錄內建立一個新的 app/page.tsx
檔案。這預設為伺服器元件。
-
將 home-page.tsx
客戶端元件匯入頁面。
-
如果您先前在 pages/index.js
中擷取資料,請使用新的資料擷取 API將資料擷取邏輯直接移至伺服器元件。詳情請參閱資料擷取升級指南。
app/page.tsx// Import your Client Component
import HomePage from './home-page'
async function getPosts() {
const res = await fetch('https://...')
const posts = await res.json()
return posts
}
export default async function Page() {
// Fetch data directly in a Server Component
const recentPosts = await getPosts()
// Forward fetched data to your Client Component
return <HomePage recentPosts={recentPosts} />
}
-
如果您先前的頁面使用了 useRouter
,則需要更新到新的路由 hook。了解更多。
-
啟動您的開發伺服器並瀏覽 https://127.0.0.1:3000
。您應該會看到現有的索引路由,現在透過 app 目錄提供服務。
步驟 5:遷移路由 Hook
已新增新的路由器以支援 app
目錄中的新行為。
在 app
中,您應該使用從 next/navigation
導入的三個新的 Hook:useRouter()
、usePathname()
和 useSearchParams()
。
- 新的
useRouter
Hook 是從 next/navigation
導入的,其行為與從 next/router
導入的 pages
中的 useRouter
Hook 不同。
- 從
next/router
導入的 useRouter
Hook 在 app
目錄中不受支援,但可以繼續在 pages
目錄中使用。
- 新的
useRouter
不會返回 pathname
字串。請改用單獨的 usePathname
Hook。
- 新的
useRouter
不會返回 query
物件。搜尋參數和動態路由參數現在是分開的。請改用 useSearchParams
和 useParams
Hook。
- 您可以同時使用
useSearchParams
和 usePathname
來監聽頁面變化。詳情請參閱路由器事件章節。
- 這些新的 Hook 僅在客戶端元件中受支援。它們不能在伺服器元件中使用。
app/example-client-component.tsx'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
此外,新的 useRouter
Hook 還有以下變更:
isFallback
已移除,因為 fallback
已被取代。
locale
、locales
、defaultLocales
、domainLocales
值已被移除,因為內建的 Next.js i18n 功能在 app
目錄中不再需要。深入瞭解 i18n。
basePath
已移除。替代方案不會成為 useRouter
的一部分。它尚未實作。
asPath
已移除,因為 as
的概念已從新的路由器中移除。
isReady
已移除,因為它不再需要。在靜態渲染期間,任何使用 useSearchParams()
Hook 的元件都會略過預渲染步驟,而是在執行階段於客戶端上渲染。
route
已移除。usePathname
或 useSelectedLayoutSegments()
提供了替代方案。
在 pages
和 app
之間共用元件 useRouter
hook 。這是來自 pages
目錄的 useRouter
hook,但旨在在路由器之間共享組件時使用。一旦您準備好僅在 app
路由器上使用它,請更新至 next/navigation
中新的 useRouter
。
步驟 6:遷移資料擷取方法
pages
目錄使用 getServerSideProps
和 getStaticProps
來擷取頁面的資料。在 app
目錄中,這些先前的資料擷取函數已被基於 fetch()
和 async
React 伺服器組件構建的更簡單的 API 取代。
app/page.tsxexport default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
伺服器端渲染 (getServerSideProps
)
在 pages
目錄中,getServerSideProps
用於在伺服器上擷取資料並將 props 轉送到檔案中預設匯出的 React 組件。頁面的初始 HTML 會從伺服器進行預渲染,然後在瀏覽器中「水合」(使其具有互動性)。
pages/dashboard.js// `pages` directory
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
在 App Router 中,我們可以使用伺服器組件在 React 組件內部共同定位資料擷取。這允許我們向客戶端發送更少的 JavaScript,同時維護從伺服器渲染的 HTML。
透過將 cache
選項設定為 no-store
,我們可以指示擷取的資料永遠不要被快取。這類似於 pages
目錄中的 getServerSideProps
。
app/dashboard/page.tsx// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
存取請求物件 pages/index.js// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}
🎥 觀看影片:瞭解如何逐步採用 App Router → YouTube (16 分鐘)
遷移到 App Router 可能會是您第一次使用 Next.js 建立在其之上的 React 功能,例如伺服器元件、Suspense 等等。當與新的 Next.js 功能(例如特殊檔案和佈局)結合使用時,遷移意味著需要學習新的概念、心智模型和行為變化。
我們建議您將遷移分解成更小的步驟,以降低這些更新的綜合複雜性。app
目錄特意設計成可與 pages
目錄同時運作,以便逐頁逐步遷移。
app
目錄支援巢狀路由 *和* 佈局。深入瞭解。- 使用巢狀資料夾來定義路由,並使用特殊的
page.js
檔案讓路由區段可公開存取。深入瞭解。 - 特殊檔案命名慣例用於為每個路由區段建立 UI。最常見的特殊檔案是
page.js
和layout.js
。- 使用
page.js
定義特定路由的 UI。 - 使用
layout.js
定義跨多個路由共用的 UI。 - 特殊檔案可以使用
.js
、.jsx
或.tsx
副檔名。
- 使用
- 您可以在
app
目錄中共同放置其他檔案,例如元件、樣式、測試等等。深入瞭解。 - 像
getServerSideProps
和getStaticProps
這樣的資料提取函式已被app
內的新 API 取代。getStaticPaths
已被generateStaticParams
取代。 pages/_app.js
和pages/_document.js
已被單個app/layout.js
根佈局取代。深入瞭解。pages/_error.js
已被更精細的error.js
特殊檔案取代。深入瞭解。pages/404.js
已被not-found.js
檔案取代。pages/api/*
API 路由已被route.js
(路由處理程式)特殊檔案取代。
步驟 1:建立 app
目錄
更新至最新版 Next.js(需要 13.4 或更高版本)
npm install next@latest
然後,在專案的根目錄(或 src/
目錄)中建立一個新的 app
目錄。
步驟二:建立根佈局
在 app
目錄內建立一個新的 app/layout.tsx
檔案。這是一個根佈局,它將套用於 app
內的所有路由。
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
app
目錄**必須**包含一個根佈局。- 根佈局必須定義
<html>
和<body>
標籤,因為 Next.js 不會自動建立它們。 - 根佈局會取代
pages/_app.tsx
和pages/_document.tsx
檔案。 - 佈局檔案可以使用
.js
、.jsx
或.tsx
副檔名。
要管理 <head>
HTML 元素,您可以使用內建的 SEO 支援。
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
遷移 _document.js
和 _app.js
如果您現有的專案中有 _app
或 _document
檔案,您可以將其內容(例如:全域樣式)複製到根佈局 (app/layout.tsx
)。app/layout.tsx
中的樣式**不會**套用至 pages/*
。您應該在遷移過程中保留 _app
/_document
,以防止 pages/*
路由失效。完成遷移後,您可以安全地刪除它們。
如果您正在使用任何 React Context providers,則需要將它們移至客戶端元件。
將 getLayout()
模式遷移到佈局(選用) 將屬性添加到頁面元件 以實現每頁佈局。此模式可以替換為 app
目錄中巢狀佈局的原生支援。
查看前後範例
遷移前
components/DashboardLayout.jsexport default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
pages/dashboard/index.jsimport DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
遷移後
-
從 pages/dashboard/index.js
中移除 Page.getLayout
屬性,並按照遷移頁面的步驟將其移至 app
目錄。
app/dashboard/page.jsexport default function Page() {
return <p>My Page</p>
}
-
將 DashboardLayout
的內容移至新的客戶端元件以保留 pages
目錄的行為。
app/dashboard/DashboardLayout.js'use client' // this directive should be at top of the file, before any imports.
// This is a Client Component
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
-
將 DashboardLayout
導入 app
目錄內新的 layout.js
檔案。
app/dashboard/layout.jsimport DashboardLayout from './DashboardLayout'
// This is a Server Component
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
}
-
您可以將 DashboardLayout.js
(客戶端元件)的非互動部分逐步移至 layout.js
(伺服器元件),以減少傳送到客戶端的元件 JavaScript 數量。
步驟 3:遷移 next/head
查看前後範例
遷移前
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
import DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
遷移後
-
從
pages/dashboard/index.js
中移除Page.getLayout
屬性,並按照遷移頁面的步驟將其移至app
目錄。app/dashboard/page.jsexport default function Page() { return <p>My Page</p> }
-
將
DashboardLayout
的內容移至新的客戶端元件以保留pages
目錄的行為。app/dashboard/DashboardLayout.js'use client' // this directive should be at top of the file, before any imports. // This is a Client Component export default function DashboardLayout({ children }) { return ( <div> <h2>My Dashboard</h2> {children} </div> ) }
-
將
DashboardLayout
導入app
目錄內新的layout.js
檔案。app/dashboard/layout.jsimport DashboardLayout from './DashboardLayout' // This is a Server Component export default function Layout({ children }) { return <DashboardLayout>{children}</DashboardLayout> }
-
您可以將
DashboardLayout.js
(客戶端元件)的非互動部分逐步移至layout.js
(伺服器元件),以減少傳送到客戶端的元件 JavaScript 數量。
next/head
在 pages
目錄中,React 元件 next/head
用於管理 <head>
HTML 元素,例如 title
和 meta
。在 app
目錄中,next/head
已被新的內建 SEO 支援取代。
遷移前
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
遷移後
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
步驟 4:遷移頁面
app
目錄中的頁面預設為伺服器元件。這與pages
目錄不同,在pages
目錄中,頁面是客戶端元件。app
中的資料擷取已變更。getServerSideProps
、getStaticProps
和getInitialProps
已被更簡單的 API 取代。app
目錄使用巢狀資料夾來定義路由,並使用特殊的page.js
檔案讓路由區段公開存取。-
pages
目錄app
目錄路由 index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[slug]/page.js
/blog/post-1
我們建議將頁面遷移分成兩個主要步驟
- 步驟 1:將預設匯出的頁面元件移至新的客戶端元件。
- 步驟 2:將新的客戶端元件匯入
app
目錄內新的page.js
檔案。
注意事項:這是最簡單的遷移路徑,因為它的行為與
pages
目錄最為相似。
步驟 1:建立新的客戶端元件
- 在
app
目錄內建立一個新的獨立檔案(例如app/home-page.tsx
或類似檔案),匯出一個客戶端元件。要定義客戶端元件,請在檔案頂端(任何匯入之前)新增'use client'
指令。- 與 Pages Router 類似,有一個最佳化步驟可在初始頁面載入時將客戶端元件預渲染為靜態 HTML。
- 將預設匯出的頁面元件從
pages/index.js
移至app/home-page.tsx
。
'use client'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
步驟 2:建立新的頁面
-
在
app
目錄內建立一個新的app/page.tsx
檔案。這預設為伺服器元件。 -
將
home-page.tsx
客戶端元件匯入頁面。 -
如果您先前在
pages/index.js
中擷取資料,請使用新的資料擷取 API將資料擷取邏輯直接移至伺服器元件。詳情請參閱資料擷取升級指南。app/page.tsx// Import your Client Component import HomePage from './home-page' async function getPosts() { const res = await fetch('https://...') const posts = await res.json() return posts } export default async function Page() { // Fetch data directly in a Server Component const recentPosts = await getPosts() // Forward fetched data to your Client Component return <HomePage recentPosts={recentPosts} /> }
-
如果您先前的頁面使用了
useRouter
,則需要更新到新的路由 hook。了解更多。 -
啟動您的開發伺服器並瀏覽
https://127.0.0.1:3000
。您應該會看到現有的索引路由,現在透過 app 目錄提供服務。
步驟 5:遷移路由 Hook
已新增新的路由器以支援 app
目錄中的新行為。
在 app
中,您應該使用從 next/navigation
導入的三個新的 Hook:useRouter()
、usePathname()
和 useSearchParams()
。
- 新的
useRouter
Hook 是從next/navigation
導入的,其行為與從next/router
導入的pages
中的useRouter
Hook 不同。- 從
next/router
導入的useRouter
Hook 在app
目錄中不受支援,但可以繼續在pages
目錄中使用。
- 從
- 新的
useRouter
不會返回pathname
字串。請改用單獨的usePathname
Hook。 - 新的
useRouter
不會返回query
物件。搜尋參數和動態路由參數現在是分開的。請改用useSearchParams
和useParams
Hook。 - 您可以同時使用
useSearchParams
和usePathname
來監聽頁面變化。詳情請參閱路由器事件章節。 - 這些新的 Hook 僅在客戶端元件中受支援。它們不能在伺服器元件中使用。
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
此外,新的 useRouter
Hook 還有以下變更:
isFallback
已移除,因為fallback
已被取代。locale
、locales
、defaultLocales
、domainLocales
值已被移除,因為內建的 Next.js i18n 功能在app
目錄中不再需要。深入瞭解 i18n。basePath
已移除。替代方案不會成為useRouter
的一部分。它尚未實作。asPath
已移除,因為as
的概念已從新的路由器中移除。isReady
已移除,因為它不再需要。在靜態渲染期間,任何使用useSearchParams()
Hook 的元件都會略過預渲染步驟,而是在執行階段於客戶端上渲染。route
已移除。usePathname
或useSelectedLayoutSegments()
提供了替代方案。
在 pages
和 app
之間共用元件 useRouter
hook 。這是來自 pages
目錄的 useRouter
hook,但旨在在路由器之間共享組件時使用。一旦您準備好僅在 app
路由器上使用它,請更新至 next/navigation
中新的 useRouter
。
步驟 6:遷移資料擷取方法
useRouter
hookpages
目錄使用 getServerSideProps
和 getStaticProps
來擷取頁面的資料。在 app
目錄中,這些先前的資料擷取函數已被基於 fetch()
和 async
React 伺服器組件構建的更簡單的 API 取代。
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
伺服器端渲染 (getServerSideProps
)
在 pages
目錄中,getServerSideProps
用於在伺服器上擷取資料並將 props 轉送到檔案中預設匯出的 React 組件。頁面的初始 HTML 會從伺服器進行預渲染,然後在瀏覽器中「水合」(使其具有互動性)。
// `pages` directory
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
在 App Router 中,我們可以使用伺服器組件在 React 組件內部共同定位資料擷取。這允許我們向客戶端發送更少的 JavaScript,同時維護從伺服器渲染的 HTML。
透過將 cache
選項設定為 no-store
,我們可以指示擷取的資料永遠不要被快取。這類似於 pages
目錄中的 getServerSideProps
。
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
存取請求物件 pages/index.js// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}
// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}