ข้ามไปยังเนื้อหา

Pages, Layouts และ Nested Routing

ใน App Router folder หนึ่งคือ route segment หนึ่ง page.tsx ทำให้ segment นั้นเข้าถึงเป็น URL ได้ ส่วน layout.tsx ห่อ segment นั้นพร้อม children ทุกตัว และยัง mount ค้างไว้ข้ามการ navigate

ทุก folder ใต้ app/ map ไปเป็น URL segment หนึ่ง segment จะกลายเป็น URL ที่เข้าถึงได้ก็ต่อเมื่อมี page.tsx อยู่ข้างใน

app/
layout.tsx // root layout (required)
page.tsx // route: /
dashboard/
layout.tsx // wraps /dashboard and its children
page.tsx // route: /dashboard
settings/
page.tsx // route: /dashboard/settings

page.tsx เป็น Server Component โดย default และ render UI เฉพาะของ segment นั้น

app/dashboard/page.tsx
export default function DashboardPage() {
return <h1>Dashboard</h1>
}

ทุกแอป App Router ต้องมี root layout หนึ่งตัว เป็น layout เดียวที่ render tag <html> และ <body> และต้อง render children เสมอ

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

layout.tsx ห่อ segment ของตัวเองและ descendant ทั้งหมด nested layout compose จากนอกเข้าใน root ห่อ dashboard ห่อ settings จุดสำคัญคือ layout จะ persist ข้ามการ navigate เมื่อย้ายระหว่าง route พี่น้องกัน layout ที่ใช้ร่วมจะไม่ re-render ทำให้ state และตำแหน่ง scroll ถูกเก็บไว้

// app/dashboard/layout.tsx — persists across navigation
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<section>
<nav>{/* stays mounted, keeps scroll + local state */}</nav>
<main>{children}</main>
</section>
)
}

ใส่ loading.tsx แล้ว Next.js จะห่อ segment ด้วย Suspense boundary ให้อัตโนมัติ fallback จะโชว์ทันทีระหว่างที่งานฝั่ง server ยัง stream เข้ามา

// app/dashboard/loading.tsx — automatic Suspense boundary
export default function Loading() {
return <p>Loading dashboard…</p>
}

ใส่ error.tsx แล้ว Next.js จะห่อ segment ด้วย error boundary ไฟล์นี้ต้องเป็น Client Component และรับ error พร้อมฟังก์ชัน reset ไว้ลอง render segment ใหม่

'use client' // error boundaries must be Client Components
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
graph TD
  R["app/layout.tsx (root, persists)"] --> D["dashboard/layout.tsx (persists)"]
  D --> P["dashboard/page.tsx"]
  D --> S["settings/page.tsx"]
Nested layouts compose และ persist
อะไรทำให้ folder segment เข้าถึงเป็น route ได้
layout มีความพิเศษอย่างไรตอน navigate
layout ตัวไหนที่ render tag html และ body
error.tsx ต้องเป็นอะไร และรับอะไรบ้าง