Tiếng Việt

Khám phá middleware của Next.js, một tính năng mạnh mẽ để chặn bắt và sửa đổi các yêu cầu đến. Học cách triển khai xác thực, phân quyền, chuyển hướng, và A/B testing với các ví dụ thực tế.

Middleware Next.js: Làm chủ việc chặn bắt yêu cầu cho các ứng dụng động

Middleware Next.js cung cấp một cách linh hoạt và mạnh mẽ để chặn bắt và sửa đổi các yêu cầu đến trước khi chúng đến các route của bạn. Khả năng này cho phép bạn triển khai một loạt các tính năng, từ xác thực và phân quyền đến chuyển hướng và A/B testing, tất cả trong khi tối ưu hóa hiệu suất. Hướng dẫn toàn diện này sẽ giúp bạn hiểu rõ các khái niệm cốt lõi của middleware Next.js và minh họa cách tận dụng nó một cách hiệu quả.

Middleware Next.js là gì?

Middleware trong Next.js là một hàm chạy trước khi một yêu cầu được hoàn thành. Nó cho phép bạn:

Các hàm middleware được định nghĩa trong tệp middleware.ts (hoặc middleware.js) ở thư mục gốc của dự án. Chúng được thực thi cho mọi route trong ứng dụng của bạn, hoặc cho các route cụ thể dựa trên các matcher có thể cấu hình.

Các khái niệm chính và lợi ích

Đối tượng Request

Đối tượng request cung cấp quyền truy cập vào thông tin về yêu cầu đến, bao gồm:

Đối tượng Response

Các hàm middleware trả về một đối tượng Response để kiểm soát kết quả của yêu cầu. Bạn có thể sử dụng các phản hồi sau:

Matchers

Matchers cho phép bạn chỉ định các route mà middleware của bạn nên được áp dụng. Bạn có thể định nghĩa matchers bằng cách sử dụng biểu thức chính quy hoặc các mẫu đường dẫn. Điều này đảm bảo rằng middleware của bạn chỉ chạy khi cần thiết, cải thiện hiệu suất và giảm chi phí hoạt động.

Edge Runtime

Middleware Next.js chạy trên Edge Runtime, một môi trường thực thi JavaScript nhẹ có thể được triển khai gần với người dùng của bạn. Sự gần gũi này giảm thiểu độ trễ và cải thiện hiệu suất tổng thể của ứng dụng, đặc biệt đối với người dùng phân tán toàn cầu. Edge Runtime có sẵn trên Mạng Edge của Vercel và các nền tảng tương thích khác. Edge Runtime có một số hạn chế, cụ thể là việc sử dụng các API của Node.js.

Các ví dụ thực tế: Triển khai các tính năng Middleware

1. Xác thực

Middleware xác thực có thể được sử dụng để bảo vệ các route yêu cầu người dùng phải đăng nhập. Dưới đây là một ví dụ về cách triển khai xác thực bằng cookies:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/dashboard/:path*'],
}

Middleware này kiểm tra sự hiện diện của cookie auth_token. Nếu không tìm thấy cookie, người dùng sẽ được chuyển hướng đến trang /login. config.matcher chỉ định rằng middleware này chỉ nên chạy cho các route dưới /dashboard.

Góc nhìn toàn cầu: Điều chỉnh logic xác thực để hỗ trợ các phương thức xác thực khác nhau (ví dụ: OAuth, JWT) và tích hợp với các nhà cung cấp danh tính khác nhau (ví dụ: Google, Facebook, Azure AD) để phục vụ người dùng từ các khu vực đa dạng.

2. Phân quyền

Middleware phân quyền có thể được sử dụng để kiểm soát quyền truy cập vào tài nguyên dựa trên vai trò hoặc quyền của người dùng. Ví dụ, bạn có thể có một trang quản trị mà chỉ những người dùng cụ thể mới có thể truy cập.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 const token = request.cookies.get('auth_token');

 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // Example: Fetch user roles from an API (replace with your actual logic)
 const userResponse = await fetch('https://api.example.com/userinfo', {
 headers: {
 Authorization: `Bearer ${token}`,
 },
 });
 const userData = await userResponse.json();

 if (userData.role !== 'admin') {
 return NextResponse.redirect(new URL('/unauthorized', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/admin/:path*'],
}

Middleware này lấy vai trò của người dùng và kiểm tra xem họ có vai trò admin hay không. Nếu không, họ sẽ được chuyển hướng đến trang /unauthorized. Ví dụ này sử dụng một điểm cuối API giữ chỗ. Hãy thay thế `https://api.example.com/userinfo` bằng điểm cuối máy chủ xác thực thực tế của bạn.

Góc nhìn toàn cầu: Hãy lưu ý đến các quy định về quyền riêng tư dữ liệu (ví dụ: GDPR, CCPA) khi xử lý dữ liệu người dùng. Triển khai các biện pháp bảo mật thích hợp để bảo vệ thông tin nhạy cảm và đảm bảo tuân thủ luật pháp địa phương.

3. Chuyển hướng

Middleware chuyển hướng có thể được sử dụng để chuyển hướng người dùng dựa trên vị trí, ngôn ngữ hoặc các tiêu chí khác. Ví dụ, bạn có thể chuyển hướng người dùng đến phiên bản địa phương hóa của trang web của mình dựa trên địa chỉ IP của họ.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const country = request.geo?.country || 'US'; // Default to US if geo-location fails

 if (country === 'DE') {
 return NextResponse.redirect(new URL('/de', request.url))
 }

 if (country === 'FR') {
 return NextResponse.redirect(new URL('/fr', request.url))
 }

 return NextResponse.next()
}

export const config = {
 matcher: ['/'],
}

Middleware này kiểm tra quốc gia của người dùng dựa trên địa chỉ IP của họ và chuyển hướng họ đến phiên bản địa phương hóa phù hợp của trang web (/de cho Đức, /fr cho Pháp). Nếu việc xác định vị trí địa lý thất bại, nó sẽ mặc định là phiên bản của Mỹ. Lưu ý rằng điều này phụ thuộc vào thuộc tính geo có sẵn (ví dụ: khi triển khai trên Vercel).

Góc nhìn toàn cầu: Đảm bảo rằng trang web của bạn hỗ trợ nhiều ngôn ngữ và tiền tệ. Cung cấp cho người dùng tùy chọn để chọn ngôn ngữ hoặc khu vực ưa thích của họ theo cách thủ công. Sử dụng các định dạng ngày và giờ phù hợp cho từng địa phương.

4. A/B Testing

Middleware có thể được sử dụng để triển khai A/B testing bằng cách phân bổ ngẫu nhiên người dùng vào các biến thể khác nhau của một trang và theo dõi hành vi của họ. Dưới đây là một ví dụ đơn giản:


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

function getRandomVariant() {
 return Math.random() < 0.5 ? 'A' : 'B';
}

export function middleware(request: NextRequest) {
 let variant = request.cookies.get('variant')?.value;

 if (!variant) {
 variant = getRandomVariant();
 const response = NextResponse.next();
 response.cookies.set('variant', variant);
 return response;
 }

 if (variant === 'B') {
 return NextResponse.rewrite(new URL('/variant-b', request.url));
 }

 return NextResponse.next();
}

export const config = {
 matcher: ['/'],
}

Middleware này phân bổ người dùng vào biến thể 'A' hoặc 'B'. Nếu người dùng chưa có cookie variant, một cookie sẽ được gán và thiết lập ngẫu nhiên. Người dùng được gán vào biến thể 'B' sẽ được viết lại đến trang /variant-b. Sau đó, bạn sẽ theo dõi hiệu suất của từng biến thể để xác định biến thể nào hiệu quả hơn.

Góc nhìn toàn cầu: Hãy xem xét sự khác biệt về văn hóa khi thiết kế các bài kiểm tra A/B. Những gì hoạt động tốt ở một khu vực có thể không gây được tiếng vang với người dùng ở một khu vực khác. Đảm bảo nền tảng A/B testing của bạn tuân thủ các quy định về quyền riêng tư ở các khu vực khác nhau.

5. Feature Flags (Cờ tính năng)

Feature flags (Cờ tính năng) cho phép bạn bật hoặc tắt các tính năng trong ứng dụng của mình mà không cần triển khai mã mới. Middleware có thể được sử dụng để xác định xem người dùng có nên có quyền truy cập vào một tính năng cụ thể hay không dựa trên ID người dùng, vị trí hoặc các tiêu chí khác của họ.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 // Example: Fetch feature flags from an API
 const featureFlagsResponse = await fetch('https://api.example.com/featureflags', {
 headers: {
 'X-User-Id': 'user123',
 },
 });
 const featureFlags = await featureFlagsResponse.json();

 if (featureFlags.new_feature_enabled) {
 // Enable the new feature
 return NextResponse.next();
 } else {
 // Disable the new feature (e.g., redirect to an alternative page)
 return NextResponse.redirect(new URL('/alternative-page', request.url));
 }
}

export const config = {
 matcher: ['/new-feature'],
}

Middleware này lấy các cờ tính năng từ một API và kiểm tra xem cờ new_feature_enabled có được đặt hay không. Nếu có, người dùng có thể truy cập trang /new-feature. Nếu không, họ sẽ được chuyển hướng đến một trang /alternative-page.

Góc nhìn toàn cầu: Sử dụng cờ tính năng để dần dần triển khai các tính năng mới cho người dùng ở các khu vực khác nhau. Điều này cho phép bạn theo dõi hiệu suất và giải quyết mọi vấn đề trước khi phát hành tính năng cho một lượng lớn khán giả. Ngoài ra, hãy đảm bảo hệ thống cờ tính năng của bạn có khả năng mở rộng trên toàn cầu và cung cấp kết quả nhất quán bất kể vị trí của người dùng. Xem xét các ràng buộc quy định của khu vực đối với việc triển khai tính năng.

Các kỹ thuật nâng cao

Chuỗi Middleware

Bạn có thể kết nối nhiều hàm middleware lại với nhau để thực hiện một loạt các hoạt động trên một yêu cầu. Điều này có thể hữu ích để chia nhỏ logic phức tạp thành các mô-đun nhỏ hơn, dễ quản lý hơn.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
 const response = NextResponse.next();

 // First middleware function
 const token = request.cookies.get('auth_token');
 if (!token) {
 return NextResponse.redirect(new URL('/login', request.url))
 }

 // Second middleware function
 response.headers.set('x-middleware-custom', 'value');

 return response;
}

export const config = {
 matcher: ['/dashboard/:path*'],
}

Ví dụ này cho thấy hai middleware trong một. Cái đầu tiên thực hiện xác thực và cái thứ hai đặt một header tùy chỉnh.

Sử dụng biến môi trường

Lưu trữ thông tin nhạy cảm, chẳng hạn như khóa API và thông tin đăng nhập cơ sở dữ liệu, trong các biến môi trường thay vì mã hóa cứng chúng trong các hàm middleware của bạn. Điều này cải thiện bảo mật và giúp quản lý cấu hình ứng dụng của bạn dễ dàng hơn.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const API_KEY = process.env.API_KEY;

export async function middleware(request: NextRequest) {
 const response = await fetch('https://api.example.com/data', {
 headers: {
 'X-API-Key': API_KEY,
 },
 });

 // ...
}

export const config = {
 matcher: ['/data'],
}

Trong ví dụ này, API_KEY được lấy từ một biến môi trường.

Xử lý lỗi

Triển khai xử lý lỗi mạnh mẽ trong các hàm middleware của bạn để ngăn các lỗi không mong muốn làm sập ứng dụng. Sử dụng các khối try...catch để bắt các ngoại lệ và ghi lại lỗi một cách thích hợp.


// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
 try {
 const response = await fetch('https://api.example.com/data');
 // ...
 } catch (error) {
 console.error('Error fetching data:', error);
 return NextResponse.error(); // Or redirect to an error page
 }
}

export const config = {
 matcher: ['/data'],
}

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

Xử lý các sự cố thường gặp

Kết luận

Middleware Next.js là một công cụ mạnh mẽ để xây dựng các ứng dụng web động và được cá nhân hóa. Bằng cách làm chủ việc chặn bắt yêu cầu, bạn có thể triển khai một loạt các tính năng, từ xác thực và phân quyền đến chuyển hướng và A/B testing. Bằng cách tuân theo các phương pháp hay nhất được nêu trong hướng dẫn này, bạn có thể tận dụng middleware Next.js để tạo ra các ứng dụng hiệu suất cao, an toàn và có khả năng mở rộng, đáp ứng nhu cầu của người dùng toàn cầu. Hãy tận dụng sức mạnh của middleware để mở ra những khả năng mới trong các dự án Next.js của bạn và mang lại những trải nghiệm người dùng đặc biệt.