Series 5 – Next.js Fundamentals

 

Bài 1:Next.js, Project Structure và App Router

1. Mở đầu

Sau khi học React, chúng ta đã biết cách xây dựng giao diện bằng Component, truyền dữ liệu qua Props và quản lý trạng thái bằng State.

Tuy nhiên, một ứng dụng web thực tế thường có nhiều trang, nhiều loại dữ liệu và yêu cầu về hiệu năng, SEO cũng như khả năng triển khai. Nếu chỉ tập trung vào giao diện, chúng ta vẫn cần giải quyết những vấn đề như định tuyến, tổ chức thư mục và cách xử lý yêu cầu từ người dùng.

Next.js cung cấp một framework dựa trên React để giúp tổ chức các phần này. Để bắt đầu, chúng ta cần hiểu ba từ khóa: Next.js, Project Structure và App Router.

2. Next.js là gì?

Next.js là framework React dùng để xây dựng ứng dụng web. Framework này bổ sung nhiều quy ước và tính năng cho việc phát triển ứng dụng, bao gồm routing, các phương thức render, lấy dữ liệu, quản lý cache và hỗ trợ metadata.

Có thể phân biệt đơn giản:

  • React: thư viện dùng để xây dựng giao diện người dùng.
  • Next.js: framework dựa trên React, cung cấp thêm cấu trúc và các cơ chế phục vụ ứng dụng web.

Ví dụ, trong React kết hợp với Vite, bạn thường cần lựa chọn và thiết lập giải pháp routing nếu ứng dụng có nhiều trang.

Với Next.js App Router, hệ thống định tuyến được tổ chức dựa trên cấu trúc thư mục và các file quy ước như page.tsx và layout.tsx.

2.1. Khi nào nên sử dụng Next.js?

Next.js phù hợp với những dự án như:

  • Website giới thiệu doanh nghiệp.
  • Blog và website tin tức.
  • Website thương mại điện tử.
  • Dashboard quản lý.
  • Ứng dụng web có nhiều trang và yêu cầu về render phía server.

Tuy nhiên, không phải dự án nào cũng bắt buộc sử dụng Next.js. Với một ứng dụng nhỏ, chủ yếu chạy phía trình duyệt, React kết hợp với Vite vẫn có thể là lựa chọn phù hợp.

Việc lựa chọn công nghệ nên dựa trên yêu cầu của dự án, độ phức tạp và cách ứng dụng cần hoạt động.

2.2. Khởi tạo dự án Next.js

Bạn có thể tạo một dự án Next.js sử dụng TypeScript, Tailwind CSS, ESLint và App Router bằng lệnh:

npx create-next-app@latest frontend --typescript --tailwind --eslint --app --src-dir

Trong đó:

  • frontend: tên thư mục dự án.
  • --typescript: sử dụng TypeScript.
  • --tailwind: thiết lập Tailwind CSS.
  • --eslint: thiết lập ESLint.
  • --app: sử dụng App Router.
  • --src-dir: đặt mã nguồn trong thư mục src/.

Các tùy chọn của trình khởi tạo có thể thay đổi theo phiên bản. Nếu được hỏi thêm, hãy chọn những tùy chọn phù hợp với cấu hình dự án của bạn.

3. Project Structure là gì?

Project Structure là cách tổ chức các thư mục và file trong một dự án.

Một cấu trúc tốt giúp lập trình viên tìm kiếm code nhanh hơn, phân chia trách nhiệm rõ ràng và dễ bảo trì khi ứng dụng phát triển.

Với Next.js sử dụng App Router và thư mục src/, cấu trúc cơ bản có thể như sau:

frontend/
├── public/
│   ├── images/
│   └── favicon.ico
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── globals.css
│   │   ├── about/
│   │   │   └── page.tsx
│   │   └── products/
│   │       └── page.tsx
│   ├── components/
│   │   ├── Header.tsx
│   │   └── Footer.tsx
│   └── lib/
├── package.json
├── tsconfig.json
└── next.config.ts

Đây là cấu trúc minh họa. Một số file có thể khác hoặc chưa xuất hiện tùy phiên bản và cách thiết lập dự án.

3.1. Thư mục public/

Thư mục public/ chứa các tài nguyên tĩnh có thể được truy cập trực tiếp bằng URL.

Ví dụ, nếu có file:

public/images/logo.png

Bạn có thể tham chiếu file đó bằng đường dẫn:

<img src="/images/logo.png" alt="Logo" />

Trong ứng dụng Next.js, next/image thường là lựa chọn hữu ích để hiển thị hình ảnh với các tính năng tối ưu hóa phù hợp.

3.2. Thư mục src/app/

Đây là nơi thường chứa các route, layout và các file liên quan đến App Router.

Ví dụ:

src/app/
├── layout.tsx
├── page.tsx
├── about/
│   └── page.tsx
└── products/
    └── page.tsx

Trong cấu trúc này:

  • layout.tsx định nghĩa layout gốc.
  • page.tsx ở cấp app/ là trang chủ.
  • about/page.tsx là trang giới thiệu.
  • products/page.tsx là trang sản phẩm.

3.3. Thư mục components/

Thư mục components/ thường được dùng để lưu những Component có thể tái sử dụng.

Ví dụ:

src/components/
├── Header.tsx
├── Footer.tsx
├── Button.tsx
└── ProductCard.tsx

Next.js không bắt buộc phải có thư mục này. Bạn có thể tổ chức Component theo cấu trúc phù hợp với dự án, chẳng hạn đặt Component dùng riêng cho một route gần route đó và những Component dùng chung trong thư mục components/.

3.4. Thư mục lib/

lib/ thường dùng để lưu các hàm tiện ích, cấu hình hoặc logic dùng chung, chẳng hạn hàm định dạng tiền, cấu hình kết nối dịch vụ hoặc hàm truy vấn dữ liệu.

Đây cũng là một quy ước tổ chức dự án chứ không phải thư mục bắt buộc của Next.js.

4. App Router là gì?

App Router là hệ thống định tuyến của Next.js dựa trên thư mục app/.

Với App Router, các thư mục giúp tổ chức đường dẫn, còn những file đặc biệt như page.tsx xác định giao diện có thể truy cập qua URL.

Ví dụ:

src/app/
├── page.tsx
├── about/
│   └── page.tsx
└── contact/
    └── page.tsx

Cấu trúc này tương ứng với các route:

File URL
src/app/page.tsx /
src/app/about/page.tsx /about
src/app/contact/page.tsx /contact

Lưu ý rằng tên thư mục tạo nên đoạn đường dẫn, nhưng không phải mọi thư mục trong app/ đều tự động trở thành một trang. Một route cần có file quy ước phù hợp, chẳng hạn page.tsx, để cung cấp giao diện trang.

4.1. Tạo trang chủ bằng page.tsx

Ví dụ file src/app/page.tsx:

export default function HomePage() {
    return (
        <main>
            <h1>Trang chủ</h1>
            <p>Chào mừng bạn đến với website.</p>
        </main>
    );
}

Hàm HomePage là một React Component. Next.js sử dụng file page.tsx để xác định nội dung của route tương ứng.

Trong App Router, Page mặc định là Server Component, trừ khi bạn chỉ định "use client" ở đầu file hoặc sử dụng một Component Client phù hợp.

4.2. Tạo trang giới thiệu

Tạo file src/app/about/page.tsx:

export default function AboutPage() {
    return (
        <main>
            <h1>Giới thiệu</h1>
            <p>Đây là trang giới thiệu website của chúng tôi.</p>
        </main>
    );
}

Sau khi chạy ứng dụng, trang này có thể được truy cập tại /about.

Tương tự, nếu muốn tạo trang liên hệ, bạn có thể tạo thư mục contact/ và đặt file page.tsx bên trong.

4.3. Layout là gì trong App Router?

Ngoài page.tsx, Next.js còn hỗ trợ layout.tsx để tạo giao diện dùng chung cho các trang trong một phạm vi route.

Ví dụ file src/app/layout.tsx:

import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
    title: "My Website",
    description: "Website được xây dựng bằng Next.js",
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="vi">
            <body>{children}</body>
        </html>
    );
}

Ở đây, children đại diện cho nội dung của các route nằm bên trong layout.

Khi người dùng truy cập trang chủ hoặc trang giới thiệu, nội dung trang tương ứng sẽ được hiển thị bên trong RootLayout.

Điều này giúp bạn thiết lập cấu trúc chung như Header, Footer hoặc các thành phần bao quanh nội dung mà không cần sao chép chúng vào từng trang.

5. Next.js xử lý route như thế nào?

Có thể hình dung quy trình cơ bản như sau:

  1. Người dùng truy cập một URL, ví dụ /about.
  2. Next.js xác định route dựa trên cấu trúc App Router.
  3. Next.js sử dụng Page và các Layout liên quan để xây dựng giao diện.
  4. Ứng dụng xử lý dữ liệu và render theo cách phù hợp với cấu hình.
  5. Nội dung được trả về cho trình duyệt.

Đây là mô hình tổng quát. Cách xử lý cụ thể sẽ phụ thuộc vào việc trang sử dụng Server Components, Client Components, dữ liệu động, cache và các tùy chọn render nào.

App Router không chỉ là cách tạo URL. Nó còn là nền tảng để tổ chức giao diện và xử lý các tính năng khác của Next.js.

6. Những lưu ý khi tổ chức dự án

Không đặt toàn bộ code vào một file. Khi ứng dụng lớn dần, hãy chia Component, logic dữ liệu và giao diện theo chức năng.

Không nhầm thư mục với route hoàn chỉnh. Một thư mục thông thường trong app/ không tự động trở thành một trang nếu thiếu file định nghĩa route tương ứng.

Không lạm dụng Client Components. Trong App Router, Server Components là mặc định. Chỉ sử dụng "use client" khi cần những tính năng phía trình duyệt như state, event handlers hoặc các React Hooks chỉ dành cho Client Components.

Không coi cấu trúc thư mục minh họa là quy tắc bắt buộc. Next.js có một số file và quy ước đặc biệt, nhưng cách tổ chức thư mục components/, lib/ và các thư mục phụ khác vẫn có thể thay đổi tùy dự án.

7. Bài tập thực hành

Hãy tạo một dự án Next.js có cấu trúc như sau:

src/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── about/
│   │   └── page.tsx
│   └── products/
│       └── page.tsx
└── components/
    ├── Header.tsx
    └── Footer.tsx

Yêu cầu:

  1. Tạo trang chủ, trang giới thiệu và trang sản phẩm.
  2. Tạo Component Header và Footer.
  3. Hiển thị Header và Footer trong layout dùng chung.
  4. Tạo nội dung khác nhau cho mỗi trang.
  5. Giải thích được file nào tạo route /, /about và /products.
  6. Chạy dự án và kiểm tra từng URL trên trình duyệt.

8. Kết luận

Next.js cung cấp framework dựa trên React để xây dựng ứng dụng web có cấu trúc rõ ràng. Project Structure giúp tổ chức code, còn App Router giúp quản lý các trang dựa trên thư mục và file quy ước.

Khi hiểu được ba khái niệm này, bạn đã có nền tảng để tiếp tục học về Dynamic Routes, Nested Routes, Navigation và Layouts trong những bài tiếp theo.

Bài tiếp theo: Static Routes, Dynamic Routes và Nested Routes – Xây dựng hệ thống định tuyến trong Next.js App Router.

 

Bài 2: Static Routes, Dynamic Routes và Nested Routes – Cách tổ chức đường dẫn trong Next.js

1. Mở đầu

Khi xây dựng một website, chúng ta thường cần nhiều trang khác nhau như trang chủ, trang giới thiệu, danh sách sản phẩm và trang chi tiết sản phẩm.

Ví dụ, một website bán hàng có thể sử dụng các đường dẫn:

  • / — Trang chủ.
  • /about — Trang giới thiệu.
  • /products — Danh sách sản phẩm.
  • /products/iphone-15 — Chi tiết sản phẩm iPhone 15.
  • /products/iphone-15/reviews — Đánh giá của sản phẩm.

Nếu sử dụng Next.js App Router, chúng ta không cần tự khai báo từng đường dẫn theo cách thủ công. Next.js sử dụng cấu trúc thư mục bên trong app để xác định các route.

Trong bài viết này, chúng ta sẽ tìm hiểu ba khái niệm: Static Routes, Dynamic Routes và Nested Routes.

2. Static Routes là gì?

Static Route là đường dẫn có cấu trúc cố định, được xác định thông qua tên thư mục trong app.

Ví dụ, để tạo trang giới thiệu có đường dẫn /about, chúng ta tổ chức thư mục như sau:

src/
└── app/
    ├── page.tsx
    └── about/
        └── page.tsx

Trong đó:

  • app/page.tsx tương ứng với /.
  • app/about/page.tsx tương ứng với /about.

Nội dung file src/app/about/page.tsx:

export default function AboutPage() {
  return (
    <main>
      <h1>Giới thiệu</h1>
      <p>Đây là trang giới thiệu của website.</p>
    </main>
  );
}

Khi người dùng truy cập /about, Next.js tìm route tương ứng và hiển thị component AboutPage.

Một số ví dụ khác

File URL
app/page.tsx /
app/about/page.tsx /about
app/contact/page.tsx /contact
app/products/page.tsx /products

Điểm quan trọng là thư mục giúp xác định đường dẫn, còn file page.tsx xác định giao diện của route đó.

Lưu ý: Tên gọi Static Route nói về cấu trúc đường dẫn cố định, không có nghĩa trang đó nhất thiết phải được render tĩnh. Static Route và Static Rendering là hai khái niệm khác nhau.

3. Dynamic Routes là gì?

Dynamic Route là route có một hoặc nhiều phần đường dẫn thay đổi theo dữ liệu.

Ví dụ, website bán hàng có hàng nghìn sản phẩm. Thay vì tạo riêng một thư mục cho từng sản phẩm, chúng ta có thể sử dụng một route động:

/products/iphone-15
/products/samsung-s24
/products/macbook-air

Cấu trúc thư mục:

src/
└── app/
    └── products/
        ├── page.tsx
        └── [id]/
            └── page.tsx

Thư mục [id] là một dynamic segment. Nó cho phép Next.js nhận các giá trị khác nhau từ URL và chuyển chúng vào route tương ứng.

Lấy tham số từ URL

Ví dụ với Next.js App Router hiện đại, nơi params được sử dụng dưới dạng Promise:

type ProductPageProps = {
  params: Promise<{
    id: string;
  }>;
};

export default async function ProductPage({
  params,
}: ProductPageProps) {
  const { id } = await params;

  return (
    <main>
      <h1>Chi tiết sản phẩm</h1>
      <p>Mã sản phẩm: {id}</p>
    </main>
  );
}

Khi truy cập /products/iphone-15, giá trị id sẽ là "iphone-15".

Khi truy cập /products/macbook-air, giá trị id sẽ là "macbook-air".

Một component có thể xử lý nhiều đường dẫn khác nhau mà không cần tạo nhiều file page.tsx.

Nếu sử dụng phiên bản Next.js cũ hơn, cách khai báo và lấy params có thể khác. Vì vậy, hãy đối chiếu với phiên bản dự án đang sử dụng.

Dynamic Route có thể dùng để làm gì?

  • Trang chi tiết sản phẩm.
  • Trang hồ sơ người dùng.
  • Trang chi tiết bài viết.
  • Trang chi tiết đơn hàng.
  • Trang chi tiết phim trong website xem phim.

Ví dụ, với website xem phim, thư mục [slug] có thể nhận các đường dẫn như /movies/avengers hoặc /movies/interstellar.

Lưu ý rằng nhận được id từ URL không có nghĩa là đã có dữ liệu sản phẩm. Trong dự án thực tế, chúng ta còn cần truy vấn dữ liệu từ API, database hoặc nguồn dữ liệu phù hợp.

4. Nested Routes là gì?

Nested Route là các route được tổ chức theo nhiều cấp, trong đó một route nằm bên trong cấu trúc của route khác.

Ví dụ, một website thương mại điện tử có các trang:

  • /products — Danh sách sản phẩm.
  • /products/iphone-15 — Chi tiết sản phẩm.
  • /products/iphone-15/reviews — Đánh giá của sản phẩm.

Cấu trúc thư mục:

src/
└── app/
    └── products/
        ├── page.tsx
        └── [id]/
            ├── page.tsx
            └── reviews/
                └── page.tsx

Các đường dẫn tương ứng:

File URL
products/page.tsx /products
products/[id]/page.tsx /products/:id
products/[id]/reviews/page.tsx /products/:id/reviews

Ký hiệu :id trong bảng chỉ là cách viết minh họa cho một giá trị động. Trong cấu trúc thư mục Next.js, chúng ta sử dụng [id].

Tạo trang đánh giá sản phẩm

File src/app/products/[id]/reviews/page.tsx:

type ReviewsPageProps = {
  params: Promise<{
    id: string;
  }>;
};

export default async function ReviewsPage({
  params,
}: ReviewsPageProps) {
  const { id } = await params;

  return (
    <main>
      <h1>Đánh giá sản phẩm</h1>
      <p>Sản phẩm: {id}</p>
    </main>
  );
}

Khi truy cập /products/iphone-15/reviews, trang sẽ nhận id là "iphone-15" và hiển thị nội dung tương ứng.

Nested Route có đồng nghĩa với Nested Layout không?

Không hoàn toàn.

Nested Route thể hiện cấu trúc đường dẫn nhiều cấp. Còn layout.tsx cho phép chia sẻ giao diện giữa các trang trong cùng một nhánh route.

Ví dụ:

app/
└── products/
    ├── layout.tsx
    ├── page.tsx
    └── [id]/
        └── page.tsx

products/layout.tsx có thể chứa thanh điều hướng hoặc phần giao diện chung cho các trang sản phẩm.

export default function ProductsLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <section>
      <nav>Danh mục sản phẩm</nav>
      {children}
    </section>
  );
}

Ở đây, children là nội dung của route con được render bên trong layout.

Nhờ đó, các trang sản phẩm có thể dùng chung một phần giao diện thay vì viết lại ở từng trang.

5. So sánh ba khái niệm

Tiêu chí Static Routes Dynamic Routes Nested Routes
Đặc điểm Đường dẫn cố định Có phần đường dẫn thay đổi Đường dẫn nhiều cấp
Ví dụ /about /products/iphone-15 /products/iphone-15/reviews
Cấu trúc thư mục about/page.tsx products/[id]/page.tsx products/[id]/reviews/page.tsx
Trường hợp sử dụng Giới thiệu, liên hệ Chi tiết sản phẩm, bài viết Danh mục con, đánh giá, trang con

Ba khái niệm này không loại trừ lẫn nhau. Một route có thể vừa chứa dynamic segment vừa nằm trong cấu trúc nested route.

6. Một số lưu ý khi làm việc với Next.js App Router

6.1. Không phải thư mục nào cũng tạo ra một trang

Một thư mục chỉ xác định route có thể tồn tại. Để tạo một trang có thể truy cập trực tiếp, thông thường cần có file page.tsx trong thư mục đó.

Ví dụ:

app/
├── about/
│   └── page.tsx
└── components/
    └── Header.tsx

/about là route vì có page.tsx. Thư mục components ở đây chỉ chứa component dùng chung, không tự tạo ra route /components.

6.2. Tránh tạo route trùng nhau

Không nên tổ chức nhiều file khác nhau cùng ánh xạ đến một URL. Hãy kiểm tra cấu trúc thư mục khi gặp lỗi route hoặc build.

6.3. Phân biệt URL với tên file

Tên page.tsx là quy ước của Next.js để xác định trang. URL được hình thành từ các segment thư mục, chứ không phải từ tên file page.tsx.

6.4. Sử dụng Link để điều hướng

Trong Next.js, có thể sử dụng component Link để chuyển giữa các trang:

import Link from "next/link";

export default function HomePage() {
  return (
    <nav>
      <Link href="/about">Giới thiệu</Link>
      <Link href="/products">Sản phẩm</Link>
    </nav>
  );
}

Cách này phù hợp với điều hướng nội bộ của ứng dụng Next.js.

7. Bài tập thực hành

Hãy xây dựng cấu trúc route cho một website xem phim với các yêu cầu:

  1. / — Trang chủ.
  2. /movies — Danh sách phim.
  3. /movies/avengers — Chi tiết phim Avengers.
  4. /movies/interstellar — Chi tiết phim Interstellar.
  5. /movies/avengers/reviews — Trang đánh giá phim Avengers.

Yêu cầu thực hiện:

  • Tạo đúng cấu trúc thư mục trong src/app.
  • Sử dụng [slug] để xử lý route chi tiết phim.
  • Lấy slug từ params và hiển thị lên giao diện.
  • Sử dụng Link để điều hướng từ danh sách phim đến trang chi tiết.
  • Tạo trang đánh giá nằm bên trong route chi tiết phim.

8. Kết luận

Static Routes, Dynamic Routes và Nested Routes giúp tổ chức đường dẫn rõ ràng và có khả năng mở rộng trong Next.js App Router.

  • Static Routes phù hợp với những đường dẫn cố định.
  • Dynamic Routes giúp xử lý nhiều tài nguyên bằng một cấu trúc route.
  • Nested Routes giúp tổ chức những trang có quan hệ phân cấp.

Khi đã nắm được cách tổ chức route, bước tiếp theo là tìm hiểu Route Groups, Catch-all Routes và Navigation để xây dựng hệ thống điều hướng linh hoạt hơn cho các dự án thực tế.

Bài viết khác

Positive vs Negative Testing(Kiểm thử tích cực và Kiểm thử tiêu cực)

1.Positive vs Negative Testing là gì? Trong ngành công nghệ phần mềm, chất lượng sản phẩm không bao giờ là một khái niệm đơn chiều. Một ứng dụng được lập trình hoàn hảo trên lý thuyết vẫn có thể sụp đổ hoàn toàn ngay trong ngày đầu tiên phát hành nếu nó chỉ được chuẩn […]

Eloquent ORM

Eloquent ORM là gì? Hướng dẫn sử dụng Eloquent trong Laravel 1. Eloquent ORM là gì? Eloquent ORM là hệ thống Object-Relational Mapping (ORM) được tích hợp trong Laravel, một framework phát triển ứng dụng Web bằng PHP. Eloquent cho phép lập trình viên tương tác với cơ sở dữ liệu thông qua các class […]

CAP theorem

1. CAP Theorem là gì? CAP Theorem là một định lý nền tảng trong Distributed Systems, giải thích giới hạn của việc đồng thời đảm bảo ba thuộc tính: Consistency (C), Availability (A) và Partition Tolerance (P) trong một hệ thống dữ liệu phân tán. Định lý bắt nguồn từ nhận định của Eric Brewer […]

CAP Theorem

CAP Theorem là gì? Tìm hiểu Consistency, Availability và Partition Tolerance 1. CAP Theorem là gì? CAP Theorem (định lý CAP) là một nguyên lý quan trọng trong thiết kế hệ thống phân tán, mô tả sự đánh đổi giữa tính nhất quán (Consistency) và tính sẵn sàng (Availability) khi xảy ra phân vùng mạng […]

Saga Pattern

Saga Pattern là gì? Quản lý Transaction trong kiến trúc Microservices 1. Saga Pattern là gì? Saga Pattern là một mẫu thiết kế được sử dụng để quản lý các nghiệp vụ phân tán gồm nhiều bước xử lý, trong đó mỗi bước có thể thực hiện một local transaction tại một service hoặc Database […]

Two-Phase Locking (2PL)

Two-Phase Locking (2PL) là gì? Cơ chế khóa hai pha trong Database 1. Two-Phase Locking (2PL) là gì? Two-Phase Locking (2PL — khóa hai pha) là một giao thức quản lý khóa trong Database, quy định cách một transaction được phép lấy và giải phóng lock trong quá trình thực thi. Khi nhiều transaction cùng […]

Leave a Reply

Your email address will not be published. Required fields are marked *