如何從 Pages Router 遷移至 App Router

本指南將幫助您:

升級步驟

Node.js 版本

最低 Node.js 版本現在要求 v18.17。詳情請參閱 Node.js 文件

Next.js 版本

要更新到 Next.js 版本 13,請使用您偏好的套件管理器執行以下命令:

終端機
npm install next@latest react@latest react-dom@latest

ESLint 版本

如果您使用 ESLint,需要升級 ESLint 版本:

終端機
npm install -D eslint-config-next@latest

小提示:您可能需要重啟 VS Code 中的 ESLint 伺服器才能使變更生效。打開命令面板(Mac 上按 cmd+shift+p;Windows 上按 ctrl+shift+p)並搜尋 ESLint: Restart ESLint Server

後續步驟

更新完成後,請參閱以下章節進行後續操作:

升級新功能

Next.js 13 引入了新的 App Router,帶來新功能和約定。新的 Router 位於 app 目錄中,與 pages 目錄共存。

升級到 Next.js 13 強制要求使用 App Router。您可以繼續使用 pages 目錄,同時享受適用於兩個目錄的新功能,例如更新的 Image 元件Link 元件Script 元件字體優化

<Image/> 元件

Next.js 12 通過臨時導入 next/future/image 對 Image 元件進行了改進。這些改進包括減少客戶端 JavaScript、更簡單的圖片擴展和樣式設置、更好的無障礙性以及原生瀏覽器懶加載。

在版本 13 中,這些新行為現在是 next/image 的預設行為。

有兩個程式碼轉換工具可幫助您遷移到新的 Image 元件:

<Link> 元件 不再需要手動添加 <a> 標籤作為子元素。此行為在 版本 12.2 中作為實驗性選項添加,現在是預設行為。在 Next.js 13 中,<Link> 總是渲染 <a> 並允許您將屬性傳遞到底層標籤。

例如:

import Link from 'next/link'

// Next.js 12: `<a>` 必須嵌套,否則會被排除
<Link href="/about">
  <a>關於</a>
</Link>

// Next.js 13: `<Link>` 總是會在底層渲染 `<a>`
<Link href="/about">
  關於
</Link>

要將您的連結升級到 Next.js 13,可以使用 new-link 程式碼轉換

<Script> 元件

next/script 的行為已更新以支援 pagesapp,但需要進行一些變更以確保順利遷移:

  • 將之前包含在 _document.js 中的任何 beforeInteractive 腳本移動到根佈局文件 (app/layout.tsx)。
  • 實驗性的 worker 策略在 app 中尚不可用,標記為此策略的腳本需要移除或修改為使用其他策略(例如 lazyOnload)。
  • onLoadonReadyonError 處理程序在伺服器元件中不起作用,因此請確保將它們移動到 客戶端元件 或完全移除。

字體優化

之前,Next.js 通過 內聯字體 CSS 幫助您優化字體。版本 13 引入了新的 next/font 模組,讓您能夠自定義字體加載體驗,同時仍確保出色的性能和隱私。next/fontpagesapp 目錄中都受支援。

雖然 內聯 CSSpages 中仍然有效,但在 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.jslayout.js
    • 使用 page.js 定義路由特有的 UI。
    • 使用 layout.js 定義多個路由共享的 UI。
    • 特殊文件可以使用 .js.jsx.tsx 文件擴展名。
  • 您可以在 app 目錄中並置其他文件,例如元件、樣式、測試等。了解更多
  • 數據獲取函數如 getServerSidePropsgetStaticProps 已被 app 中的 新 API 取代。getStaticPaths 已被 generateStaticParams 取代。
  • pages/_app.jspages/_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 目錄。

步驟 2:創建根佈局

app 目錄中創建一個新的 app/layout.tsx 文件。這是一個 根佈局,將應用於 app 內的所有路由。

export default function RootLayout({
  // 佈局必須接受 children 屬性。
  // 這將被嵌套佈局或頁面填充
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({
  // 佈局必須接受 children 屬性。
  // 這將被嵌套佈局或頁面填充
  children,
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
  • app 目錄 必須 包含一個根佈局。
  • 根佈局必須定義 <html><body> 標籤,因為 Next.js 不會自動創建它們。
  • 根佈局取代了 pages/_app.tsxpages/_document.tsx 文件。
  • 佈局文件可以使用 .js.jsx.tsx 擴展名。

要管理 <head> HTML 元素,可以使用 內建的 SEO 支援

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '首頁',
  description: '歡迎使用 Next.js',
}
export const metadata = {
  title: '首頁',
  description: '歡迎使用 Next.js',
}

遷移 _document.js_app.js

如果您有現有的 _app_document 文件,可以將其內容(例如全局樣式)複製到根佈局 (app/layout.tsx)。app/layout.tsx 中的樣式 不會 應用於 pages/*。在遷移過程中應保留 _app/_document 以防止 pages/* 路由損壞。完全遷移後,可以安全地刪除它們。

如果您使用任何 React Context 提供者,需要將它們移動到 客戶端元件

getLayout() 模式遷移至佈局(可選)

Next.js 建議在 pages 目錄中為頁面元件添加 屬性 以實現每頁佈局。此模式可以被 app 目錄中對 嵌套佈局 的原生支援取代。

查看前後範例

之前

components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <h2>我的儀表板</h2>
      {children}
    </div>
  )
}
pages/dashboard/index.js
import DashboardLayout from '../components/DashboardLayout'

export default function Page() {
  return <p>我的頁面</p>
}

Page.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>
}

之後

  • pages/dashboard/index.js 中移除 Page.getLayout 屬性,並按照 遷移頁面的步驟 將頁面遷移到 app 目錄。

    app/dashboard/page.js
    export default function Page() {
      return <p>我的頁面</p>
    }
  • DashboardLayout 的內容移動到新的 客戶端元件 以保留 pages 目錄的行為。

    app/dashboard/DashboardLayout.js
    'use client' // 此指令應位於文件頂部,任何導入之前。
    
    // 這是一個客戶端元件
    export default function DashboardLayout({ children }) {
      return (
        <div>
          <h2>我的儀表板</h2>
          {children}
        </div>
      )
    }
  • DashboardLayout 導入到 app 目錄中的新 layout.js 文件。

    app/dashboard/layout.js
    import DashboardLayout from './DashboardLayout'
    
    // 這是一個伺服器元件
    export default function Layout({ children }) {
      return <DashboardLayout>{children}</DashboardLayout>
    }
  • 您可以逐步將 DashboardLayout.js(客戶端元件)中的非互動部分移動到 layout.js(伺服器元件)中,以減少發送到客戶端的元件 JavaScript 量。

步驟 3:遷移 next/head

pages 目錄中,next/head React 元件用於管理 <head> HTML 元素,例如 titlemeta。在 app 目錄中,next/head 被新的 內建 SEO 支援 取代。

之前:

import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>我的頁面標題</title>
      </Head>
    </>
  )
}
import Head from 'next/head'

export default function Page() {
  return (
    <>
      <Head>
        <title>我的頁面標題</title>
      </Head>
    </>
  )
}

之後:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '我的頁面標題',
}

export default function Page() {
  return '...'
}
export const metadata = {
  title: '我的頁面標題',
}

export default function Page() {
  return '...'
}

查看所有 metadata 選項

步驟 4:遷移頁面

我們建議將頁面遷移分為兩個主要步驟:

  • 步驟 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'

// 這是一個客戶端元件(與 `pages` 目錄中的元件相同)
// 它接收資料作為 props,可以存取狀態和效果,並且在
// 初始頁面載入時在伺服器上進行預渲染。
export default function HomePage({ recentPosts }) {
  return (
    <div>
      {recentPosts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}
'use client'

// 這是一個客戶端元件。它接收資料作為 props,
// 並且可以存取狀態和效果,就像 `pages` 目錄中的頁面元件一樣。
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。詳情請參閱資料獲取升級指南

    // 匯入您的客戶端元件
    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() {
      // 直接在伺服器元件中獲取資料
      const recentPosts = await getPosts()
      // 將獲取的資料傳遞給您的客戶端元件
      return <HomePage recentPosts={recentPosts} />
    }
    // 匯入您的客戶端元件
    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() {
      // 直接在伺服器元件中獲取資料
      const recentPosts = await getPosts()
      // 將獲取的資料傳遞給您的客戶端元件
      return <HomePage recentPosts={recentPosts} />
    }
  • 如果您的舊頁面使用了 useRouter,您需要更新為新的路由鉤子。了解更多

  • 啟動您的開發伺服器並訪問 http://localhost:3000。您應該會看到現有的索引路由,現在通過 app 目錄提供。

步驟 5:遷移路由鉤子

已新增一個新的路由器來支援 app 目錄中的新行為。

app 中,您應該使用從 next/navigation 匯入的三個新鉤子:useRouter()usePathname()useSearchParams()

  • 新的 useRouter 鉤子是從 next/navigation 匯入的,其行為與 pages 中從 next/router 匯入的 useRouter 鉤子不同。
  • 新的 useRouter 不返回 pathname 字串。請改用獨立的 usePathname 鉤子。
  • 新的 useRouter 不返回 query 物件。搜尋參數和動態路由參數現在是分開的。請改用 useSearchParamsuseParams 鉤子。
  • 您可以一起使用 useSearchParamsusePathname 來監聽頁面變更。詳情請參閱路由器事件部分。
  • 這些新鉤子僅在客戶端元件中受支援。它們不能在伺服器元件中使用。
'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}
'use client'

import { useRouter, usePathname, useSearchParams } from 'next/navigation'

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  // ...
}

此外,新的 useRouter 鉤子有以下變更:

  • isFallback 已被移除,因為 fallback 已被替換
  • localelocalesdefaultLocalesdomainLocales 值已被移除,因為內建的 i18n Next.js 功能在 app 目錄中不再需要。了解更多關於 i18n
  • basePath 已被移除。替代方案不會是 useRouter 的一部分。目前尚未實現。
  • asPath 已被移除,因為新路由器中已移除 as 的概念。
  • isReady 已被移除,因為不再需要。在靜態渲染期間,任何使用 useSearchParams() 鉤子的元件將跳過預渲染步驟,改為在運行時在客戶端渲染。
  • route 已被移除。usePathnameuseSelectedLayoutSegments() 提供了替代方案。

查看 useRouter() API 參考

pagesapp 之間共用元件

要保持元件在 pagesapp 路由器之間的相容性,請參考next/compat/router 匯入的 useRouter 鉤子。 這是 pages 目錄中的 useRouter 鉤子,但旨在在路由器之間共用元件時使用。當您準備好僅在 app 路由器中使用它時,請更新為新的next/navigation 匯入的 useRouter

步驟 6:遷移資料獲取方法

pages 目錄使用 getServerSidePropsgetStaticProps 來獲取頁面資料。在 app 目錄中,這些舊的資料獲取函數已被基於 fetch()async React 伺服器元件的更簡單 API 取代。

export default async function Page() {
  // 此請求應被快取直到手動失效。
  // 類似於 `getStaticProps`。
  // `force-cache` 是預設值,可以省略。
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

  // 此請求應在每次請求時重新獲取。
  // 類似於 `getServerSideProps`。
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

  // 此請求應被快取,有效期為 10 秒。
  // 類似於帶有 `revalidate` 選項的 `getStaticProps`。
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}
export default async function Page() {
  // 此請求應被快取直到手動失效。
  // 類似於 `getStaticProps`。
  // `force-cache` 是預設值,可以省略。
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

  // 此請求應在每次請求時重新獲取。
  // 類似於 `getServerSideProps`。
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

  // 此請求應被快取,有效期為 10 秒。
  // 類似於帶有 `revalidate` 選項的 `getStaticProps`。
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })

  return <div>...</div>
}

伺服器端渲染 (getServerSideProps)

pages 目錄中,getServerSideProps 用於在伺服器上獲取資料並將 props 傳遞給檔案中的預設匯出 React 元件。頁面的初始 HTML 從伺服器預渲染,然後在瀏覽器中「水合」(hydrating) 頁面(使其可互動)。

pages/dashboard.js
// `pages` 目錄

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 中,我們可以使用伺服器元件 (Server Components) 將資料獲取與 React 元件放在一起。這使我們可以向客戶端發送更少的 JavaScript,同時保留伺服器渲染的 HTML。

通過將 cache 選項設置為 no-store,我們可以指示獲取的資料應永遠不被快取。這類似於 pages 目錄中的 getServerSideProps

// `app` 目錄

// 此函數可以命名為任何名稱
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>
  )
}
// `app` 目錄

// 此函數可以命名為任何名稱
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 目錄中,您可以基於 Node.js HTTP API 檢索基於請求的資料。

例如,您可以從 getServerSideProps 檢索 req 物件,並使用它來獲取請求的 cookies 和 headers。

pages/index.js
// `pages` 目錄

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 目錄公開了新的唯讀函數來檢索請求資料:

// `app` 目錄
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = (await headers()).get('authorization')

  return '...'
}

export default async function Page() {
  // 您可以直接在伺服器元件中使用 `cookies` 或 `headers`,
  // 或在您的資料獲取函數中使用
  const theme = (await cookies()).get('theme')
  const data = await getData()
  return '...'
}
// `app` 目錄
import { cookies, headers } from 'next/headers'

async function getData() {
  const authHeader = (await headers()).get('authorization')

  return '...'
}

export default async function Page() {
  // 您可以直接在伺服器元件中使用 `cookies` 或 `headers`,
  // 或在您的資料獲取函數中使用
  const theme = (await cookies()).get('theme')
  const data = await getData()
  return '...'
}

靜態網站生成 (getStaticProps)

pages 目錄中,getStaticProps 函數用於在建置時預渲染頁面。此函數可用於從外部 API 或直接從資料庫獲取資料,並在建置過程中將此資料傳遞給整個頁面。

pages/index.js
// `pages` 目錄

export async function getStaticProps() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return { props: { projects } }
}

export default function Index({ projects }) {
  return projects.map((project) => <div>{project.name}</div>)
}

app 目錄中,使用 fetch() 獲取資料將預設為 cache: 'force-cache',這將快取請求資料直到手動失效。這類似於 pages 目錄中的 getStaticProps

app/page.js
// `app` 目錄

// 此函數可以命名為任何名稱
async function getProjects() {
  const res = await fetch(`https://...`)
  const projects = await res.json()

  return projects
}

export default async function Index() {
  const projects = await getProjects()

  return projects.map((project) => <div>{project.name}</div>)
}

動態路徑 (getStaticPaths)

pages 目錄中,getStaticPaths 函數用於定義在建置時應預先渲染的動態路徑。

pages/posts/[id].js
// `pages` 目錄
import PostLayout from '@/components/post-layout'

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
  }
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  return { props: { post } }
}

export default function Post({ post }) {
  return <PostLayout post={post} />
}

app 目錄中,getStaticPathsgenerateStaticParams 取代。

generateStaticParams 的行為與 getStaticPaths 類似,但提供更簡化的 API 來回傳路由參數,並可在 版面配置 (layouts) 中使用。generateStaticParams 的回傳格式是一個片段陣列,而非嵌套的 param 物件陣列或解析後的路徑字串。

app/posts/[id]/page.js
// `app` 目錄
import PostLayout from '@/components/post-layout'

export async function generateStaticParams() {
  return [{ id: '1' }, { id: '2' }]
}

async function getPost(params) {
  const res = await fetch(`https://.../posts/${(await params).id}`)
  const post = await res.json()

  return post
}

export default async function Post({ params }) {
  const post = await getPost(params)

  return <PostLayout post={post} />
}

app 目錄的新模型中,使用 generateStaticParams 這個名稱比 getStaticPaths 更合適。get 前綴被更具描述性的 generate 取代,這在 getStaticPropsgetServerSideProps 不再必要的情況下更為貼切。Paths 後綴則被 Params 取代,這對於具有多個動態片段 (dynamic segments) 的嵌套路由更為合適。


取代 fallback

pages 目錄中,getStaticPaths 回傳的 fallback 屬性用於定義未在建置時預先渲染的頁面行為。此屬性可設為 true 以在頁面生成時顯示後備頁面,設為 false 以顯示 404 頁面,或設為 blocking 以在請求時生成頁面。

pages/posts/[id].js
// `pages` 目錄

export async function getStaticPaths() {
  return {
    paths: [],
    fallback: 'blocking'
  };
}

export async function getStaticProps({ params }) {
  ...
}

export default function Post({ post }) {
  return ...
}

app 目錄中,config.dynamicParams 屬性 控制如何處理 generateStaticParams 之外的參數:

  • true:(預設值)未包含在 generateStaticParams 中的動態片段會按需生成。
  • false:未包含在 generateStaticParams 中的動態片段將回傳 404。

這取代了 pages 目錄中 getStaticPathsfallback: true | false | 'blocking' 選項。fallback: 'blocking' 選項未包含在 dynamicParams 中,因為在使用串流 (streaming) 時,'blocking'true 之間的差異可以忽略不計。

app/posts/[id]/page.js
// `app` 目錄

export const dynamicParams = true;

export async function generateStaticParams() {
  return [...]
}

async function getPost(params) {
  ...
}

export default async function Post({ params }) {
  const post = await getPost(params);

  return ...
}

dynamicParams 設為 true(預設值)時,請求尚未生成的路由片段時,將進行伺服器渲染並快取。

增量靜態再生 (getStaticProps 搭配 revalidate)

pages 目錄中,getStaticProps 函數允許您添加 revalidate 欄位,以在指定時間後自動重新生成頁面。

pages/index.js
// `pages` 目錄

export async function getStaticProps() {
  const res = await fetch(`https://.../posts`)
  const posts = await res.json()

  return {
    props: { posts },
    revalidate: 60,
  }
}

export default function Index({ posts }) {
  return (
    <Layout>
      <PostList posts={posts} />
    </Layout>
  )
}

app 目錄中,使用 fetch() 進行資料獲取時可以使用 revalidate,這將快取請求指定的秒數。

app/page.js
// `app` 目錄

async function getPosts() {
  const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
  const data = await res.json()

  return data.posts
}

export default async function PostList() {
  const posts = await getPosts()

  return posts.map((post) => <div>{post.name}</div>)
}

API 路由

API 路由在 pages/api 目錄中繼續正常工作,無需任何變更。然而,在 app 目錄中,它們已被 路由處理器 (Route Handlers) 取代。

路由處理器允許您使用 Web RequestResponse API 為指定路由建立自訂請求處理器。

export async function GET(request: Request) {}
export async function GET(request) {}

須知:如果您之前使用 API 路由從客戶端呼叫外部 API,現在可以使用 伺服器元件 (Server Components) 來安全地獲取資料。了解更多關於 資料獲取 (data fetching) 的資訊。

單頁應用程式 (SPA)

如果您同時從單頁應用程式 (SPA) 遷移到 Next.js,請參閱我們的 文件 以了解更多。

步驟 7:樣式設定

pages 目錄中,全域樣式表僅限於 pages/_app.js。在 app 目錄中,此限制已被取消。全域樣式可以添加到任何版面配置、頁面或元件中。

Tailwind CSS

如果您使用 Tailwind CSS,則需要將 app 目錄添加到您的 tailwind.config.js 檔案中:

tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // <-- 添加此行
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

您還需要在 app/layout.js 檔案中導入您的全域樣式:

app/layout.js
import '../styles/globals.css'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

了解更多關於 使用 Tailwind CSS 設定樣式

同時使用 App Router 和 Pages Router

在不同 Next.js 路由器之間導航時,將進行硬導航 (hard navigation)。使用 next/link 的自動連結預取不會跨路由器預取。

相反,您可以 優化導航 以在 App Router 和 Pages Router 之間保留預取和快速的頁面轉換。了解更多

代碼轉換 (Codemods)

Next.js 提供代碼轉換 (Codemod) 來幫助您在功能被棄用時升級程式碼庫。詳見 代碼轉換 (Codemods) 以獲取更多資訊。