跳到內容
App Router入門佈局和頁面

佈局與頁面

Next.js 使用基於檔案系統的路由,這意味著你可以使用資料夾和檔案來定義路由。本頁將指導你如何建立佈局和頁面,以及如何在其間進行連結。

建立頁面

頁面是在特定路由上渲染的 UI。要建立一個頁面,在 app 目錄下新增一個 page 檔案並預設匯出 React 元件。例如,要建立一個索引頁面 (/)

page.js special file
app/page.tsx
export default function Page() {
  return <h1>Hello Next.js!</h1>
}

建立佈局

佈局是在多個頁面之間共享的 UI。在導航時,佈局會保留狀態、保持互動性並且不會重新渲染。

你可以透過從 layout 檔案預設匯出 React 元件來定義佈局。該元件應接受一個 children 屬性,它可以是一個頁面或另一個佈局

例如,要建立一個接受索引頁面作為子元件的佈局,請在 app 目錄下新增一個 layout 檔案

layout.js special file
app/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        {/* Place children where you want to render a page or nested layout */}
        <main>{children}</main>
      </body>
    </html>
  )
}

上面的佈局稱為根佈局,因為它是在 app 目錄的根部定義的。根佈局是必需的,並且必須包含 htmlbody 標籤。

建立巢狀路由

巢狀路由是由多個 URL 片段組成的路由。例如,/blog/[slug] 路由由三個片段組成

  • /(根片段)
  • blog(片段)
  • [slug](葉片段)

在 Next.js 中

  • 資料夾用於定義對映到 URL 片段的路由片段。
  • 檔案(如 pagelayout)用於建立在片段中顯示的 UI。

要建立巢狀路由,你可以將資料夾相互巢狀。例如,要新增 /blog 路由,請在 app 目錄中建立一個名為 blog 的資料夾。然後,為了使 /blog 可以公開訪問,新增一個 page.tsx 檔案

File hierarchy showing blog folder and a page.js file
app/blog/page.tsx
// Dummy imports
import { getPosts } from '@/lib/posts'
import { Post } from '@/ui/post'
 
export default async function Page() {
  const posts = await getPosts()
 
  return (
    <ul>
      {posts.map((post) => (
        <Post key={post.id} post={post} />
      ))}
    </ul>
  )
}

你可以繼續巢狀資料夾以建立巢狀路由。例如,要為特定部落格文章建立路由,請在 blog 內部建立一個新的 [slug] 資料夾並新增一個 page 檔案

File hierarchy showing blog folder with a nested slug folder and a page.js file
app/blog/[slug]/page.tsx
function generateStaticParams() {}
 
export default function Page() {
  return <h1>Hello, Blog Post Page!</h1>
}

將資料夾名稱用方括號括起來(例如 [slug])會建立一個動態路由片段,用於根據資料生成多個頁面。例如,部落格文章、產品頁面等。

巢狀佈局

預設情況下,資料夾層次結構中的佈局也是巢狀的,這意味著它們透過其 children 屬性包裝子佈局。你可以透過在特定路由片段(資料夾)內新增 layout 來巢狀佈局。

例如,要為 /blog 路由建立一個佈局,請在 blog 資料夾中新增一個新的 layout 檔案。

File hierarchy showing root layout wrapping the blog layout
app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

如果你將上述兩個佈局組合起來,根佈局 (app/layout.js) 將會包裹部落格佈局 (app/blog/layout.js),而部落格佈局又會包裹部落格 (app/blog/page.js) 和部落格文章頁面 (app/blog/[slug]/page.js)。

建立動態片段

動態片段允許您建立從資料生成的路由。例如,您可以建立一個動態片段,根據部落格文章資料生成路由,而不是手動為每篇部落格文章建立路由。

要建立動態片段,請將片段(資料夾)名稱用方括號括起來:[segmentName]。例如,在 app/blog/[slug]/page.tsx 路由中,[slug] 就是動態片段。

app/blog/[slug]/page.tsx
export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPost(slug)
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  )
}

瞭解更多關於動態片段params屬性的資訊。

巢狀在動態片段中的佈局也可以訪問 params 屬性。

使用搜索引數渲染

在伺服器元件頁面中,您可以使用searchParams屬性訪問搜尋引數

app/page.tsx
export default async function Page({
  searchParams,
}: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const filters = (await searchParams).filters
}

使用 searchParams 會使您的頁面進入動態渲染,因為它需要傳入請求來讀取搜尋引數。

客戶端元件可以使用 useSearchParams 鉤子來讀取搜尋引數。

瞭解更多關於 useSearchParams靜態渲染動態渲染路由中的使用。

何時使用以及如何使用

  • 當你需要搜尋引數來載入頁面資料(例如,分頁、從資料庫過濾)時,使用 searchParams 屬性。
  • 當搜尋引數僅在客戶端使用(例如,過濾已透過屬性載入的列表)時,使用 useSearchParams
  • 作為一項小最佳化,你可以在回撥函式或事件處理器中使用 new URLSearchParams(window.location.search) 來讀取搜尋引數,而不會觸發重新渲染。

頁面間連結

你可以使用 <Link> 元件在路由之間進行導航。<Link> 是一個內建的 Next.js 元件,它擴充套件了 HTML <a> 標籤,提供了預取客戶端導航

例如,要生成部落格文章列表,從 next/link 匯入 <Link> 並向元件傳遞一個 href 屬性

app/ui/post.tsx
import Link from 'next/link'
 
export default async function Post({ post }) {
  const posts = await getPosts()
 
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

須知: <Link> 是 Next.js 中在路由之間導航的主要方式。你也可以使用useRouter 鉤子進行更高階的導航。

路由屬性輔助函式

Next.js 提供了實用工具型別,可從你的路由結構中推斷出 params 和命名槽

  • PageProps: page 元件的屬性,包括 paramssearchParams
  • LayoutProps: layout 元件的屬性,包括 children 和任何命名槽(例如 @analytics 等資料夾)。

這些是全域性可用的輔助函式,在執行 next devnext buildnext typegen 時生成。

app/blog/[slug]/page.tsx
export default async function Page(props: PageProps<'/blog/[slug]'>) {
  const { slug } = await props.params
  return <h1>Blog post: {slug}</h1>
}
app/dashboard/layout.tsx
export default function Layout(props: LayoutProps<'/dashboard'>) {
  return (
    <section>
      {props.children}
      {/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
      {/* {props.analytics} */}
    </section>
  )
}

須知

  • 靜態路由會將 params 解析為 {}
  • PagePropsLayoutProps 是全域性輔助工具——無需匯入。
  • 型別在 next devnext buildnext typegen 期間生成。