Routing cơ bản
Bài 4 – Hệ thống định tuyến cơ bản và nested routing trong Next.js
Routing cơ bản và Nested Routing
Next.js sử dụng cơ chế định tuyến dựa trên cấu trúc thư mục (file-system routing). Mỗi folder trong thư mục app/ sẽ tương ứng với một route. Cách tổ chức này giúp code rõ ràng, tránh cấu hình phức tạp, và dễ mở rộng với các tuyến đường lồng nhau.
1. Nguyên tắc routing trong Next.js
1.1. File-system routing
- Mỗi thư mục = một route segment
- page.tsx = entry point của route
- Không cần config: Tự động dựa trên cấu trúc thư mục
Ý nghĩa trọng tâm:
Cấu trúc thư mục = Cấu trúc URL. Đơn giản và trực quan!
2. Các loại route cơ bản
2.1. Static Routes
// app/page.tsx → route "/"
export default function Home() {
return <h1>Trang chủ</h1>;
}
// app/about/page.tsx → route "/about"
export default function About() {
return (
<main>
<h1>Giới thiệu</h1>
<p>Đây là trang giới thiệu về công ty chúng tôi.</p>
</main>
);
}
// app/contact/page.tsx → route "/contact"
export default function Contact() {
return <h1>Liên hệ</h1>;
}
2.2. Nested Routes
// app/dashboard/page.tsx → route "/dashboard"
export default function Dashboard() {
return <h1>Dashboard chính</h1>;
}
// app/dashboard/users/page.tsx → route "/dashboard/users"
export default function Users() {
return <h1>Danh sách người dùng</h1>;
}
// app/dashboard/settings/page.tsx → route "/dashboard/settings"
export default function Settings() {
return <h1>Cài đặt</h1>;
}
2.3. Dynamic Routes
// app/products/[id]/page.tsx → route "/products/:id"
export default function ProductDetail({ params }: { params: { id: string } }) {
return (
<div>
<h1>Chi tiết sản phẩm: {params.id}</h1>
<p>Đây là trang chi tiết cho sản phẩm #{params.id}</p>
</div>
);
}
// app/blog/[slug]/page.tsx → route "/blog/:slug"
export default function BlogPost({ params }: { params: { slug: string } }) {
return (
<article>
<h1>Bài viết: {params.slug}</h1>
<p>Đây là nội dung bài viết với slug: {params.slug}</p>
</article>
);
}
2.4. Catch-all Routes
// app/docs/[...slug]/page.tsx → route "/docs/*"
export default function DocsPage({ params }: { params: { slug: string[] } }) {
return (
<div>
<h1>Documentation</h1>
<p>Path: /docs/{params.slug.join('/')}</p>
</div>
);
}
3. Cấu trúc thư mục thực tế
3.1. Blog website structure
app/
├── page.tsx # Trang chủ /
├── blog/
│ ├── page.tsx # /blog (danh sách bài viết)
│ ├── [slug]/
│ │ ├── page.tsx # /blog/[slug] (chi tiết bài viết)
│ │ └── loading.tsx # Loading state cho bài viết
│ └── categories/
│ └── [category]/
│ └── page.tsx # /blog/categories/[category]
├── about/
│ └── page.tsx # /about
└── contact/
└── page.tsx # /contact
3.2. E-commerce structure
app/
├── page.tsx # Homepage /
├── products/
│ ├── page.tsx # /products (danh sách)
│ ├── [category]/
│ │ └── page.tsx # /products/[category]
│ └── [id]/
│ ├── page.tsx # /products/[id] (chi tiết)
│ └── loading.tsx # Loading state
├── cart/
│ └── page.tsx # /cart
└── checkout/
└── page.tsx # /checkout
4. Advanced routing patterns
4.1. Multiple dynamic segments
// app/shop/[category]/[product]/page.tsx
export default function ProductPage({
params
}: {
params: {
category: string
product: string
}
}) {
return (
<div>
<h1>Sản phẩm: {params.product}</h1>
<p>Danh mục: {params.category}</p>
</div>
);
}
4.2. Optional catch-all
// app/optional/[[...slug]]/page.tsx
export default function OptionalPage({
params
}: {
params: { slug?: string[] }
}) {
return (
<div>
<h1>Optional Catch-all</h1>
<p>Slug: {params.slug?.join('/') || 'none'}</p>
</div>
);
}
5. Route Groups (Tổ chức logic)
5.1. Không ảnh hưởng URL
app/
├── (marketing)/ # Route group - không hiện trong URL
│ ├── page.tsx # /
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
├── (dashboard)/ # Route group khác
│ ├── dashboard/
│ │ └── page.tsx # /dashboard
│ └── settings/
│ └── page.tsx # /settings
└── layout.tsx # Root layout
5.2. Layout khác nhau
// (marketing)/layout.tsx - Layout cho marketing pages
export default function MarketingLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="marketing-layout">
<header>Marketing Header</header>
{children}
<footer>Marketing Footer</footer>
</div>
);
}
// (dashboard)/layout.tsx - Layout cho dashboard
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard-layout">
<nav>Dashboard Nav</nav>
<main>{children}</main>
</div>
);
}
6. Best Practices
6.1. Naming conventions
// ✅ Tốt: Clear, descriptive names
app/products/[productId]/page.tsx
app/blog/[blogSlug]/page.tsx
app/categories/[categoryName]/page.tsx
// ❌ Tránh: Vague names
app/items/[id]/page.tsx
app/posts/[slug]/page.tsx
6.2. Folder organization
// ✅ Tốt: Logical grouping
app/
├── (auth)/
│ ├── login/
│ ├── register/
│ └── forgot-password/
├── (shop)/
│ ├── products/
│ ├── cart/
│ └── checkout/
└── (user)/
├── profile/
├── orders/
└── settings/
7. Bài tập thực hành
Bài tập 1
Tạo các route static:
/services→ Hiển thị danh sách dịch vụ/services/design→ Chi tiết dịch vụ thiết kế/services/development→ Chi tiết dịch vụ development
Bài tập 2
Tạo dynamic route /blog/[slug] với:
- Hiển thị giá trị slug
- Thêm metadata cơ bản
- Tạo loading state
Bài tập 3
Tạo nested routing cho e-commerce:
/products→ Danh sách sản phẩm/products/[category]→ Sản phẩm theo danh mục/products/[category]/[id]→ Chi tiết sản phẩm
8. Kết luận bài học
- File-system routing: Cấu trúc thư mục = Cấu trúc URL
- page.tsx: Entry point bắt buộc cho mỗi route
- Dynamic routes: Sử dụng
[param]cho flexible URLs - Nested routing: Tạo UX tốt với routes lồng nhau
- Route groups: Tổ chức code logic mà không ảnh hưởng URL
🔑 GHI NHỚ QUAN TRỌNG:
- Mỗi thư mục cần
page.tsxđể tạo route- Dynamic routes dùng
[param]hoặc[...slug]- Route groups giúp tổ chức code tốt hơn
- Luôn plan cấu trúc routes trước khi code