跳到內容

layout.js

layout 檔案用於在 Next.js 應用中定義佈局。

app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

根佈局是根 app 目錄中最頂層的佈局。它用於定義 <html><body> 標籤以及其他全域性共享的 UI。

app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

參考

屬性

children (必填)

佈局元件應接受並使用 children prop。渲染時,children 將被佈局所包裹的路由段填充。這些主要將是子 佈局(如果存在)或 頁面 的元件,但也可能是其他特殊檔案,如 載入錯誤(如果適用)。

params (可選)

一個 Promise,解析為一個物件,其中包含從根段到該佈局的動態路由引數物件。

app/dashboard/[team]/layout.tsx
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params
}
路由示例URLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>
  • 由於 params prop 是一個 Promise。您必須使用 async/await 或 React 的 use 函式來訪問這些值。
    • 在 14 版及更早版本中,params 是一個同步屬性。為了幫助實現向後相容性,您仍然可以在 Next.js 15 中同步訪問它,但此行為將來會被棄用。

佈局屬性助手

您可以使用 LayoutProps 來型別化佈局,從而獲得從目錄結構推斷出的強型別 params 和命名槽。LayoutProps 是一個全域性可用的助手。

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>
  )
}

須知:

  • 型別在 next devnext buildnext typegen 期間生成。
  • 生成型別後,LayoutProps 助手全域性可用。無需匯入。

根佈局

app 目錄必須包含一個根佈局,它是根 app 目錄中最頂層的佈局。通常,根佈局是 app/layout.js

app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
  • 根佈局必須定義 <html><body> 標籤。
    • 不應手動向根佈局新增 <head> 標籤,例如 <title><meta>。相反,您應該使用元資料 API,它會自動處理高階要求,例如流式傳輸和重複資料刪除 <head> 元素。
  • 您可以使用路由組建立多個根佈局
    • 多個根佈局之間導航將導致完全頁面載入(而不是客戶端導航)。例如,從使用 app/(shop)/layout.js/cart 導航到使用 app/(marketing)/layout.js/blog 將導致完全頁面載入。這適用於多個根佈局。
  • 根佈局可以位於動態段下,例如在透過 app/[lang]/layout.js 實現國際化時。

注意事項

請求物件

佈局在導航期間在客戶端快取,以避免不必要的伺服器請求。

佈局不會重新渲染。它們可以被快取和重用,以避免在頁面之間導航時進行不必要的計算。透過限制佈局訪問原始請求,Next.js 可以防止在佈局中執行可能緩慢或昂貴的使用者程式碼,這可能會對效能產生負面影響。

要訪問請求物件,您可以在伺服器元件和函式中使用headerscookies API。

app/shop/layout.tsx
import { cookies } from 'next/headers'
 
export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

查詢引數

佈局在導航時不會重新渲染,因此它們無法訪問搜尋引數,否則搜尋引數會過時。

要訪問更新的查詢引數,您可以使用 Page searchParams prop,或者在客戶端元件中使用 useSearchParams 鉤子讀取它們。由於客戶端元件在導航時會重新渲染,因此它們可以訪問最新的查詢引數。

app/ui/search.tsx
'use client'
 
import { useSearchParams } from 'next/navigation'
 
export default function Search() {
  const searchParams = useSearchParams()
 
  const search = searchParams.get('search')
 
  return '...'
}
app/shop/layout.tsx
import Search from '@/app/ui/search'
 
export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}

路徑名

佈局在導航時不會重新渲染,因此它們無法訪問路徑名,否則路徑名會過時。

要訪問當前路徑名,您可以在客戶端元件中使用 usePathname 鉤子讀取它。由於客戶端元件在導航期間會重新渲染,因此它們可以訪問最新的路徑名。

app/ui/breadcrumbs.tsx
'use client'
 
import { usePathname } from 'next/navigation'
 
// Simplified breadcrumbs logic
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')
 
  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
app/docs/layout.tsx
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
 
export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}

獲取資料

佈局不能將資料傳遞給其 children。但是,您可以在路由中多次獲取相同的資料,並使用 React cache 來消除重複請求,而不會影響效能。

或者,在 Next.js 中使用fetch時,請求會自動去重。

app/lib/data.ts
export async function getUser(id: string) {
  const res = await fetch(`https://.../users/${id}`)
  return res.json()
}
app/dashboard/layout.tsx
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
 
export default async function Layout({ children }) {
  const user = await getUser('1')
 
  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
app/dashboard/page.tsx
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'
 
export default async function Page() {
  const user = await getUser('1')
 
  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}

訪問子段

佈局無法訪問其下方的路由段。要訪問所有路由段,您可以在客戶端元件中使用 useSelectedLayoutSegmentuseSelectedLayoutSegments

app/ui/nav-link.tsx
'use client'
 
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
 
export default function NavLink({
  slug,
  children,
}: {
  slug: string
  children: React.ReactNode
}) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment
 
  return (
    <Link
      href={`/blog/${slug}`}
      // Change style depending on whether the link is active
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
app/blog/layout.tsx
import { NavLink } from './nav-link'
import getPosts from './get-posts'
 
export default async function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}

示例

元資料

您可以使用metadata 物件generateMetadata 函式修改 <head> HTML 元素,例如 titlemeta

app/layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Next.js',
}
 
export default function Layout({ children }: { children: React.ReactNode }) {
  return '...'
}

重要提示:您不應手動將 <head> 標籤(例如 <title><meta>)新增到根佈局。相反,請使用元資料 API,它會自動處理高階要求,例如流式傳輸和重複資料刪除 <head> 元素。

您可以使用 usePathname 鉤子來判斷導航連結是否活躍。

由於 usePathname 是一個客戶端鉤子,您需要將導航連結提取到客戶端元件中,然後將其匯入到您的佈局中

app/ui/nav-links.tsx
'use client'
 
import { usePathname } from 'next/navigation'
import Link from 'next/link'
 
export function NavLinks() {
  const pathname = usePathname()
 
  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Home
      </Link>
 
      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        About
      </Link>
    </nav>
  )
}
app/layout.tsx
import { NavLinks } from '@/app/ui/nav-links'
 
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}

根據 params 顯示內容

使用動態路由段,您可以根據 params prop 顯示或獲取特定內容。

app/dashboard/layout.tsx
export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params
 
  return (
    <section>
      <header>
        <h1>Welcome to {team}'s Dashboard</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

在客戶端元件中讀取 params

要在客戶端元件(不能是 async)中使用 params,您可以使用 React 的 use 函式來讀取 Promise。

app/page.tsx
'use client'
 
import { use } from 'react'
 
export default function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = use(params)
}

版本歷史

版本更改
v15.0.0-RCparams 現在是一個 Promise。程式碼轉換器可用。
v13.0.0layout 引入。