Tiếng Việt

Mở khóa giao diện người dùng linh hoạt và có khả năng mở rộng trong Next.js. Hướng dẫn toàn diện của chúng tôi bao gồm Route Groups để tổ chức và Parallel Routes cho các dashboard phức tạp. Nâng cao kỹ năng ngay!

Làm chủ Next.js App Router: Phân tích chuyên sâu về kiến trúc Route Groups và Parallel Routes

Sự ra mắt của Next.js App Router đã đánh dấu một sự thay đổi mô hình trong cách các nhà phát triển xây dựng ứng dụng web với framework React nổi tiếng. Chuyển đổi khỏi các quy ước dựa trên tệp của Pages Router, App Router đã giới thiệu một mô hình mạnh mẽ, linh hoạt và lấy máy chủ làm trung tâm hơn. Sự phát triển này cho phép chúng ta tạo ra các giao diện người dùng có hiệu suất cao và cực kỳ phức tạp với khả năng kiểm soát và tổ chức tốt hơn. Trong số các tính năng mang tính chuyển đổi được giới thiệu, nổi bật nhất là Route GroupsParallel Routes.

Đối với các nhà phát triển muốn xây dựng các ứng dụng cấp doanh nghiệp, việc thành thạo hai khái niệm này không chỉ có lợi—mà còn là điều cần thiết. Chúng giải quyết các thách thức kiến trúc phổ biến liên quan đến quản lý layout, tổ chức route, và việc tạo ra các giao diện động, đa bảng điều khiển như dashboard. Hướng dẫn này cung cấp một khám phá toàn diện về Route Groups và Parallel Routes, đi từ các khái niệm nền tảng đến các chiến lược triển khai nâng cao và các phương pháp hay nhất cho cộng đồng nhà phát triển toàn cầu.

Tìm hiểu về Next.js App Router: Ôn tập nhanh

Trước khi chúng ta đi sâu vào chi tiết, hãy cùng ôn lại ngắn gọn các nguyên tắc cốt lõi của App Router. Kiến trúc của nó được xây dựng trên một hệ thống dựa trên thư mục, nơi các thư mục xác định các phân đoạn URL. Các tệp đặc biệt trong các thư mục này xác định giao diện người dùng và hành vi cho phân đoạn đó:

Cấu trúc này, kết hợp với việc sử dụng mặc định các React Server Components (RSC), khuyến khích một phương pháp tiếp cận ưu tiên máy chủ (server-first) có thể cải thiện đáng kể hiệu suất và các mẫu tìm nạp dữ liệu. Route Groups và Parallel Routes là các quy ước nâng cao được xây dựng trên nền tảng này.

Giải mã Route Groups: Tổ chức dự án của bạn để dễ quản lý và mở rộng

Khi một ứng dụng phát triển, số lượng route có thể trở nên khó quản lý. Bạn có thể có một bộ các trang cho marketing, một bộ khác cho xác thực người dùng, và một bộ thứ ba cho dashboard ứng dụng cốt lõi. Về mặt logic, đây là các phần riêng biệt, nhưng làm thế nào để bạn tổ chức chúng trong hệ thống tệp của mình mà không làm lộn xộn URL? Đây chính xác là vấn đề mà Route Groups giải quyết.

Route Groups là gì?

Một Route Group là một cơ chế để tổ chức các tệp và phân đoạn route của bạn thành các nhóm logic mà không ảnh hưởng đến cấu trúc URL. Bạn tạo một route group bằng cách đặt tên thư mục trong dấu ngoặc đơn, ví dụ: (marketing) hoặc (app).

Tên thư mục trong dấu ngoặc đơn hoàn toàn chỉ dành cho mục đích tổ chức. Next.js hoàn toàn bỏ qua nó khi xác định đường dẫn URL. Ví dụ, tệp tại app/(marketing)/about/page.js sẽ được phục vụ tại URL /about, chứ không phải /(marketing)/about.

Các trường hợp sử dụng chính và lợi ích của Route Groups

Mặc dù tổ chức đơn giản là một lợi ích, sức mạnh thực sự của Route Groups nằm ở khả năng phân chia ứng dụng của bạn thành các phần với các layout chung, riêng biệt.

1. Tạo các Layout khác nhau cho các phân đoạn Route

Đây là trường hợp sử dụng phổ biến và mạnh mẽ nhất. Hãy tưởng tượng một ứng dụng web có hai phần chính:

Nếu không có Route Groups, việc áp dụng các root layout khác nhau cho các phần này sẽ rất phức tạp. Với Route Groups, điều đó trở nên cực kỳ trực quan. Bạn có thể tạo một tệp layout.js duy nhất bên trong mỗi nhóm.

Đây là một cấu trúc tệp điển hình cho kịch bản này:

app/
├── (marketing)/
│   ├── layout.js      // Layout công khai với header/footer của marketing
│   ├── page.js        // Hiển thị tại '/'
│   └── about/
│       └── page.js    // Hiển thị tại '/about'
├── (app)/
│   ├── layout.js      // Layout dashboard với thanh bên
│   ├── dashboard/
│   │   └── page.js    // Hiển thị tại '/dashboard'
│   └── settings/
│       └── page.js    // Hiển thị tại '/settings'
└── layout.js          // Root layout (ví dụ, cho <html> và <body> tags)

Trong kiến trúc này:

2. Chọn không tham gia một Layout chung cho một phân đoạn

Đôi khi, một trang hoặc một phần cụ thể cần thoát hoàn toàn khỏi layout cha. Một ví dụ phổ biến là quy trình thanh toán hoặc một trang đích đặc biệt không nên có thanh điều hướng của trang web chính. Bạn có thể đạt được điều này bằng cách đặt route vào một nhóm không chia sẻ layout cấp cao hơn. Mặc dù điều này nghe có vẻ phức tạp, nó chỉ đơn giản có nghĩa là cung cấp cho một route group một tệp layout.js cấp cao nhất của riêng nó mà không render `children` từ root layout.

Ví dụ thực tế: Xây dựng một ứng dụng đa Layout

Hãy xây dựng một phiên bản tối thiểu của cấu trúc marketing/app đã mô tả ở trên.

1. Root Layout (app/layout.js)

Layout này là tối thiểu và áp dụng cho mọi trang. Nó định nghĩa cấu trúc HTML thiết yếu.

// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

2. Layout Marketing (app/(marketing)/layout.js)

Layout này bao gồm một header và footer dành cho trang công khai.

// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
  return (
    <div>
      <header>Marketing Header</header>
      <main>{children}</main>
      <footer>Marketing Footer</footer>
    </div>
  );
}

3. Layout Dashboard Ứng dụng (app/(app)/layout.js)

Layout này có một cấu trúc khác, với một thanh bên cho người dùng đã xác thực.

// app/(app)/layout.js
export default function AppLayout({ children }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', borderRight: '1px solid #ccc' }}>
        Dashboard Sidebar
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>{children}</main>
    </div>
  );
}

Với cấu trúc này, việc điều hướng đến /about sẽ render trang với `MarketingLayout`, trong khi điều hướng đến /dashboard sẽ render nó với `AppLayout`. URL vẫn sạch sẽ và có ngữ nghĩa, trong khi cấu trúc tệp của dự án được tổ chức hoàn hảo và có khả năng mở rộng.

Mở khóa giao diện người dùng động với Parallel Routes

Trong khi Route Groups giúp tổ chức các phần riêng biệt của một ứng dụng, Parallel Routes giải quyết một thách thức khác: hiển thị nhiều chế độ xem trang độc lập trong một layout duy nhất. Đây là một yêu cầu phổ biến đối với các dashboard phức tạp, các trang tin tức mạng xã hội, hoặc bất kỳ giao diện người dùng nào mà các bảng điều khiển khác nhau cần được render và quản lý đồng thời.

Parallel Routes là gì?

Parallel Routes cho phép bạn render đồng thời một hoặc nhiều trang trong cùng một layout. Các route này được định nghĩa bằng cách sử dụng một quy ước thư mục đặc biệt gọi là slots. Slots được tạo bằng cú pháp @folderName. Chúng không phải là một phần của cấu trúc URL; thay vào đó, chúng được tự động truyền dưới dạng props đến tệp `layout.js` cha chung gần nhất.

Ví dụ, nếu bạn có một layout cần hiển thị một bảng tin hoạt động của nhóm và một biểu đồ phân tích cạnh nhau, bạn có thể định nghĩa hai slot: `@team` và `@analytics`.

Ý tưởng cốt lõi: Slots

Hãy nghĩ về slots như các trình giữ chỗ (placeholder) được đặt tên trong layout của bạn. Tệp layout chấp nhận các slot này một cách tường minh dưới dạng props và quyết định nơi để render chúng.

Hãy xem xét thành phần layout này:

// Một layout chấp nhận hai slot: 'team' và 'analytics'
export default function DashboardLayout({ children, team, analytics }) {
  return (
    <div>
      {children}
      <div style={{ display: 'flex' }}>
        {team}
        {analytics}
      </div>
    </div>
  );
}

Ở đây, `children`, `team`, và `analytics` đều là các slot. `children` là một slot ngầm định tương ứng với tệp `page.js` tiêu chuẩn trong thư mục. `team` và `analytics` là các slot tường minh phải được tạo với tiền tố `@` trong hệ thống tệp.

Các tính năng và lợi ích chính

Kịch bản thực tế: Xây dựng một Dashboard phức tạp

Hãy thiết kế một dashboard tại URL /dashboard. Nó sẽ có một khu vực nội dung chính, một bảng điều khiển hoạt động của nhóm, và một bảng điều khiển phân tích hiệu suất.

Cấu trúc tệp:

app/
└── dashboard/
    ├── @analytics/
    │   ├── page.js          // UI cho slot analytics
    │   └── loading.js     // UI tải dành riêng cho analytics
    ├── @team/
    │   └── page.js          // UI cho slot team
    ├── layout.js            // Layout điều phối các slot
    └── page.js              // Slot 'children' ngầm định (nội dung chính)

1. Layout Dashboard (app/dashboard/layout.js)

Layout này nhận và sắp xếp ba slot.

// app/dashboard/layout.js
export default function DashboardLayout({ children, analytics, team }) {
  const isLoggedIn = true; // Thay thế bằng logic xác thực thực tế

  return isLoggedIn ? (
    <div>
      <h1>Main Dashboard</h1>
      {children}
      <div style={{ marginTop: '20px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
        <div style={{ border: '1px solid blue', padding: '10px' }}>
          <h2>Team Activity</h2>
          {team}
        </div>
        <div style={{ border: '1px solid green', padding: '10px' }}>
          <h2>Performance Analytics</h2>
          {analytics}
        </div>
      </div>
    </div>
  ) : (
    <div>Please log in to view the dashboard.</div>
  );
}

2. Các trang Slot (ví dụ: app/dashboard/@analytics/page.js)

Tệp `page.js` của mỗi slot chứa UI cho bảng điều khiển cụ thể đó.

// app/dashboard/@analytics/page.js
async function getAnalyticsData() {
  // Mô phỏng một yêu cầu mạng
  await new Promise(resolve => setTimeout(resolve, 3000));
  return { views: '1.2M', revenue: '$50,000' };
}

export default async function AnalyticsPage() {
  const data = await getAnalyticsData();
  return (
    <div>
      <p>Page Views: {data.views}</p>
      <p>Revenue: {data.revenue}</p>
    </div>
  );
}

// app/dashboard/@analytics/loading.js
export default function Loading() {
  return <p>Loading analytics data...</p>;
}

Với thiết lập này, khi người dùng điều hướng đến /dashboard, Next.js sẽ render `DashboardLayout`. Layout sẽ nhận nội dung đã render từ dashboard/page.js, dashboard/@team/page.js, và dashboard/@analytics/page.js dưới dạng props và đặt chúng vào vị trí tương ứng. Quan trọng là, bảng phân tích sẽ hiển thị trạng thái `loading.js` của riêng nó trong 3 giây mà không chặn việc render phần còn lại của dashboard.

Xử lý các Route không khớp với `default.js`

Một câu hỏi quan trọng nảy sinh: Điều gì xảy ra nếu Next.js không thể lấy trạng thái hoạt động của một slot cho URL hiện tại? Ví dụ, trong lần tải đầu tiên hoặc khi tải lại trang, URL có thể là /dashboard, không cung cấp hướng dẫn cụ thể về những gì cần hiển thị bên trong các slot @team hoặc `@analytics`. Theo mặc định, Next.js sẽ render lỗi 404.

Để ngăn chặn điều này, chúng ta có thể cung cấp một giao diện người dùng dự phòng (fallback UI) bằng cách tạo một tệp default.js bên trong parallel route.

Ví dụ:

// app/dashboard/@analytics/default.js
export default function DefaultAnalyticsPage() {
  return (
    <div>
      <p>No analytics data selected.</p>
    </div>
  );
}

Bây giờ, nếu slot analytics không khớp, Next.js sẽ render nội dung của `default.js` thay vì một trang 404. Điều này là cần thiết để tạo ra một trải nghiệm người dùng mượt mà, đặc biệt là khi tải lần đầu một thiết lập parallel route phức tạp.

Kết hợp Route Groups và Parallel Routes cho các kiến trúc nâng cao

Sức mạnh thực sự của App Router được nhận ra khi bạn kết hợp các tính năng của nó. Route Groups và Parallel Routes hoạt động tuyệt vời cùng nhau để tạo ra các kiến trúc ứng dụng tinh vi và được tổ chức cao.

Trường hợp sử dụng: Trình xem nội dung đa phương thức (Multi-Modal)

Hãy tưởng tượng một nền tảng như một thư viện media hoặc một trình xem tài liệu nơi người dùng có thể xem một mục nhưng cũng có thể mở một cửa sổ modal để xem chi tiết của nó mà không mất bối cảnh của trang nền. Điều này thường được gọi là "Intercepting Route" (Route chặn) và là một mẫu mạnh mẽ được xây dựng trên parallel routes.

Hãy tạo một thư viện ảnh. Khi bạn nhấp vào một bức ảnh, nó sẽ mở ra trong một modal. Nhưng nếu bạn làm mới trang hoặc điều hướng trực tiếp đến URL của bức ảnh, nó sẽ hiển thị một trang riêng cho bức ảnh đó.

Cấu trúc tệp:

app/
├── @modal/(..)(..)photos/[id]/page.js  // Route bị chặn cho modal
├── photos/
│   └── [id]/
│       └── page.js                  // Trang ảnh chuyên dụng
├── layout.js                        // Root layout nhận slot @modal
└── page.js                          // Trang thư viện chính

Giải thích:

Mẫu này kết hợp parallel routes (slot `@modal`) với các quy ước định tuyến nâng cao để tạo ra một trải nghiệm người dùng liền mạch mà sẽ rất phức tạp để triển khai thủ công.

Các phương pháp hay nhất và những cạm bẫy thường gặp

Các phương pháp hay nhất cho Route Groups

Các phương pháp hay nhất cho Parallel Routes

Những cạm bẫy thường gặp cần tránh

Kết luận: Xây dựng tương lai của ứng dụng web

Next.js App Router, với các tính năng như Route Groups và Parallel Routes, cung cấp một nền tảng vững chắc và có khả năng mở rộng cho phát triển web hiện đại. Route Groups cung cấp một giải pháp thanh lịch để tổ chức mã và áp dụng các layout riêng biệt mà không ảnh hưởng đến ngữ nghĩa của URL. Parallel Routes mở khóa khả năng xây dựng các giao diện động, đa bảng điều khiển với các trạng thái độc lập, điều mà trước đây chỉ có thể đạt được thông qua quản lý trạng thái phức tạp phía máy khách.

Bằng cách hiểu và kết hợp các mẫu kiến trúc mạnh mẽ này, bạn có thể vượt ra ngoài các trang web đơn giản và bắt đầu xây dựng các ứng dụng tinh vi, hiệu suất cao và dễ bảo trì, đáp ứng nhu cầu của người dùng ngày nay. Quá trình học hỏi có thể khó khăn hơn so với Pages Router cổ điển, nhưng lợi ích về mặt kiến trúc ứng dụng và trải nghiệm người dùng là vô cùng lớn. Hãy bắt đầu thử nghiệm với các khái niệm này trong dự án tiếp theo của bạn và mở khóa toàn bộ tiềm năng của Next.js.