44 questions
20 / 44 questions
Next.js là framework full-stack xây trên React, cung cấp sẵn routing dựa trên file, nhiều chiến lược render (SSR, SSG, ISR), tối ưu ảnh/font, và cả backend (Route Handlers, Server Actions). React thuần chỉ là thư viện UI, bạn phải tự lắp router, bundler và cơ chế render server. Next.js chuẩn hoá những mảnh đó để tập trung vào tính năng thay vì hạ tầng.
Pages Router (pages/) là mô hình cũ: mỗi file là một route, dữ liệu lấy qua getServerSideProps / getStaticProps, mọi component chạy phía client. App Router (app/, từ Next 13+) mặc định dùng Server Components, fetch dữ liệu ngay trong component, hỗ trợ nested layouts, streaming, Server Actions. App Router là hướng khuyến nghị cho dự án mới; Pages Router vẫn được hỗ trợ để tương thích.
Mặc định mọi component trong App Router là Server Component: chạy ở server, có thể async, fetch dữ liệu trực tiếp, không gửi JS xuống client. Thêm "use client" khi cần tương tác (state, effect, browser API, event handler). Giữ client component nhỏ ở phần lá để bundle gọn và tận dụng render phía server tối đa.
"use client" đặt ở dòng đầu tiên của file, đánh dấu ranh giới (boundary) chuyển từ server sang client. Mọi module được import từ file đó trở xuống sẽ nằm trong client bundle. Bạn chỉ cần khai báo ở component gốc của nhánh client, không cần lặp lại ở từng file con. Đặt boundary càng thấp (gần lá) càng tốt để giảm JS gửi xuống trình duyệt.
Trong một thư mục route, Next.js nhận diện các file: page.tsx (UI của route), layout.tsx (khung bọc chung, giữ state khi điều hướng), loading.tsx (fallback Suspense), error.tsx (bắt lỗi client), not-found.tsx (404), và template.tsx (giống layout nhưng tạo lại mới mỗi lần điều hướng). Ngoài ra còn route.ts cho API và default.tsx cho parallel routes.
Layout bọc các route con và giữ nguyên state khi điều hướng giữa các trang cùng layout (không remount, không chạy lại effect). Template cũng bọc con nhưng tạo một instance mới mỗi lần điều hướng, nên state bị reset, effect chạy lại và animation vào trang được kích hoạt lại. Dùng template khi cần hiệu ứng enter mỗi lần đổi trang hoặc logic per-navigation.
Mỗi thư mục có thể có layout.tsx riêng; Next.js lồng chúng theo cây thư mục. Layout cha bọc layout con qua prop children, tạo UI dùng chung (header, sidebar) mà không render lại khi đổi trang con. Root layout (app/layout.tsx) bắt buộc và phải chứa thẻ <html> và <body>.
export default function Layout({ children }: { children: React.ReactNode }) {
return <section><Sidebar />{children}</section>;
}
Đặt tên thư mục trong ngoặc như (marketing) tạo một route group: nhóm các route để tổ chức code hoặc gán layout chung mà không thêm segment vào URL. Ví dụ app/(shop)/cart/page.tsx vẫn có URL /cart. Rất tiện để chia nhiều layout gốc khác nhau (ví dụ (auth) và (dashboard)) mà URL không bị lồng thêm.
[id] khớp một segment (/post/1), giá trị nhận qua prop params. [...slug] là catch-all, khớp nhiều segment (/docs/a/b/c → slug = ['a','b','c']). [[...slug]] là optional catch-all, khớp cả khi không có segment nào (/docs). Trong App Router, params là Promise ở Next 15+, cần await.
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
generateStaticParams khai báo trước danh sách các giá trị params để Next.js pre-render các trang dynamic tại build time (tương đương getStaticPaths cũ). Nó chạy ở server, trả về mảng object params. Kết hợp với route [slug] để tạo SSG cho nội dung biết trước như bài blog.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((p) => ({ slug: p.slug }));
}
Khi dùng generateStaticParams, biến dynamicParams quyết định điều xảy ra với param không nằm trong danh sách. dynamicParams = true (mặc định): các param mới được render on-demand (SSR/ISR) khi có request. dynamicParams = false: mọi param ngoài danh sách trả về 404. Đặt false khi bạn muốn giới hạn tập trang cố định.
Export một object metadata (tĩnh) hoặc hàm generateMetadata (động, async) từ layout/page để Next.js sinh thẻ <head> (title, description, Open Graph...). generateMetadata nhận params/searchParams, phù hợp cho tiêu đề phụ thuộc dữ liệu.
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return { title: post.title };
}
SSG (Static Site Generation): dựng HTML tại build time, phục vụ nhanh, phù hợp nội dung ít đổi. SSR (Server-Side Rendering): dựng HTML mỗi request, luôn mới nhưng chậm hơn. ISR (Incremental Static Regeneration): phục vụ trang static đã cache, dựng lại nền theo lịch (revalidate). Ưu tiên SSG/ISR cho nội dung chung; SSR cho trang cá nhân hoá theo request.
Một route mặc định là static (dựng khi build). Nó tự động chuyển sang dynamic khi bạn dùng API phụ thuộc request như cookies(), headers(), searchParams, hoặc fetch với { cache: 'no-store' }. Bạn cũng có thể ép bằng export const dynamic = 'force-dynamic' hoặc 'force-static'. Hiểu điều này giúp tránh vô tình biến trang thành dynamic.
Đặt export const revalidate = 60 ở page/layout, hoặc fetch(url, { next: { revalidate: 60 } }) cho từng request, để bật ISR theo thời gian. Sau khoảng thời gian đó, request tiếp theo phục vụ bản cache cũ rồi dựng lại nền (stale-while-revalidate), các request sau nhận bản mới. revalidate = 0 nghĩa là luôn dynamic; số dương là chu kỳ tính bằng giây.
Các Dynamic Functions đọc dữ liệu request khiến route không thể static: cookies(), headers(), draftMode(), và việc dùng prop searchParams trong page. Khi Next.js gặp chúng, toàn route được đánh dấu dynamic và render mỗi request. Nếu chỉ một phần cần dynamic, hãy cân nhắc PPR hoặc tách phần đó vào Suspense boundary để giữ phần còn lại static.
Trong Next 15+, fetch không còn cache mặc định (mặc định no-store) — khác với Next 13/14 cache mọi thứ. Bạn bật cache tường minh bằng fetch(url, { cache: 'force-cache' }) hoặc { next: { revalidate: N } }. Dùng { cache: 'no-store' } cho dữ liệu luôn mới. Việc kiểm soát rõ ràng giúp tránh bug dữ liệu cũ khó lường.
unstable_cache từ next/cache bọc một hàm bất kỳ (không phải fetch) để cache kết quả ở server — ví dụ query Prisma/DB. Bạn truyền key parts và options (tags, revalidate) để có thể revalidate theo tag. Nó cần thiết vì cơ chế cache tự động của Next chỉ áp cho fetch.
const getUser = unstable_cache(async (id) => db.user.find(id), ['user'], { revalidate: 60, tags: ['user'] });
Time-based revalidate dựng lại theo lịch, còn on-demand cho phép xoá cache ngay lập tức khi có sự kiện (ví dụ sau khi cập nhật DB). revalidatePath('/blog') làm mới đúng path; revalidateTag('posts') làm mới mọi fetch/unstable_cache gắn tag đó. Thường gọi trong Server Action hoặc Route Handler sau khi mutate dữ liệu.
revalidateTag('posts');
Server Actions là hàm async chạy trên server, khai báo bằng "use server", được gọi trực tiếp từ component (kể cả từ form) mà không cần tạo API endpoint thủ công. Chúng phù hợp cho mutation: tạo/sửa/xoá dữ liệu, sau đó revalidatePath/revalidateTag. Next.js tự sinh cơ chế RPC an toàn giữa client và server.
async function createTodo(formData: FormData) {
"use server";
await db.todo.create({ text: formData.get('text') });
}