App Router dùng thư mục để định nghĩa đường dẫn: mỗi thư mục là một đoạn URL, tệp page.tsx tạo trang, layout.tsx tạo khung dùng chung. Bài này hướng dẫn tạo nhiều trang, route động, layout lồng nhau, liên kết và các tệp đặc biệt loading, error, not-found.
Bạn sẽ học được
- Tạo trang theo thư mục
- Dùng route động [id]
- Tạo layout lồng nhau và thanh điều hướng
- Dùng Link, useRouter, loading.tsx, error.tsx, not-found.tsx
Một số khái niệm
| Đường dẫn tệp | URL |
|---|---|
| app/page.tsx | / |
| app/gioi-thieu/page.tsx | /gioi-thieu |
| app/sinhvien/page.tsx | /sinhvien |
| app/sinhvien/[id]/page.tsx | /sinhvien/1, /sinhvien/2... |
| app/(admin)/quan-tri/page.tsx | /quan-tri (nhóm (admin) không vào URL) |
Layout
// src/app/layout.tsx
import type { Metadata } from 'next';
import Link from 'next/link';
import './globals.css';
export const metadata: Metadata = { title: 'QLSV', description: 'Quản lý sinh viên' };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="vi">
<body className="bg-slate-50 text-slate-800">
<nav className="flex gap-4 bg-emerald-700 px-6 py-3 text-white">
<Link href="/" className="font-bold">QLSV</Link>
<Link href="/sinhvien">Sinh viên</Link>
<Link href="/gioi-thieu">Giới thiệu</Link>
</nav>
<main className="mx-auto max-w-5xl p-6">{children}</main>
</body>
</html>
);
}Route động
// src/app/sinhvien/[id]/page.tsx
export default async function ChiTiet({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params; // params là Promise (Next.js 15+)
return <h1 className="text-2xl font-bold">Sinh viên mã {id}</h1>;
}// tham số truy vấn: /sinhvien?q=an&page=2
export default async function DanhSach({ searchParams }: { searchParams: Promise<{ q?: string; page?: string }> }) {
const { q = '', page = '1' } = await searchParams;
return <p>Tìm: {q} — trang {page}</p>;
}Tệp đặc biệt
| Tệp | Vai trò |
|---|---|
| loading.tsx | Hiện khi trang đang tải dữ liệu (skeleton) |
| error.tsx | Bắt lỗi trong đoạn route (phải là Client Component) |
| not-found.tsx | Trang 404; gọi notFound() để hiện |
| layout.tsx | Khung giữ nguyên khi chuyển trang con |
// src/app/sinhvien/[id]/page.tsx
import { notFound } from 'next/navigation';
if (!sv) notFound();'use client';
import { useRouter, usePathname } from 'next/navigation';
const router = useRouter();
router.push('/sinhvien'); // điều hướng bằng mãMẹo hay
Dùng <Link> thay cho thẻ <a> khi liên kết nội bộ: Next.js tải trước trang và chuyển trang không tải lại toàn bộ.
Lưu ý
Từ Next.js 15, params và searchParams là Promise; phải await trước khi dùng.
Thực hành
- Tạo trang /sinhvien, /gioi-thieu, /sinhvien/[id].
- Tạo layout có thanh điều hướng, đánh dấu mục đang mở bằng usePathname.
- Thêm loading.tsx và not-found.tsx.
- Tạo nhóm (admin) với layout riêng có sidebar.
Tóm tắt bài học
- Thư mục = đoạn URL, page.tsx = trang.
- [id] cho route động; params là Promise.
- layout.tsx lồng nhau, giữ nguyên khi chuyển trang.
- Link, useRouter, loading, error, not-found.