Tiếng Việt

Khai phá sức mạnh của App Router trong Next.js với hướng dẫn chi tiết về định tuyến dựa trên tệp. Học cách cấu trúc ứng dụng, tạo route động, xử lý layout, và hơn thế nữa.

App Router của Next.js: Hướng Dẫn Toàn Diện về Định tuyến Dựa trên Tệp

App Router của Next.js, được giới thiệu trong Next.js 13 và trở thành tiêu chuẩn trong các phiên bản sau này, đã cách mạng hóa cách chúng ta cấu trúc và điều hướng ứng dụng. Nó giới thiệu một hệ thống định tuyến dựa trên tệp mạnh mẽ và trực quan, giúp đơn giản hóa việc phát triển, cải thiện hiệu suất và nâng cao trải nghiệm tổng thể cho nhà phát triển. Hướng dẫn toàn diện này sẽ đi sâu vào định tuyến dựa trên tệp của App Router, cung cấp cho bạn kiến thức và kỹ năng để xây dựng các ứng dụng Next.js mạnh mẽ và có khả năng mở rộng.

Định tuyến Dựa trên Tệp là gì?

Định tuyến dựa trên tệp là một hệ thống định tuyến trong đó cấu trúc các route của ứng dụng được xác định trực tiếp bởi cách tổ chức các tệp và thư mục của bạn. Trong App Router của Next.js, bạn xác định các route bằng cách tạo các tệp bên trong thư mục `app`. Mỗi thư mục đại diện cho một phân đoạn route, và các tệp đặc biệt trong các thư mục đó xác định cách phân đoạn route đó sẽ được xử lý. Cách tiếp cận này mang lại nhiều lợi thế:

Bắt đầu với App Router

Để sử dụng App Router, bạn cần tạo một dự án Next.js mới hoặc di chuyển một dự án hiện có. Hãy chắc chắn rằng bạn đang sử dụng Next.js phiên bản 13 trở lên.

Tạo một dự án mới:

Bạn có thể tạo một dự án Next.js mới với App Router bằng lệnh sau:

npx create-next-app@latest my-app --example with-app

Di chuyển một dự án hiện có:

Để di chuyển một dự án hiện có, bạn cần di chuyển các trang của mình từ thư mục `pages` sang thư mục `app`. Bạn có thể cần điều chỉnh logic định tuyến của mình cho phù hợp. Next.js cung cấp một hướng dẫn di chuyển để giúp bạn trong quá trình này.

Các khái niệm cốt lõi của Định tuyến Dựa trên Tệp

App Router giới thiệu một số tệp và quy ước đặc biệt xác định cách các route của bạn được xử lý:

1. Thư mục `app`

Thư mục `app` là gốc của các route ứng dụng của bạn. Tất cả các tệp và thư mục trong thư mục này sẽ được sử dụng để tạo route. Bất cứ thứ gì bên ngoài thư mục `app` (như thư mục `pages` nếu bạn đang di chuyển) sẽ bị App Router bỏ qua.

2. Tệp `page.js`

Tệp `page.js` (hoặc `page.jsx`, `page.ts`, `page.tsx`) là phần cơ bản nhất của App Router. Nó định nghĩa thành phần UI sẽ được hiển thị cho một phân đoạn route cụ thể. Đây là một tệp bắt buộc cho bất kỳ phân đoạn route nào bạn muốn có thể truy cập trực tiếp.

Ví dụ:

Nếu bạn có một cấu trúc tệp như sau:

app/
  about/
    page.js

Thành phần được xuất từ `app/about/page.js` sẽ được hiển thị khi người dùng điều hướng đến `/about`.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>Về chúng tôi</h1>
      <p>Tìm hiểu thêm về công ty của chúng tôi.</p>
    </div>
  );
}

3. Tệp `layout.js`

Tệp `layout.js` (hoặc `layout.jsx`, `layout.ts`, `layout.tsx`) định nghĩa một giao diện người dùng được chia sẻ trên nhiều trang trong một phân đoạn route. Layout rất hữu ích để tạo các tiêu đề, chân trang, thanh bên và các yếu tố khác nhất quán nên có mặt trên nhiều trang.

Ví dụ:

Giả sử bạn muốn thêm một tiêu đề cho cả trang `/about` và một trang giả định `/about/team`. Bạn có thể tạo một tệp `layout.js` trong thư mục `app/about`:

// app/about/layout.js
import React from 'react';

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>Về Công ty Chúng tôi</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

Prop `children` sẽ được thay thế bằng UI được hiển thị bởi tệp `page.js` trong cùng thư mục hoặc trong bất kỳ thư mục con nào.

4. Tệp `template.js`

Tệp `template.js` tương tự như `layout.js`, nhưng nó tạo một phiên bản mới của thành phần cho mỗi route con. Điều này hữu ích cho các kịch bản mà bạn muốn duy trì trạng thái của thành phần hoặc ngăn chặn việc render lại khi điều hướng giữa các route con. Không giống như layout, template sẽ render lại khi điều hướng. Sử dụng template rất tuyệt vời để tạo hiệu ứng hoạt hình cho các yếu tố khi điều hướng.

Ví dụ:

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>Template: {count}</p>
      <button onClick={() => setCount(count + 1)}>Cập nhật Template</button>
      {children}
    </main>
  )
}

5. Tệp `loading.js`

Tệp `loading.js` (hoặc `loading.jsx`, `loading.ts`, `loading.tsx`) cho phép bạn tạo một giao diện tải được hiển thị trong khi một phân đoạn route đang tải. Điều này hữu ích để cung cấp trải nghiệm người dùng tốt hơn khi tìm nạp dữ liệu hoặc thực hiện các hoạt động bất đồng bộ khác.

Ví dụ:

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>Đang tải thông tin về chúng tôi...</p>;
}

Khi người dùng điều hướng đến `/about`, thành phần `Loading` sẽ được hiển thị cho đến khi thành phần `page.js` được hiển thị đầy đủ.

6. Tệp `error.js`

Tệp `error.js` (hoặc `error.jsx`, `error.ts`, `error.tsx`) cho phép bạn tạo một giao diện lỗi tùy chỉnh được hiển thị khi có lỗi xảy ra trong một phân đoạn route. Điều này hữu ích để cung cấp một thông báo lỗi thân thiện hơn với người dùng và ngăn chặn toàn bộ ứng dụng bị sập.

Ví dụ:

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Đã xảy ra lỗi!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Thử lại</button>
    </div>
  );
}

Nếu có lỗi xảy ra trong khi hiển thị trang `/about`, thành phần `Error` sẽ được hiển thị. Prop `error` chứa thông tin về lỗi, và hàm `reset` cho phép người dùng thử tải lại trang.

7. Nhóm Route (Route Groups)

Nhóm Route `(groupName)` cho phép bạn tổ chức các route của mình mà không ảnh hưởng đến cấu trúc URL. Chúng được tạo bằng cách đặt tên thư mục trong dấu ngoặc đơn. Điều này đặc biệt hữu ích để tổ chức các layout và các thành phần được chia sẻ.

Ví dụ:

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

Trong ví dụ này, các trang `about` và `contact` được nhóm dưới nhóm `marketing`, và trang `products` nằm dưới nhóm `shop`. Các URL vẫn là `/about`, `/contact`, và `/products`.

8. Route Động (Dynamic Routes)

Route động cho phép bạn tạo các route với các phân đoạn thay đổi. Điều này hữu ích để hiển thị nội dung dựa trên dữ liệu được tìm nạp từ cơ sở dữ liệu hoặc API. Các phân đoạn route động được định nghĩa bằng cách đặt tên phân đoạn trong dấu ngoặc vuông (ví dụ: `[id]`).

Ví dụ:

Giả sử bạn muốn tạo một route để hiển thị các bài đăng blog riêng lẻ dựa trên ID của chúng. Bạn có thể tạo một cấu trúc tệp như sau:

app/
  blog/
    [id]/
      page.js

Phân đoạn `[id]` là một phân đoạn động. Thành phần được xuất từ `app/blog/[id]/page.js` sẽ được hiển thị khi người dùng điều hướng đến một URL như `/blog/123` hoặc `/blog/456`. Giá trị của tham số `id` sẽ có sẵn trong prop `params` của thành phần.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // Tìm nạp dữ liệu cho bài đăng blog với ID đã cho
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Không tìm thấy bài đăng blog.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // Mô phỏng việc lấy dữ liệu từ cơ sở dữ liệu hoặc API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Bài Blog Đầu tiên của tôi', content: 'Đây là nội dung của bài blog đầu tiên của tôi.' },
        '456': { title: 'Một Bài Blog Khác', content: 'Đây là một số nội dung thú vị hơn.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Bạn cũng có thể sử dụng nhiều phân đoạn động trong một route. Ví dụ, bạn có thể có một route như `/blog/[category]/[id]`.

9. Phân đoạn Catch-all

Phân đoạn catch-all cho phép bạn tạo các route khớp với bất kỳ số lượng phân đoạn nào. Điều này hữu ích cho các kịch bản như tạo một CMS nơi cấu trúc URL được xác định bởi người dùng. Các phân đoạn catch-all được định nghĩa bằng cách thêm ba dấu chấm trước tên phân đoạn (ví dụ: `[...slug]`).

Ví dụ:

app/
  docs/
    [...slug]/
      page.js

Phân đoạn `[...slug]` sẽ khớp với bất kỳ số lượng phân đoạn nào sau `/docs`. Ví dụ, nó sẽ khớp với `/docs/getting-started`, `/docs/api/users`, và `/docs/advanced/configuration`. Giá trị của tham số `slug` sẽ là một mảng chứa các phân đoạn đã khớp.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>Tài liệu</h1>
      <p>Slug: {slug ? slug.join('/') : 'Không có slug'}</p>
    </div>
  );
}

Các phân đoạn catch-all tùy chọn có thể được tạo bằng cách đặt tên phân đoạn trong dấu ngoặc vuông kép `[[...slug]]`. Điều này làm cho phân đoạn route trở nên tùy chọn. Ví dụ:

app/
  blog/
    [[...slug]]/
      page.js

Thiết lập này sẽ hiển thị thành phần page.js ở cả `/blog` và `/blog/any/number/of/segments`.

10. Route Song song (Parallel Routes)

Route Song song cho phép bạn hiển thị đồng thời một hoặc nhiều trang trong cùng một layout. Điều này đặc biệt hữu ích cho các layout phức tạp, chẳng hạn như dashboard, nơi các phần khác nhau của trang có thể được tải độc lập. Route Song song được định nghĩa bằng cách sử dụng ký hiệu `@` theo sau là tên slot (ví dụ: `@sidebar`, `@main`).

Ví dụ:

app/
  @sidebar/
    page.js  // Nội dung cho thanh bên
  @main/
    page.js  // Nội dung cho phần chính
  default.js // Bắt buộc: Định nghĩa layout mặc định cho các route song song

Tệp `default.js` là bắt buộc khi sử dụng route song song. Nó định nghĩa cách các slot khác nhau được kết hợp để tạo ra layout cuối cùng.

// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
        {sidebar}
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        {main}
      </main>
    </div>
  );
}

11. Route Chặn (Intercepting Routes)

Route Chặn cho phép bạn tải một route từ một phần khác của ứng dụng của bạn trong layout hiện tại. Điều này hữu ích để tạo các modal, thư viện ảnh và các yếu tố UI khác nên xuất hiện trên đầu nội dung trang hiện có. Route Chặn được định nghĩa bằng cú pháp `(..)`, chỉ ra cần đi lên bao nhiêu cấp trong cây thư mục để tìm route bị chặn.

Ví dụ:

app/
  (.)photos/
    [id]/
      page.js  // Route bị chặn
  feed/
    page.js  // Trang nơi modal ảnh được hiển thị

Trong ví dụ này, khi người dùng nhấp vào một bức ảnh trên trang `/feed`, route `app/(.)photos/[id]/page.js` bị chặn và hiển thị dưới dạng modal trên trang `/feed`. Cú pháp `(.)` cho Next.js biết tìm route `photos/[id]` ở một cấp cao hơn (đến thư mục `app`).

Tìm nạp dữ liệu với App Router

App Router cung cấp hỗ trợ tích hợp cho việc tìm nạp dữ liệu bằng cách sử dụng Server Components và Client Components. Server Components được hiển thị trên máy chủ, trong khi Client Components được hiển thị trên máy khách. Điều này cho phép bạn chọn cách tiếp cận tốt nhất cho mỗi thành phần dựa trên yêu cầu của nó.

Server Components

Server Components là mặc định trong App Router. Chúng cho phép bạn tìm nạp dữ liệu trực tiếp trong các thành phần của mình mà không cần các API route riêng biệt. Điều này có thể cải thiện hiệu suất và đơn giản hóa mã nguồn của bạn.

Ví dụ:

// app/products/page.js
import React from 'react';

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div>
      <h1>Sản phẩm</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

async function fetchProducts() {
  // Mô phỏng việc lấy dữ liệu từ cơ sở dữ liệu hoặc API
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = [
        { id: 1, name: 'Sản phẩm A' },
        { id: 2, name: 'Sản phẩm B' },
        { id: 3, name: 'Sản phẩm C' },
      ];
      resolve(products);
    }, 500);
  });
}

Trong ví dụ này, hàm `fetchProducts` được gọi trực tiếp trong thành phần `ProductsPage`. Thành phần được hiển thị trên máy chủ, và dữ liệu được tìm nạp trước khi HTML được gửi đến máy khách.

Client Components

Client Components được hiển thị trên máy khách và cho phép bạn sử dụng các tính năng phía máy khách như trình lắng nghe sự kiện, trạng thái và API của trình duyệt. Để sử dụng một Client Component, bạn cần thêm chỉ thị `'use client'` ở đầu tệp.

Ví dụ:

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Bộ đếm</h1>
      <p>Số lượng: {count}</p>
      <button onClick={() => setCount(count + 1)}>Tăng</button>
    </div>
  );
}

Trong ví dụ này, thành phần `CounterPage` là một Client Component vì nó sử dụng hook `useState`. Chỉ thị `'use client'` cho Next.js biết để hiển thị thành phần này trên máy khách.

Các Kỹ thuật Định tuyến Nâng cao

App Router cung cấp một số kỹ thuật định tuyến nâng cao có thể được sử dụng để tạo ra các ứng dụng phức tạp và tinh vi.

1. Trình xử lý Route (Route Handlers)

Trình xử lý Route cho phép bạn tạo các điểm cuối API trong thư mục `app` của mình. Điều này loại bỏ nhu cầu về một thư mục `pages/api` riêng biệt. Trình xử lý Route được định nghĩa trong các tệp có tên `route.js` (hoặc `route.ts`) và xuất các hàm xử lý các phương thức HTTP khác nhau (ví dụ: `GET`, `POST`, `PUT`, `DELETE`).

Ví dụ:

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // Mô phỏng việc lấy người dùng từ cơ sở dữ liệu
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ];

  return NextResponse.json(users);
}

export async function POST(request) {
  const body = await request.json()
  console.log('Dữ liệu đã nhận:', body)
  return NextResponse.json({ message: 'Người dùng đã được tạo' }, { status: 201 })
}

Ví dụ này định nghĩa một trình xử lý route tại `/api/users` xử lý cả hai yêu cầu `GET` và `POST`. Hàm `GET` trả về một danh sách người dùng, và hàm `POST` tạo một người dùng mới.

2. Nhóm Route với nhiều Layout

Bạn có thể kết hợp các nhóm route với các layout để tạo các layout khác nhau cho các phần khác nhau của ứng dụng. Điều này hữu ích cho các kịch bản mà bạn muốn có một tiêu đề hoặc thanh bên khác nhau cho các phần khác nhau của trang web của mình.

Ví dụ:

app/
  (marketing)/
    layout.js  // Layout marketing
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Layout admin
    dashboard/
      page.js

Trong ví dụ này, các trang `about` và `contact` sẽ sử dụng layout `marketing`, trong khi trang `dashboard` sẽ sử dụng layout `admin`.

3. Middleware

Middleware cho phép bạn chạy mã trước khi một yêu cầu được xử lý bởi ứng dụng của bạn. Điều này hữu ích cho các tác vụ như xác thực, ủy quyền, ghi nhật ký và chuyển hướng người dùng dựa trên vị trí hoặc thiết bị của họ.

Middleware được định nghĩa trong một tệp có tên `middleware.js` (hoặc `middleware.ts`) tại thư mục gốc của dự án của bạn.

Ví dụ:

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Kiểm tra xem người dùng đã được xác thực chưa
  const isAuthenticated = false; // Thay thế bằng logic xác thực của bạn

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// Xem "Matching Paths" bên dưới để tìm hiểu thêm
export const config = {
  matcher: '/admin/:path*',
}

Ví dụ này định nghĩa middleware kiểm tra xem người dùng đã được xác thực chưa trước khi cho phép họ truy cập bất kỳ route nào dưới `/admin`. Nếu người dùng chưa được xác thực, họ sẽ được chuyển hướng đến trang `/login`.

Các Phương pháp Tốt nhất cho Định tuyến Dựa trên Tệp

Để tận dụng tối đa hệ thống định tuyến dựa trên tệp của App Router, hãy xem xét các phương pháp tốt nhất sau:

Ví dụ về Quốc tế hóa với Next.js App Router

Next.js App Router đơn giản hóa việc quốc tế hóa (i18n) thông qua định tuyến dựa trên tệp. Dưới đây là cách bạn có thể triển khai i18n một cách hiệu quả:

1. Định tuyến theo đường dẫn con (Sub-path Routing)

Tổ chức các route của bạn dựa trên ngôn ngữ bằng cách sử dụng các đường dẫn con. Ví dụ:

app/
  [locale]/
    page.tsx         // Trang chủ cho ngôn ngữ
    about/
      page.tsx     // Trang giới thiệu cho ngôn ngữ
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';

export default async function HomePage({ params: { locale } }) {
  const t = await getTranslations(locale);
  return (<h1>{t.home.title}</h1>);
}

// dictionaries.js
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  es: () => import('./dictionaries/es.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`Không thể tải bản dịch cho ngôn ngữ ${locale}`, error);
    return dictionaries.en();
  }
};

Trong thiết lập này, phân đoạn route động `[locale]` xử lý các ngôn ngữ khác nhau (ví dụ: `/en`, `/es`). Các bản dịch được tải động dựa trên ngôn ngữ.

2. Định tuyến theo tên miền (Domain Routing)

Để có một cách tiếp cận nâng cao hơn, bạn có thể sử dụng các tên miền hoặc tên miền phụ khác nhau cho mỗi ngôn ngữ. Điều này thường liên quan đến cấu hình bổ sung với nhà cung cấp dịch vụ lưu trữ của bạn.

3. Middleware để phát hiện ngôn ngữ

Sử dụng middleware để tự động phát hiện ngôn ngữ ưa thích của người dùng và chuyển hướng họ cho phù hợp.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'es', 'fr'];

function getLocale(request) {
  const negotiatorHeaders = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  try {
      return match(languages, locales, 'en'); // Sử dụng "en" làm ngôn ngữ mặc định
  } catch (error) {
      console.error("Lỗi khớp ngôn ngữ:", error);
      return 'en'; // Quay lại tiếng Anh nếu khớp không thành công
  }
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
        request.url
      )
    );
  }
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Middleware này kiểm tra xem đường dẫn được yêu cầu có tiền tố ngôn ngữ hay không. Nếu không, nó sẽ phát hiện ngôn ngữ ưa thích của người dùng bằng cách sử dụng tiêu đề `Accept-Language` và chuyển hướng họ đến đường dẫn cụ thể theo ngôn ngữ thích hợp. Các thư viện như `@formatjs/intl-localematcher` và `negotiator` được sử dụng để xử lý việc đàm phán ngôn ngữ.

Next.js App Router và Khả năng tiếp cận Toàn cầu

Việc tạo ra các ứng dụng web có thể truy cập toàn cầu đòi hỏi sự xem xét cẩn thận các nguyên tắc về khả năng tiếp cận (a11y). Next.js App Router cung cấp một nền tảng vững chắc để xây dựng các trải nghiệm có thể truy cập, nhưng điều cần thiết là phải thực hiện các phương pháp tốt nhất để đảm bảo ứng dụng của bạn có thể sử dụng được bởi mọi người, bất kể khả năng của họ.

Những lưu ý chính về khả năng tiếp cận

  1. HTML ngữ nghĩa: Sử dụng các phần tử HTML ngữ nghĩa (ví dụ: `<article>`, `<nav>`, `<aside>`, `<main>`) để cấu trúc nội dung của bạn. Điều này cung cấp ý nghĩa cho các công nghệ hỗ trợ và giúp người dùng điều hướng trang web của bạn dễ dàng hơn.
  2. Thuộc tính ARIA: Sử dụng các thuộc tính ARIA (Accessible Rich Internet Applications) để tăng cường khả năng tiếp cận của các thành phần và widget tùy chỉnh. Các thuộc tính ARIA cung cấp thông tin bổ sung về vai trò, trạng thái và thuộc tính của các phần tử cho các công nghệ hỗ trợ.
  3. Điều hướng bằng bàn phím: Đảm bảo rằng tất cả các phần tử tương tác đều có thể truy cập được qua bàn phím. Người dùng nên có thể điều hướng qua ứng dụng của bạn bằng phím `Tab` và tương tác với các phần tử bằng phím `Enter` hoặc `Space`.
  4. Độ tương phản màu sắc: Sử dụng đủ độ tương phản màu sắc giữa văn bản và nền để đảm bảo khả năng đọc cho người dùng có khiếm thị. Hướng dẫn về khả năng tiếp cận nội dung web (WCAG) khuyến nghị tỷ lệ tương phản ít nhất là 4.5:1 cho văn bản thông thường và 3:1 cho văn bản lớn.
  5. Văn bản thay thế cho hình ảnh (Alt Text): Cung cấp văn bản thay thế mô tả cho tất cả các hình ảnh. Văn bản thay thế cung cấp một giải pháp thay thế văn bản cho hình ảnh có thể được đọc bởi trình đọc màn hình.
  6. Nhãn biểu mẫu: Liên kết các nhãn biểu mẫu với các trường nhập tương ứng của chúng bằng cách sử dụng phần tử `<label>`. Điều này giúp người dùng hiểu rõ thông tin nào được mong đợi trong mỗi trường.
  7. Kiểm tra bằng trình đọc màn hình: Kiểm tra ứng dụng của bạn bằng trình đọc màn hình để đảm bảo rằng nó có thể truy cập được bởi người dùng có khiếm thị. Các trình đọc màn hình phổ biến bao gồm NVDA, JAWS và VoiceOver.

Triển khai khả năng tiếp cận trong Next.js App Router

  1. Sử dụng thành phần Link của Next.js: Sử dụng thành phần `<Link>` để điều hướng. Nó cung cấp các tính năng tiếp cận tích hợp, chẳng hạn như tìm nạp trước và quản lý tiêu điểm.
  2. Quản lý tiêu điểm: Khi điều hướng giữa các trang hoặc mở các modal, hãy đảm bảo rằng tiêu điểm được quản lý đúng cách. Tiêu điểm nên được đặt vào phần tử hợp lý nhất trên trang hoặc modal mới.
  3. Các thành phần tùy chỉnh có thể truy cập: Khi tạo các thành phần tùy chỉnh, hãy đảm bảo rằng chúng có thể truy cập được bằng cách tuân theo các nguyên tắc đã nêu ở trên. Sử dụng HTML ngữ nghĩa, thuộc tính ARIA và điều hướng bằng bàn phím để làm cho các thành phần của bạn có thể sử dụng được bởi mọi người.
  4. Linting và Kiểm thử: Sử dụng các công cụ linting như ESLint với các plugin về khả năng tiếp cận để xác định các vấn đề tiềm ẩn về khả năng tiếp cận trong mã của bạn. Ngoài ra, hãy sử dụng các công cụ kiểm thử tự động để kiểm tra ứng dụng của bạn về các vi phạm khả năng tiếp cận.

Kết luận

Hệ thống định tuyến dựa trên tệp của Next.js App Router cung cấp một cách mạnh mẽ và trực quan để cấu trúc và điều hướng các ứng dụng của bạn. Bằng cách hiểu các khái niệm cốt lõi và các phương pháp tốt nhất được nêu trong hướng dẫn này, bạn có thể xây dựng các ứng dụng Next.js mạnh mẽ, có khả năng mở rộng và dễ bảo trì. Hãy thử nghiệm với các tính năng khác nhau của App Router và khám phá cách nó có thể đơn giản hóa quy trình phát triển của bạn và cải thiện trải nghiệm người dùng.