1. Mục tiêu bài học
Trong ngày đầu tiên, chúng ta sẽ tìm hiểu những khái niệm nền tảng của Next.js 16 và xây dựng một website blog nhỏ để thực hành.
Sau bài học này, bạn có thể:
- Hiểu Next.js là gì và khác React như thế nào.
- Khởi tạo project Next.js 16 bằng
create-next-app. - Hiểu cấu trúc thư mục
src/app. - Tạo trang bằng
page.tsxvà layout dùng chung bằnglayout.tsx. - Phân biệt Server Components và Client Components.
- Tạo dynamic route với URL dạng
/posts/[slug]. - Hiểu cách sử dụng
paramstrong Next.js 16. - Kiểm tra build và xử lý cảnh báo bảo mật từ
npm audit. - Hiểu nền tảng để kết nối Next.js với một backend API.
2. Next.js là gì?
Next.js là framework được xây dựng dựa trên React, giúp phát triển ứng dụng web hoàn chỉnh. React cung cấp cơ chế xây dựng giao diện bằng component, còn Next.js bổ sung các quy ước về routing, rendering, lấy dữ liệu và tổ chức ứng dụng.
| Tính năng | Ý nghĩa |
|---|---|
| App Router | Tổ chức trang và layout dựa trên cấu trúc thư mục. |
| Server Components | Cho phép render component phía server theo mặc định trong App Router. |
| Client Components | Hỗ trợ tương tác cần JavaScript phía trình duyệt. |
| Dynamic Routes | Tạo URL động như /posts/what-is-nextjs. |
| Data Fetching | Lấy dữ liệu từ API và các nguồn dữ liệu khác. |
| Metadata | Khai báo tiêu đề, mô tả và thông tin phục vụ SEO. |
2.1. React và Next.js khác nhau thế nào?
React là thư viện xây dựng giao diện người dùng. Next.js sử dụng React và cung cấp thêm các quy ước để tổ chức một ứng dụng web.
Với App Router, cấu trúc thư mục thể hiện trực tiếp các URL:
src/app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
└── posts/
├── page.tsx → /posts
└── [slug]/
└── page.tsx → /posts/:slug
Nhờ cách tổ chức này, chúng ta không cần tự xây dựng một hệ thống ánh xạ route riêng cho những trường hợp cơ bản.
2.2. Next.js trong kiến trúc ứng dụng web
Next.js có thể được sử dụng độc lập cho những ứng dụng phù hợp hoặc kết hợp với backend API riêng. Ví dụ, frontend sử dụng Next.js và backend sử dụng ASP.NET Core Web API.
flowchart LR
A["Người dùng"] --> B["Trình duyệt"]
B --> C["Next.js"]
C --> D["ASP.NET Core Web API"]
D --> E["Database"]
E --> D
D --> C
C --> B
- Next.js: Quản lý giao diện, routing và hiển thị dữ liệu.
- ASP.NET Core Web API: Xử lý nghiệp vụ, xác thực và cung cấp dữ liệu.
- Database: Lưu trữ dữ liệu của ứng dụng.
Trong Day 1, chúng ta chưa cần xây dựng backend. Trước tiên, hãy hiểu cách Next.js tổ chức trang và dữ liệu thông qua một ví dụ đơn giản.
2.3. Một request được xử lý như thế nào trong Next.js?
Để hiểu Next.js, chúng ta không nên chỉ nhìn vào cấu trúc thư mục. Hãy theo dõi điều gì xảy ra khi người dùng truy cập một URL.
Ví dụ, người dùng mở http://localhost:3000/posts.
Đây là mô hình khái quát. Cách render và truyền dữ liệu thực tế có thể khác nhau tùy route, caching, rendering strategy và quá trình điều hướng.
Trong quá trình này, có hai môi trường thực thi cần phân biệt:
- Server — Node.js: xử lý các phần chạy phía server, chẳng hạn logic của Server Components và việc lấy dữ liệu từ API trong ví dụ của chúng ta.
- Client — Browser: hiển thị giao diện, thực thi JavaScript phía client và xử lý các tương tác cần thiết.
Server Component vẫn có thể tạo ra giao diện HTML được gửi tới trình duyệt. Vì vậy, không nên hiểu rằng code chạy trên server thì trình duyệt không nhận được giao diện.
Sự khác biệt giữa hai môi trường này giải thích tại sao chúng ta cần nhiều công cụ debug: Terminal và Node.js debugger để kiểm tra code phía server; Browser DevTools để kiểm tra code phía client.
3. Khởi tạo project Next.js 16
3.1. Kiểm tra môi trường
Mở terminal và kiểm tra Node.js cùng npm:
node --version
npm --version
Hãy sử dụng phiên bản Node.js được Next.js hỗ trợ. Tham khảo tài liệu cài đặt chính thức của Next.js để kiểm tra yêu cầu hiện hành.
3.2. Tạo project
Project thực hành được tạo bằng lệnh:
npx create-next-app@latest nextjs-day1-lab --typescript --eslint --app --import-alias "@/*" --use-npm --src-dir
Ý nghĩa các tham số:
| Tham số | Giải thích |
|---|---|
create-next-app@latest |
Sử dụng phiên bản CLI mới nhất tại thời điểm chạy. |
--typescript |
Sử dụng TypeScript. |
--eslint |
Cấu hình ESLint để kiểm tra mã nguồn. |
--app |
Sử dụng App Router. |
--import-alias "@/*" |
Cấu hình alias @/ cho đường dẫn import. |
--use-npm |
Sử dụng npm để quản lý package. |
--src-dir |
Đặt mã nguồn trong thư mục src. |
Trong lần thực hành này, CLI cài đặt Next.js 16.4.0. Để kiểm tra phiên bản thực tế trong project, chạy:
npm ls next react react-dom
Lưu ý: Phiên bản được cài đặt có thể thay đổi theo thời điểm chạy lệnh @latest. Hãy kiểm tra kết quả trong project của bạn.
3.3. Có bắt buộc phải dùng Tailwind CSS không?
Không. CLI có thể chọn template có Tailwind CSS. Bạn vẫn có thể giữ cấu hình đã tạo mà không sử dụng các utility class của Tailwind trong bài học này.
Nếu tạo project mới và không muốn sử dụng Tailwind, có thể thêm tham số --no-tailwind:
npx create-next-app@latest nextjs-day1-lab --typescript --eslint --app --import-alias "@/*" --use-npm --src-dir --no-tailwind
Nếu đã tạo project có Tailwind, không cần tạo lại project chỉ vì lý do này. Trong Day 1, chúng ta sẽ không thêm CSS mới.
4. Xử lý cảnh báo bảo mật sau khi tạo project
Sau khi khởi tạo project, tôi chạy lệnh:
npm audit
Kết quả báo có 5 lỗ hổng mức High. Đây là tình huống có thể gặp ngay cả khi sử dụng CLI chính thức, bởi các package được cài đặt có thể phụ thuộc vào những phiên bản đang có cảnh báo bảo mật.
4.1. Truy tìm dependency gây cảnh báo
Để kiểm tra chuỗi dependency liên quan, chạy:
npm ls next eslint-config-next @next/eslint-plugin-next fast-glob micromatch braces
Kết quả thực tế:
nextjs-day1-lab
+-- eslint-config-next@16.4.0
| `-- @next/eslint-plugin-next@16.4.0
| `-- fast-glob@3.3.1
| `-- micromatch@4.0.8
| `-- braces@3.0.3
`-- next@16.4.0
Có thể minh họa chuỗi dependency như sau:
flowchart TD
A["eslint-config-next"]
B["@next/eslint-plugin-next"]
C["fast-glob"]
D["micromatch"]
E["braces"]
F["Security Advisory"]
A --> B --> C --> D --> E --> F
Package liên quan đến cảnh báo là braces, một dependency gián tiếp trong chuỗi công cụ phát triển.
Theo GitHub Security Advisory GHSA-vfj7-8cjw-p6xm, vấn đề được báo cáo liên quan đến việc xử lý brace pattern lồng nhau quá sâu, có thể gây cạn stack và khiến tiến trình Node.js bị dừng.
Cần phân biệt hai khái niệm:
- Dependency có lỗ hổng: Phiên bản package nằm trong phạm vi bị ảnh hưởng của một advisory.
- Ứng dụng có thể bị khai thác: Phụ thuộc vào cách package được sử dụng, dữ liệu đầu vào và môi trường thực thi.
Cảnh báo không đồng nghĩa ứng dụng đã bị xâm nhập. Tuy nhiên, cũng không nên bỏ qua cảnh báo mà chưa đánh giá nguyên nhân và phạm vi ảnh hưởng.
4.2. Vì sao không nên chạy ngay npm audit fix --force?
npm đề xuất cách sửa lỗi bằng lệnh:
npm audit fix --force
Trong kết quả thực tế, npm cảnh báo rằng thao tác này có thể cài đặt eslint-config-next@14.2.35, trong khi project đang sử dụng Next.js 16.
Việc hạ phiên bản cấu hình ESLint từ 16 xuống 14 có thể gây không tương thích với cấu hình lint hoặc quy trình build của project.
Vì vậy, tôi không chạy npm audit fix --force một cách máy móc.
Tương tự, không nên tự ép braces sang một phiên bản chưa được xác minh là đã vá. Chỉ nên ghi đè dependency khi đã xác định được phiên bản an toàn, kiểm tra tính tương thích và thử nghiệm kết quả.
4.3. Kiểm tra riêng dependencies production
Tiếp theo, tôi chạy:
npm audit --omit=dev
Kết quả:
found 0 vulnerabilities
Điều này có nghĩa npm không phát hiện lỗ hổng trong nhóm dependency production được kiểm tra.
Chuỗi dependency gây cảnh báo xuất phát từ công cụ ESLint, vốn thường nằm trong nhóm devDependencies. Vì vậy, kết quả kiểm tra toàn bộ dependency tree có thể khác với kết quả kiểm tra riêng production.
Tuy nhiên, kết quả này không chứng minh toàn bộ project đã hoàn toàn an toàn. Các dependency phục vụ phát triển vẫn cần được theo dõi, đặc biệt khi chúng được sử dụng trong CI/CD hoặc môi trường build.
4.4. Cách xử lý trong project hiện tại
Đối với project học tập này, tôi chọn phương án thận trọng:
- Giữ nguyên Next.js 16 và phiên bản cấu hình ESLint hiện tại.
- Không hạ phiên bản package chỉ để làm biến mất cảnh báo.
- Không ép dependency sang phiên bản chưa được xác minh.
- Chạy ứng dụng và kiểm tra build.
- Theo dõi advisory và kiểm tra lại khi có bản vá tương thích.
Chạy các lệnh sau để kiểm tra:
npm run dev
npm run build
npm audit --omit=dev
npm audit
Đây là cách xử lý tạm thời có kiểm soát, không phải bản vá triệt để cho braces. Với ứng dụng production, cần đánh giá thêm khả năng khai thác, quy trình build và chính sách bảo mật trước khi quyết định chấp nhận rủi ro.
5. Tìm hiểu cấu trúc project
Sau khi cài đặt, cấu trúc project có thể tương tự như sau:
nextjs-day1-lab/
├── public/
├── src/
│ └── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── package-lock.json
├── tsconfig.json
└── ...
| File hoặc thư mục | Vai trò |
|---|---|
src/app/page.tsx |
Định nghĩa trang chủ tại URL /. |
src/app/layout.tsx |
Định nghĩa root layout dùng chung. |
src/app/globals.css |
Chứa CSS toàn cục được import vào layout. |
public/ |
Lưu các tài nguyên tĩnh. |
package.json |
Khai báo scripts và dependencies. |
next.config.ts |
Cấu hình Next.js. |
tsconfig.json |
Cấu hình TypeScript. |
6. Page và Layout trong App Router
6.1. Tạo trang chủ
Trong App Router, file page.tsx định nghĩa giao diện của một route.
Mở src/app/page.tsx và thay nội dung bằng:
export default function HomePage() {
return (
<main>
<h1>Ứng dụng Next.js 16 đầu tiên</h1>
<p>Chào mừng bạn đến với Day 1.</p>
</main>
);
}
Khởi chạy ứng dụng:
npm run dev
Mở URL được hiển thị trong terminal, thường là http://localhost:3000. Bạn sẽ thấy trang chủ với nội dung vừa tạo.
6.2. Tạo layout dùng chung
Layout giúp nhiều trang sử dụng chung các thành phần như header, navigation và footer.
Mở src/app/layout.tsx. Giữ lại các import cần thiết và điều chỉnh nội dung theo ví dụ sau:
import type { Metadata } from "next";
import "./globals.css";
import Link from "next/link";
export const metadata: Metadata = {
title: "Creating My First Next.js App",
description: "My first Next.js app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="vi">
<body>
<header>
<nav>
<Link href="/">Trang chủ</Link>{" | "}
<Link href="/posts">Bài viết</Link>
</nav>
</header>
{children}
</body>
</html>
);
}
Next.js khuyến nghị sử dụng component Link từ next/link để điều hướng nội bộ thay vì dùng thẻ HTML <a> trực tiếp
Đây là ví dụ layout tối giản. Giữ nguyên file CSS được CLI tạo sẵn; bài học này không yêu cầu thêm CSS mới.
Thuộc tính children đại diện cho nội dung của route hiện tại.
flowchart TD
A["RootLayout"]
B["Header / Navigation"]
C["children"]
D["HomePage"]
E["PostsPage"]
F["PostDetailPage"]
A --> B
A --> C
C --> D
C --> E
C --> F
Khi người dùng truy cập /, layout hiển thị trang chủ bên trong children. Khi truy cập /posts, phần nội dung sẽ là trang danh sách bài viết.
7. Server Components và Client Components
7.1. Server Components
Trong App Router, component là Server Component theo mặc định, trừ khi module được đánh dấu bằng "use client" tại ranh giới client phù hợp.
Ví dụ:
export default function AboutPage() {
return (
<main>
<h1>Giới thiệu</h1>
<p>Đây là một Server Component.</p>
</main>
);
}
Server Components phù hợp với các trường hợp:
- Hiển thị nội dung lấy từ nguồn dữ liệu bên ngoài.
- Truy xuất dữ liệu phía server.
- Render bài viết hoặc thông tin sản phẩm.
- Giảm lượng JavaScript phía client khi không cần tương tác.
7.2. Client Components
Khi cần tương tác phía trình duyệt như state hoặc event handler, hãy sử dụng chỉ thị "use client".
Tạo file src/app/components/Counter.tsx:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<section>
<p>Số lần nhấn: {count}</p>
<button onClick={() => setCount((value) => value + 1)}>
Tăng
</button>
</section>
);
}
Sử dụng component trong src/app/page.tsx:
import Counter from "./components/Counter";
export default function HomePage() {
return (
<main>
<h1>Ứng dụng Next.js 16 đầu tiên</h1>
<Counter />
</main>
);
}
| Tiêu chí | Server Component | Client Component |
|---|---|---|
| Mặc định trong App Router | Có | Không |
| React state và event handler phía client | Không trực tiếp | Có |
| Hiển thị nội dung từ API | Phù hợp trong nhiều trường hợp | Phù hợp khi cần tương tác |
"use client" |
Không cần | Cần tại ranh giới client |
Hai loại component có thể kết hợp với nhau. Việc lựa chọn phụ thuộc vào nhu cầu render, truy xuất dữ liệu và tương tác của từng thành phần.
8. Xây dựng blog nhỏ với Dynamic Routes
Bây giờ chúng ta sẽ tạo một website blog đơn giản để hiểu cách URL được ánh xạ tới trang và cách lấy dữ liệu theo slug.
8.1. Tạo dữ liệu mẫu
Tạo file src/data/posts.ts:
export type Post = {
slug: string;
title: string;
excerpt: string;
content: string;
};
export const posts: Post[] = [
{
slug: "what-is-nextjs",
title: "Next.js là gì?",
excerpt: "Giới thiệu về framework Next.js.",
content:
"Next.js là framework dựa trên React để xây dựng ứng dụng web.",
},
{
slug: "understanding-app-router",
title: "Tìm hiểu App Router",
excerpt: "Cách thư mục và file định nghĩa route.",
content:
"App Router sử dụng thư mục app để tổ chức trang, layout và route.",
},
{
slug: "server-and-client-components",
title: "Server và Client Components",
excerpt: "Tìm hiểu sự khác nhau giữa hai loại component.",
content:
"Server Components phù hợp để render nội dung, còn Client Components hỗ trợ tương tác phía trình duyệt.",
},
];
Đây là dữ liệu giả lập được lưu trực tiếp trong mã nguồn. Trong ứng dụng thực tế, dữ liệu có thể đến từ API hoặc database.
8.2. Tạo trang danh sách bài viết
Tạo file src/app/posts/page.tsx:
import Link from "next/link";
import { posts } from "@/data/posts";
export default function PostsPage() {
return (
<main>
<h1>Danh sách bài viết</h1>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<h2>
<Link href={`/posts/${post.slug}`}>
{post.title}
</Link>
</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</main>
);
}
Truy cập /posts, bạn sẽ thấy danh sách ba bài viết.
Trong ví dụ này:
Linkcủa Next.js hỗ trợ điều hướng giữa các route.map()tạo phần tử giao diện cho mỗi bài viết.keygiúp React nhận diện từng phần tử trong danh sách.
8.3. Tạo Dynamic Route
Tạo file:
src/app/posts/[slug]/page.tsx
Thư mục [slug] là một dynamic segment. Ví dụ:
| URL | Giá trị slug |
|---|---|
/posts/what-is-nextjs |
what-is-nextjs |
/posts/understanding-app-router |
understanding-app-router |
/posts/server-and-client-components |
server-and-client-components |
8.4. Xử lý params trong Next.js 16
Trong Next.js 16, params của page là một Promise. Vì vậy, chúng ta cần sử dụng await trước khi đọc giá trị bên trong.
Nội dung file src/app/posts/[slug]/page.tsx:
import { notFound } from "next/navigation";
import { posts } from "@/data/posts";
type PostDetailPageProps = {
params: Promise<{
slug: string;
}>;
};
export default async function PostDetailPage({
params,
}: PostDetailPageProps) {
const { slug } = await params;
const post = posts.find((item) => item.slug === slug);
if (!post) {
notFound();
}
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
);
}
Có ba điểm cần chú ý:
- Hàm page được khai báo là
asyncđể sử dụngawait params. - Kiểu của
paramsđược khai báo là Promise trong ví dụ Next.js 16 này. notFound()xử lý slug không tồn tại bằng cách hiển thị giao diện 404.
8.5. Kiểm tra các route
| URL | Kết quả mong đợi |
|---|---|
/ |
Trang chủ |
/posts |
Danh sách bài viết |
/posts/what-is-nextjs |
Chi tiết bài viết Next.js |
/posts/understanding-app-router |
Chi tiết bài viết App Router |
/posts/unknown-post |
Trang 404 |
App Router dựa trên cấu trúc thư mục để ánh xạ các URL này, nên chúng ta không cần tự xây dựng một router riêng cho ví dụ.
9. Kết nối Next.js với ASP.NET Core Web API
Trong ứng dụng thực tế, dữ liệu thường được cung cấp bởi backend API thay vì khai báo trực tiếp trong mã nguồn frontend.
Ví dụ, một ứng dụng có thể sử dụng Next.js làm frontend và ASP.NET Core Web API làm backend:
flowchart LR
A["Next.js Page"]
B["ASP.NET Core Web API"]
C["Database"]
A -->|"HTTP Request"| B
B --> C
C --> B
B -->|"JSON Response"| A
Backend chịu trách nhiệm xử lý nghiệp vụ và truy xuất dữ liệu. Next.js gọi API, nhận JSON và chuyển dữ liệu thành giao diện.
9.1. Ví dụ gọi API từ Server Component
Giả sử backend cung cấp endpoint GET /api/articles/{slug} và trả về JSON có cấu trúc:
{
"title": "Giới thiệu Next.js",
"content": "Đây là bài viết giới thiệu về Next.js."
}
Đây là hợp đồng API minh họa. Bạn cần xây dựng endpoint tương ứng trong backend ASP.NET Core.
Tạo hàm lấy dữ liệu phía server:
type Article = {
title: string;
content: string;
};
async function getArticle(slug: string): Promise<Article | null> {
const baseUrl = process.env.API_BASE_URL;
if (!baseUrl) {
throw new Error("API_BASE_URL is not configured");
}
const response = await fetch(
`${baseUrl}/api/articles/${encodeURIComponent(slug)}`
);
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error("Failed to fetch article");
}
return response.json() as Promise<Article>;
}
Trong file .env.local, cấu hình địa chỉ backend:
API_BASE_URL=https://localhost:7001
Hãy thay URL bằng địa chỉ thực tế của ASP.NET Core Web API. Không sử dụng tiền tố NEXT_PUBLIC_ cho biến chỉ cần ở phía server.
Trong page, bạn có thể gọi hàm trên sau khi lấy slug từ params:
import { notFound } from "next/navigation";
type Article = {
title: string;
content: string;
};
async function getArticle(slug: string): Promise<Article | null> {
const baseUrl = process.env.API_BASE_URL;
if (!baseUrl) {
throw new Error("API_BASE_URL is not configured");
}
const response = await fetch(
`${baseUrl}/api/articles/${encodeURIComponent(slug)}`
);
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error("Failed to fetch article");
}
return response.json() as Promise<Article>;
}
export default async function ArticlePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const article = await getArticle(slug);
if (!article) {
notFound();
}
return (
<main>
<h1>{article.title}</h1>
<p>{article.content}</p>
</main>
);
}
Ví dụ này minh họa cách kết nối frontend và backend. Khi triển khai thực tế, cần bổ sung xử lý lỗi phù hợp, xác thực nếu API yêu cầu, và kiểm tra kiểu dữ liệu trả về thay vì mặc định rằng JSON luôn đúng cấu trúc.
Lưu ý: Nếu Next.js gọi backend trực tiếp từ server, cấu hình CORS thường không phải yếu tố quyết định cho request server-to-server. Nếu trình duyệt gọi API trực tiếp, backend cần cấu hình CORS phù hợp với origin của frontend.
Phần tích hợp API chỉ nhằm giới thiệu hướng phát triển tiếp theo. Day 1 vẫn tập trung vào routing và component; chúng ta chưa cần xây dựng backend để hoàn thành bài thực hành.
10. Kiểm tra ứng dụng và build
Khởi chạy development server:
npm run dev
Kiểm tra các route trong trình duyệt. Sau đó dừng server và chạy:
npm run build
Nếu project có script lint, bạn có thể chạy thêm:
npm run lint
Nếu script này không tồn tại trong package.json, hãy kiểm tra scripts hiện tại thay vì mặc định rằng mọi project Next.js đều có cấu hình giống nhau.
Cuối cùng, kiểm tra lại dependencies:
npm audit --omit=dev
npm audit
Lệnh đầu kiểm tra nhóm production dependencies; lệnh thứ hai kiểm tra dependency tree rộng hơn. Nếu cảnh báo trong development dependencies vẫn còn, hãy ghi nhận và theo dõi thay vì ép hạ phiên bản không tương thích.
11. Debug Next.js 16 bằng VS Code và Antigravity IDE
11.1. Vì sao lập trình viên cần biết debug?
Debug là quá trình tìm nguyên nhân gây ra lỗi bằng cách quan sát chương trình khi nó chạy, thay vì chỉ sửa code theo phỏng đoán.
Khi phát triển ứng dụng Next.js, chúng ta thường gặp các nhóm lỗi sau:
| Nhóm lỗi | Ví dụ | Nơi kiểm tra đầu tiên |
|---|---|---|
| Syntax / Compile Error | Thiếu dấu ngoặc, import sai module | Terminal và error overlay |
| Routing Error | Truy cập URL chưa có page tương ứng | Browser và cấu trúc thư mục |
| Server-side Error | Server Component hoặc hàm lấy dữ liệu bị lỗi | Terminal và Node.js debugger |
| Client-side Error | Event handler hoặc JavaScript phía trình duyệt bị lỗi | Browser Console và Sources |
| API Error | API trả về HTTP 500 hoặc dữ liệu không đúng định dạng | Network, Terminal và debugger |
11.2. Phân biệt Server-side và Client-side Debugging
Trong App Router, page và layout mặc định là Server Components nếu không được đánh dấu bằng "use client" tại ranh giới client phù hợp.
Ví dụ, app/page.tsx có thể chứa:
export default function HomePage() {
console.log("HomePage is rendering");
return (
<main>
<h1>Ứng dụng Next.js 16 đầu tiên</h1>
<p>Chào mừng bạn đến với Day 1.</p>
</main>
);
}
Với component này, log thường xuất hiện trong Terminal chạy Next.js vì hàm được thực thi phía server.
Nếu chuyển sang một Client Component sử dụng state hoặc event handler, log trong phần code thực thi phía client sẽ xuất hiện trong Browser Console.
Quy tắc thực hành:
- Không thấy trang hoặc gặp lỗi compile: kiểm tra Terminal trước.
- Server Component báo lỗi khi render: xem stack trace trong Terminal và thử Node.js debugger.
- Nút bấm không hoạt động hoặc giao diện gặp lỗi JavaScript: mở Browser Console.
- Request API thất bại: kiểm tra Network và xác định request được thực hiện từ server hay browser.
11.3. Debug bằng breakpoint là gì?
Breakpoint là điểm dừng do lập trình viên đặt trong mã nguồn. Khi chương trình thực thi đến điểm này, debugger tạm dừng để bạn quan sát trạng thái hiện tại.
Khi chương trình dừng, bạn có thể:
- Variables: xem giá trị các biến.
- Watch: theo dõi biểu thức cụ thể.
- Call Stack: xem chuỗi lời gọi hàm dẫn đến vị trí hiện tại.
- Step Over: thực thi dòng tiếp theo mà không đi sâu vào hàm được gọi.
- Step Into: đi vào hàm đang được gọi.
- Continue: tiếp tục chạy cho đến breakpoint tiếp theo hoặc khi chương trình kết thúc.
Breakpoint giúp trả lời câu hỏi: chương trình đang chạy đến đâu, dữ liệu hiện tại là gì và điều kiện nào khiến kết quả không như mong đợi?
11.4. Cấu hình debug trong Visual Studio Code
VS Code sử dụng file .vscode/launch.json để khai báo các cấu hình chạy debugger của project.
Bước 1: Tạo folder .vscode tại thư mục gốc project, ngang hàng với package.json.
Bước 2: Tạo file .vscode/launch.json với cấu hình sau:
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev -- --inspect"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"],
"skipFiles": ["<node_internals>/**"],
"serverReadyAction": {
"action": "debugWithChrome",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
}
}
]
}
Trong cấu hình này:
debug server-sidekhởi động Next.js với Node.js Inspector.debug client-sidemở Chrome để debug code phía trình duyệt.debug full stackkết hợp debug server với việc mở debugger cho Chrome khi server sẵn sàng.
Ở cấu hình full stack, debugWithChrome yêu cầu debugger Chrome tương thích. Nếu dùng Edge, có thể thay bằng debugWithEdge.
Bước 3: Mở Run and Debug bằng Ctrl + Shift + D, chọn cấu hình muốn chạy và nhấn F5.
Nếu port 3000 đã được sử dụng, hãy dừng server đang chạy hoặc cập nhật URL theo port thực tế.
Lưu ý: Chỉ dùng một cấu hình khởi chạy server tại một thời điểm để tránh xung đột cổng hoặc nhiều debugger cùng cố gắng sử dụng một Inspector endpoint.
Cách chọn cấu hình trong IDE
-
Mở Run and Debug bằng tổ hợp phím
Ctrl + Shift + Dtrong VS Code hoặc IDE hỗ trợ phím tắt này. - Tìm danh sách cấu hình debug ở phía trên.
-
Chọn một trong các cấu hình:
Next.js: Debug Server— debug code chạy phía Server.Next.js: debug client-side— debug code chạy phía trình duyệt.
- Nhấn F5 để chạy cấu hình đã chọn.
Lưu ý: Nhấn F5 chỉ chạy cấu hình đang được chọn.
Nếu chọn Next.js: debug client-side, bạn cần khởi động
Next.js trước bằng lệnh sau trong Terminal:
npm run dev
Sau khi ứng dụng chạy tại http://localhost:3000,
nhấn F5 để mở trình duyệt và bắt đầu debug Client.
Ngược lại, cấu hình Next.js: Debug Server ở ví dụ
đã thiết lập lệnh khởi động kèm Node Inspector, nên có thể tự khởi động
server khi nhấn F5.
11.5. Thực hành breakpoint trong app/page.tsx
Sau khi chạy cấu hình server-side, mở app/page.tsx hoặc src/app/page.tsx, tùy cấu trúc project.
Bước 1: Đặt breakpoint tại dòng console.log hoặc dòng thực thi bên trong HomePage.
Bước 2: Mở trang chủ trong trình duyệt.
Bước 3: Nếu breakpoint được kích hoạt, quan sát Variables và Call Stack.
Bước 4: Dùng Step Over để đi qua các dòng code, rồi Continue để tiếp tục thực thi.
Nếu breakpoint không dừng, hãy xác nhận debugger đã khởi động đúng tiến trình Node.js, server đang phục vụ đúng project và source maps có thể ánh xạ tới file nguồn.
Việc tải lại trang hoặc điều hướng có thể khiến Server Component được thực thi lại. Log xuất hiện nhiều lần trong development không nhất thiết có nghĩa là lỗi.
11.6. Debug trong Antigravity IDE
Antigravity IDE cung cấp môi trường phát triển có terminal và các công cụ hỗ trợ lập trình bằng AI. Tuy nhiên, cần phân biệt khả năng chạy ứng dụng với khả năng nhận trực tiếp cấu hình debugger.
Cách 1 — Thử dùng launch.json:
- Mở project Next.js trong Antigravity.
- Tạo file
.vscode/launch.jsonnhư phần trên. - Mở khu vực Run and Debug nếu phiên bản IDE cung cấp.
- Kiểm tra xem cấu hình
Next.js: debug server-sidecó xuất hiện và chạy được hay không.
Nếu Antigravity nhận cấu hình và breakpoint dừng đúng vị trí, bạn có thể sử dụng quy trình tương tự VS Code.
Cách 2 — Dùng Node.js Inspector:
Nếu IDE không nhận hoặc không chạy được cấu hình debugger, mở Terminal tích hợp và chạy:
npm run dev -- --inspect
Khi Inspector hoạt động, Terminal thường hiển thị thông báo có dạng:
Debugger listening on ws://127.0.0.1:9229/...
Giữ server đang chạy. Mở một tab Chrome mới và truy cập chrome://inspect. Tìm Node.js target tương ứng rồi chọn Inspect để mở debugger.
Trong tab Sources, bạn có thể tìm file nguồn, đặt breakpoint và quan sát biến. Nếu không thấy target, hãy kiểm tra cấu hình Inspector và cổng 9229.
Cách 3 — Debug client-side:
Khởi động ứng dụng như bình thường, mở website trong Chrome và nhấn F12. Chọn tab Sources, tìm file nguồn được ánh xạ từ source maps và đặt breakpoint. Browser Console dùng để kiểm tra lỗi JavaScript phía client.
Như vậy, ngay cả khi không sử dụng được giao diện debugger tích hợp trong IDE, bạn vẫn có thể debug bằng Node.js Inspector và Chrome DevTools.
11.7. Quy trình debug khi gặp lỗi
- Đọc thông báo lỗi đầu tiên và xác định file liên quan.
- Phân loại lỗi: compile, routing, server-side, client-side hay API.
- Đặt breakpoint tại dòng có khả năng gây lỗi.
- Chạy lại thao tác tạo ra lỗi.
- Quan sát giá trị biến, điều kiện và Call Stack.
- Sửa nguyên nhân, sau đó kiểm tra lại route và chạy
npm run build.
Debug hiệu quả không có nghĩa là sửa code thật nhanh. Mục tiêu là xác định được nguyên nhân và chứng minh rằng thay đổi đã khắc phục vấn đề.
Tài liệu tham khảo:
12. Tổng kết Day 1
Sau bài học này, bạn cần nhớ những điểm chính:
- Next.js là framework được xây dựng dựa trên React.
- App Router ánh xạ cấu trúc thư mục thành URL.
page.tsxđịnh nghĩa trang, cònlayout.tsxcung cấp cấu trúc giao diện dùng chung.- Server Components là mặc định trong App Router.
- Client Components được sử dụng khi cần tương tác phía trình duyệt.
- Thư mục
[slug]tạo dynamic route. - Trong ví dụ Next.js 16 này,
paramslà Promise và cần đượcawait. notFound()xử lý nội dung không tồn tại.- Dữ liệu mẫu có thể được thay bằng dữ liệu lấy từ backend API.
- Khi gặp cảnh báo bảo mật, cần xác định dependency và đánh giá tính tương thích trước khi sửa.
Những nội dung chưa học trong Day 1
- Chiến lược caching và revalidation.
- Metadata động theo từng bài viết.
- Xác thực và phân quyền giữa frontend và backend.
- Xử lý lỗi API và validation dữ liệu.
- Deployment và cấu hình production.
13. Bài tập thực hành
Hãy thử tự hoàn thành các bài tập sau mà không sao chép toàn bộ lời giải:
- Thêm một bài viết mới vào
posts.ts. - Tạo trang
/aboutvà thêm liên kết vào navigation. - Truy cập một slug không tồn tại và kiểm tra hành vi 404.
- Thêm Client Component có nút tăng số đếm.
- Chạy
npm run buildvà ghi lại kết quả. - Chạy cả hai lệnh audit và giải thích vì sao kết quả có thể khác nhau.
- Vẽ sơ đồ luồng request từ Next.js đến ASP.NET Core Web API.
Câu hỏi tự kiểm tra
Câu 1: Vì sao file src/app/posts/[slug]/page.tsx có thể xử lý nhiều bài viết mà không cần tạo một file riêng cho từng bài?
Câu 2: Vì sao đoạn code dưới đây không phù hợp với cách sử dụng params trong ví dụ Next.js 16 này?
export default function Page({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>;
}
Câu 3: Khi nào một component cần chỉ thị "use client"?
Câu 4: Vì sao không nên chạy npm audit fix --force mà chưa xem xét những thay đổi được đề xuất?
Câu 5: Nếu thay dữ liệu mẫu bằng ASP.NET Core Web API, phần routing nào của Next.js có thể giữ nguyên?
14. Kết luận
Day 1 tập trung vào những nền tảng quan trọng: làm quen với App Router, tạo trang và layout, phân biệt Server Components với Client Components, đồng thời xây dựng dynamic route cho một website blog nhỏ.
Phần xử lý npm audit cũng minh họa một kỹ năng cần thiết của lập trình viên: không chỉ làm cho ứng dụng chạy được mà còn phải hiểu dependency, đánh giá rủi ro và xử lý cảnh báo một cách có kiểm soát.
Sau khi nắm vững các kiến thức này, bạn đã có nền tảng để tiếp tục tìm hiểu cách lấy dữ liệu từ API, xử lý dữ liệu và xây dựng ứng dụng web hoàn chỉnh với Next.js.
Hoàn thành Day 1: Next.js 16 App Router, routing, layout, Server và Client Components, kiểm tra dependency và xây dựng blog chạy trên môi trường local.
Nhận xét