Series 6 – Next.js Advanced & Production
Bài 3: Next.js Internationalized Routing – Tổ chức URL cho website đa ngôn ngữ
1. Mở đầu
Sau khi xây dựng cơ chế dịch nội dung và định dạng theo locale, ứng dụng cần một cách để người dùng truy cập đúng phiên bản ngôn ngữ.
Ví dụ, một website có thể cung cấp cùng một trang sản phẩm bằng tiếng Việt và tiếng Anh:
/vi/products
/en/products
Hai URL đại diện cho cùng một loại nội dung nhưng sử dụng ngôn ngữ khác nhau. Cách tổ chức này giúp người dùng nhận biết phiên bản ngôn ngữ đang xem, đồng thời hỗ trợ quản lý các trang đa ngôn ngữ.
Trong Next.js App Router, chúng ta có thể triển khai internationalized routing bằng cách tổ chức thư mục route theo locale.
2. Internationalized Routing là gì?
Internationalized Routing là cách tổ chức đường dẫn và điều hướng để một ứng dụng hỗ trợ nhiều locale.
Có nhiều chiến lược URL khác nhau, chẳng hạn:
| Chiến lược | Ví dụ |
|---|---|
| Locale trong path | example.com/vi/products |
| Subdomain | vi.example.com/products |
| Domain riêng | example.vn/products |
Đối với nhiều ứng dụng Next.js, đưa locale vào path là cách dễ bắt đầu vì các phiên bản ngôn ngữ có thể dùng chung một domain và cấu trúc route.
Next.js App Router không tự động dịch toàn bộ ứng dụng chỉ vì bạn thêm một đoạn /vi vào URL. Developer vẫn cần thiết kế cơ chế chọn locale, tải thông điệp và hiển thị nội dung tương ứng.
3. Tổ chức thư mục trong App Router
Một cấu trúc phổ biến là đặt các route phụ thuộc locale bên dưới một dynamic segment [locale].
src/
app/
[locale]/
layout.tsx
page.tsx
products/
page.tsx
Khi đó, các đường dẫn như /vi, /en, /vi/products và /en/products có thể được xử lý thông qua cùng cấu trúc component.
Trong ví dụ này, locale là một dynamic route segment. Giá trị của nó được truyền qua params.
4. Tạo danh sách locale được hỗ trợ
Trước tiên, xác định các locale mà ứng dụng hỗ trợ:
// src/i18n/config.ts
export const locales = ["vi", "en"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "vi";
Việc tập trung danh sách locale vào một file giúp tránh phải khai báo các giá trị rải rác trong nhiều component.
5. Nhận diện locale trong page
Ví dụ với src/app/[locale]/page.tsx:
import { notFound } from "next/navigation";
import { locales } from "@/i18n/config";
type Props = {
params: Promise<{ locale: string }>;
};
export default async function HomePage({
params,
}: Props) {
const { locale } = await params;
if (!locales.includes(locale as (typeof locales)[number])) {
notFound();
}
if (locale === "vi") {
return <h1>Chào mừng bạn</h1>;
}
return <h1>Welcome</h1>;
}
Đây là ví dụ tối giản để minh họa routing. Khi ứng dụng lớn hơn, không nên đặt toàn bộ bản dịch trực tiếp trong page. Hãy tách thông điệp thành các file riêng hoặc dùng thư viện i18n.
Đối với một dự án thực tế, cũng nên xác thực locale trong layout và các phần dùng chung, tránh lặp lại cùng một logic kiểm tra ở mọi trang.
6. Sử dụng locale trong layout
layout.tsx có thể nhận locale từ route và thiết lập ngôn ngữ cho tài liệu HTML:
import { notFound } from "next/navigation";
import { locales } from "@/i18n/config";
type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};
export default async function LocaleLayout({
children,
params,
}: Props) {
const { locale } = await params;
if (!locales.includes(locale as (typeof locales)[number])) {
notFound();
}
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
Khai báo lang phù hợp giúp trình duyệt và công nghệ hỗ trợ nhận biết ngôn ngữ chính của nội dung.
Nếu sử dụng layout gốc khác, cần bảo đảm chỉ có một cấu trúc HTML gốc hợp lệ cho tài liệu được render.
7. Điều hướng giữa các locale
Khi người dùng đang ở trang sản phẩm tiếng Việt và chọn tiếng Anh, ứng dụng cần điều hướng đến phiên bản tương ứng:
import Link from "next/link";
export function LanguageSwitcher() {
return (
<nav>
<Link href="/vi/products">Tiếng Việt</Link>
<Link href="/en/products">English</Link>
</nav>
);
}
Đây là ví dụ đơn giản. Với ứng dụng nhiều trang, language switcher nên giữ lại phần đường dẫn tương ứng nếu trang đó có bản dịch ở locale đích. Nếu không có bản dịch, ứng dụng cần có quy tắc chuyển về trang phù hợp.
8. Metadata cho các phiên bản ngôn ngữ
Khi website có nhiều phiên bản ngôn ngữ, metadata cần phản ánh đúng nội dung của từng locale.
Ví dụ, trang /vi/products nên có title và description bằng tiếng Việt, còn /en/products nên có metadata bằng tiếng Anh.
Với các website có nội dung tương đương ở nhiều ngôn ngữ, có thể cân nhắc cấu hình các liên kết alternates.languages để khai báo các phiên bản ngôn ngữ tương ứng. Các URL cần là URL thực tế và ổn định của từng phiên bản.
Không nên khai báo phiên bản ngôn ngữ không tồn tại hoặc dẫn mọi locale về cùng một URL mà không có lý do phù hợp.
9. Những lưu ý khi triển khai
- Xác thực locale để tránh URL ngôn ngữ không được hỗ trợ.
- Thiết kế quy tắc mặc định khi người dùng truy cập
/mà chưa có locale. - Không giả định rằng locale luôn phản ánh chính xác vị trí của người dùng.
- Giữ trạng thái và đường dẫn hợp lý khi chuyển đổi ngôn ngữ.
- Kiểm tra metadata và các liên kết ngôn ngữ trên từng trang.
- Nếu cần redirect theo cookie hoặc header, hãy cân nhắc cấu hình phù hợp với phiên bản Next.js và kiến trúc deployment.
10. Bài tập thực hành
Hãy xây dựng một website giới thiệu sản phẩm với hai locale vi và en.
- Tạo route
/[locale]. - Xây dựng trang chủ và trang sản phẩm cho cả hai ngôn ngữ.
- Tạo language switcher.
- Cấu hình
lang, title và description theo locale. - Xử lý locale không hợp lệ bằng
notFound(). - Kiểm tra trường hợp trang chưa có bản dịch tương ứng.
11. Kết luận
Internationalized Routing giúp ứng dụng Next.js tổ chức URL rõ ràng cho các phiên bản ngôn ngữ khác nhau. Khi kết hợp dynamic route segment, cấu hình locale và hệ thống thông điệp, Developer có thể xây dựng ứng dụng đa ngôn ngữ dễ quản lý hơn.
Tuy nhiên, routing chỉ giải quyết việc xác định đường dẫn. Cơ chế dịch nội dung, định dạng theo locale và metadata vẫn cần được thiết kế đồng bộ.
Ở bài tiếp theo, chúng ta sẽ tìm hiểu Next.js Static Export, cách xuất ứng dụng thành các file tĩnh và những giới hạn cần cân nhắc khi lựa chọn hình thức triển khai này.
Bài 4: Next.js Static Export – Xuất ứng dụng Next.js thành website tĩnh
1. Mở đầu
Next.js thường được biết đến với khả năng render trên server, lấy dữ liệu và xây dựng các ứng dụng web hiện đại. Tuy nhiên, không phải dự án nào cũng cần một server Next.js chạy liên tục để xử lý từng yêu cầu.
Ví dụ, một website giới thiệu công ty, trang portfolio cá nhân hoặc website tài liệu có nội dung tương đối ổn định có thể được triển khai bằng các file HTML, CSS và JavaScript tĩnh.
Static Export cho phép Next.js tạo ra các tài nguyên tĩnh để có thể triển khai trên những dịch vụ hosting tĩnh, miễn là ứng dụng đáp ứng các giới hạn của chế độ này.
2. Static Export là gì?
Static Export là quá trình xuất ứng dụng Next.js thành các file tĩnh có thể được phục vụ trực tiếp bởi web server hoặc dịch vụ hosting tĩnh.
Thay vì cần một server Next.js để render nội dung theo từng request, ứng dụng sử dụng các file được tạo ra trong quá trình build.
Một số đặc điểm:
- Các trang được xuất thành tài nguyên tĩnh trong quá trình build.
- Có thể triển khai trên hosting tĩnh hoặc CDN.
- Không cần một server Next.js chạy liên tục cho các trang đã được xuất.
- Những tính năng phụ thuộc vào server runtime có thể không sử dụng được theo cách thông thường.
Static Export đặc biệt phù hợp với website có nội dung ổn định hoặc có thể xác định trước tại thời điểm build.
3. Cấu hình Static Export trong Next.js
Trong file next.config.ts, cấu hình output: 'export':
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
};
export default nextConfig;
Sau đó chạy lệnh build:
npm run build
Với cấu hình static export, Next.js sẽ tạo thư mục out chứa các tài nguyên đã xuất.
Ví dụ cấu trúc đầu ra có thể có dạng:
out/
index.html
404.html
_next/
static/
about/
index.html
Cấu trúc chính xác phụ thuộc vào các route và cấu hình của ứng dụng.
Các file trong out là đầu ra để triển khai, không phải mã nguồn cần tiếp tục chỉnh sửa trực tiếp.
4. Static Export hỗ trợ những loại trang nào?
Các trang có thể tạo trước trong quá trình build là ứng viên phù hợp cho static export.
Ví dụ:
/
/about
/services
/blog
Với dynamic route như /blog/[slug], ứng dụng cần xác định trước các đường dẫn sẽ được xuất, thường thông qua generateStaticParams.
Ví dụ:
type Props = {
params: Promise<{ slug: string }>;
};
export async function generateStaticParams() {
return [
{ slug: "frontend-basics" },
{ slug: "react-introduction" },
];
}
export default async function BlogPost({
params,
}: Props) {
const { slug } = await params;
return <article>{slug}</article>;
}
Ví dụ này minh họa việc xác định các giá trị slug cần xuất. Trong dự án thực tế, bạn có thể lấy danh sách slug từ nguồn dữ liệu tại thời điểm build.
Nếu có một slug mới sau khi build, file tương ứng sẽ không tự xuất hiện cho đến khi ứng dụng được build lại và triển khai lại.
5. Những giới hạn cần biết
Static Export không tương đương với việc chạy đầy đủ Next.js trên server. Một số tính năng cần server runtime không được hỗ trợ theo cách thông thường.
Các giới hạn quan trọng gồm:
- Dynamic request rendering: Không thể render tùy ý một trang trên server cho mỗi request nếu chỉ phục vụ file tĩnh.
- Route Handlers phụ thuộc request: Những endpoint cần xử lý request động không thể hoạt động như API server thông thường trong static export.
- Server Actions: Cần server để xử lý hành động phía server, nên không phù hợp với static export thuần túy.
- Cookie và header từ request: Không thể đọc trực tiếp thông tin request trên server tại thời điểm người dùng truy cập nếu không có server runtime.
- ISR: Không thể dùng Incremental Static Regeneration theo cơ chế server runtime thông thường trong static export.
- Image Optimization mặc định: Image Optimization của Next.js thường cần server; với static export cần chọn loader hoặc giải pháp tối ưu ảnh phù hợp.
Không nên kết luận rằng mọi dynamic route đều không thể dùng static export. Những route có thể xác định trước và xuất tại thời điểm build vẫn có thể hoạt động tốt.
6. Static Export và dữ liệu động
Một website tĩnh vẫn có thể lấy dữ liệu động từ API bên ngoài thông qua JavaScript phía trình duyệt.
Ví dụ, trang được xuất thành HTML tĩnh nhưng sau khi tải, client có thể gọi API để lấy danh sách sản phẩm mới nhất.
Tuy nhiên, cần phân biệt hai trường hợp:
Dữ liệu lấy tại thời điểm build: Nội dung được lấy và đưa vào trang khi build. Khi dữ liệu nguồn thay đổi, cần build lại để cập nhật HTML đã xuất.
Dữ liệu lấy tại thời điểm người dùng truy cập: Trình duyệt gọi API sau khi trang được tải. Dữ liệu có thể mới hơn, nhưng trải nghiệm ban đầu phụ thuộc vào việc tải dữ liệu phía client.
Nếu API cần bí mật như khóa truy cập riêng tư, không được đặt bí mật đó trong mã JavaScript phía trình duyệt. Các bí mật cần được giữ ở môi trường server phù hợp.
7. Static Export với website đa ngôn ngữ
Với ứng dụng sử dụng route như /vi và /en, static export có thể phù hợp nếu các trang và phiên bản ngôn ngữ cần thiết đều có thể được tạo trước khi build.
Ví dụ:
/
vi/
index.html
products/
index.html
en/
index.html
products/
index.html
Cấu trúc đầu ra thực tế phụ thuộc vào cấu hình route và cách Next.js xuất trang.
Developer cần bảo đảm các locale và route cần thiết được tạo ra trong quá trình build. Nếu ứng dụng cần tự động nhận diện locale dựa trên header hoặc cookie rồi redirect bằng server, cần một giải pháp khác vì static hosting không có Next.js server runtime để xử lý logic đó.
8. Triển khai Static Export
Sau khi chạy build thành công, thư mục out có thể được triển khai lên dịch vụ hosting tĩnh hoặc CDN hỗ trợ phục vụ các file đã xuất.
Quy trình tổng quát:
- Cài đặt dependencies.
- Cấu hình
output: "export". - Chạy lệnh build.
- Kiểm tra thư mục
out. - Triển khai nội dung của
outlên hosting. - Kiểm tra đường dẫn, ảnh, CSS, JavaScript và các trang lỗi.
Một lưu ý quan trọng là cách máy chủ xử lý URL có thể khác nhau. Ví dụ, việc truy cập /about có hoạt động hay không còn phụ thuộc cấu hình rewrite hoặc quy tắc phục vụ file của hosting. Hãy kiểm tra URL trực tiếp thay vì chỉ kiểm tra điều hướng từ trang chủ.
9. Khi nào nên sử dụng Static Export?
Static Export phù hợp khi:
- Website chủ yếu là trang giới thiệu, portfolio hoặc tài liệu.
- Các route cần thiết có thể xác định tại thời điểm build.
- Không cần server Next.js xử lý request động.
- Muốn triển khai đơn giản trên static hosting hoặc CDN.
Không nên chọn static export chỉ vì muốn website nhanh hơn. Ứng dụng cần đăng nhập, xử lý quyền trên server, API động hoặc các tính năng phụ thuộc server runtime có thể cần một kiến trúc triển khai khác.
10. Bài tập thực hành
Hãy tạo một website portfolio bằng Next.js.
- Tạo các trang trang chủ, giới thiệu và dự án.
- Cấu hình
output: "export". - Chạy
npm run build. - Kiểm tra các file trong thư mục
out. - Triển khai thư mục
outlên hosting tĩnh. - Truy cập trực tiếp từng URL để kiểm tra routing.
- Thử thêm một dynamic route và xác định các đường dẫn cần xuất trước khi build.
11. Kết luận
Next.js Static Export giúp chuyển ứng dụng thành các tài nguyên tĩnh, đơn giản hóa việc triển khai đối với những website phù hợp. Tuy nhiên, lựa chọn này đi kèm giới hạn về server runtime, xử lý request và các tính năng cần chạy động.
Trước khi quyết định sử dụng static export, Developer nên đối chiếu nhu cầu thực tế của ứng dụng với các tính năng được hỗ trợ, đặc biệt khi website có xác thực, dữ liệu động hoặc routing đa ngôn ngữ.
Ở bài tiếp theo, chúng ta sẽ tìm hiểu Next.js Build & Deployment để hoàn thiện quy trình từ mã nguồn đến môi trường production.





Khoá học lập trình game con rắn cho trẻ em




