Series 6 – Next.js Advanced & Production

Bài 1: Metadata & SEO trong Next.js – Tối ưu thông tin và khả năng hiển thị trên công cụ tìm kiếm

1. Mở đầu

Khi xây dựng một website, việc hoàn thiện giao diện và chức năng mới chỉ là một phần của quá trình phát triển. Một website còn cần cung cấp thông tin rõ ràng cho trình duyệt, công cụ tìm kiếm và các nền tảng chia sẻ liên kết.

Chẳng hạn, khi tìm kiếm một sản phẩm trên Google, người dùng thường nhìn thấy tiêu đề, mô tả và đường dẫn của trang trước khi quyết định truy cập. Khi chia sẻ một liên kết qua mạng xã hội, trang web cũng có thể hiển thị tiêu đề, hình ảnh và phần mô tả đi kèm.

Những thông tin này được quản lý một phần thông qua Metadata. Trong Next.js, framework cung cấp Metadata API giúp Developer khai báo các thông tin cần thiết cho từng trang một cách có tổ chức.

2. Metadata là gì?

Metadata là dữ liệu mô tả thông tin về một trang web. Phần lớn metadata dành cho trình duyệt và các hệ thống bên ngoài, thay vì hiển thị trực tiếp trong nội dung chính của trang.

Một số metadata phổ biến gồm:

  • title: Tiêu đề của trang.
  • description: Mô tả ngắn về nội dung trang.
  • metadataBase: URL cơ sở để tạo các URL metadata tương đối.
  • Open Graph: Thông tin phục vụ việc chia sẻ liên kết trên các nền tảng hỗ trợ.
  • Twitter card metadata: Thông tin hiển thị khi chia sẻ liên kết trên các nền tảng hỗ trợ thẻ này.

Ví dụ HTML cơ bản:

<head>
  <title>Khóa học Frontend cho người mới</title>
  <meta
    name="description"
    content="Tìm hiểu HTML, CSS, JavaScript và React."
  />
</head>

Các thẻ này không thay thế nội dung thực tế của trang, nhưng giúp mô tả trang rõ ràng hơn với các hệ thống có khả năng đọc metadata.

3. SEO là gì và Metadata hỗ trợ SEO như thế nào?

SEO (Search Engine Optimization) là quá trình tối ưu website để công cụ tìm kiếm có thể hiểu nội dung và hiển thị trang phù hợp với truy vấn của người dùng.

Metadata hỗ trợ SEO bằng cách cung cấp thông tin về trang, nhưng chỉ khai báo metadata không đảm bảo website sẽ đạt thứ hạng cao.

Một website còn cần chú ý đến chất lượng nội dung, khả năng thu thập dữ liệu, cấu trúc liên kết, hiệu suất và trải nghiệm người dùng.

Cần phân biệt:

  • title có thể được sử dụng làm cơ sở cho tiêu đề hiển thị trên kết quả tìm kiếm.
  • description có thể được sử dụng làm đoạn mô tả, nhưng công cụ tìm kiếm có thể chọn nội dung khác từ trang.
  • Open Graph chủ yếu phục vụ việc trình bày liên kết khi chia sẻ, không phải yếu tố trực tiếp quyết định thứ hạng tìm kiếm.

4. Khai báo Metadata trong Next.js

Với App Router, Next.js cung cấp metadata và generateMetadata để quản lý metadata ngay trong file layout.tsx hoặc page.tsx.

Khai báo metadata tĩnh

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

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "Frontend Blog",
    template: "%s | Frontend Blog",
  },
  description:
    "Chia sẻ kiến thức về Frontend Development.",
};

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

Trong ví dụ trên:

  • default là tiêu đề mặc định.
  • template cho phép tiêu đề của các trang con được ghép với tên website.
  • description cung cấp mô tả mặc định.
  • lang="vi" khai báo ngôn ngữ chính của tài liệu HTML.

Tại một trang cụ thể, chẳng hạn src/app/blog/page.tsx:

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Bài viết",
  description: "Các bài viết về lập trình Frontend.",
};

export default function BlogPage() {
  return <h1>Bài viết</h1>;
}

Với cấu hình template phía trên, tiêu đề của trang có thể được tạo thành Bài viết | Frontend Blog.

Metadata động với generateMetadata

Một số trang có nội dung thay đổi theo dữ liệu, chẳng hạn trang chi tiết bài viết hoặc sản phẩm. Khi đó, có thể sử dụng generateMetadata.

import type { Metadata } from "next";

type Props = {
  params: Promise<{ slug: string }>;
};

export async function generateMetadata({
  params,
}: Props): Promise<Metadata> {
  const { slug } = await params;

  const post = await getPost(slug);

  return {
    title: post.title,
    description: post.description,
  };
}

export default async function PostPage({
  params,
}: Props) {
  const { slug } = await params;
  const post = await getPost(slug);

  return <article>{post.content}</article>;
}

Trong ví dụ, getPost là hàm lấy dữ liệu cần được triển khai theo dự án thực tế. Cú pháp params là Promise phù hợp với các phiên bản Next.js hiện đại; những phiên bản cũ có thể sử dụng kiểu khác.

Không nên đưa dữ liệu chưa được xác thực từ người dùng vào metadata mà không xử lý phù hợp.

5. Open Graph và chia sẻ liên kết

Open Graph cho phép website cung cấp thông tin để các nền tảng hỗ trợ hiển thị bản xem trước khi chia sẻ liên kết.

Ví dụ:

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Học React từ cơ bản",
  description: "Lộ trình học React dành cho người mới.",
  openGraph: {
    title: "Học React từ cơ bản",
    description: "Lộ trình học React dành cho người mới.",
    images: ["/images/react-cover.jpg"],
    type: "article",
  },
};

Để các URL hình ảnh tương đối được tạo chính xác trong những trường hợp cần URL tuyệt đối, hãy cân nhắc cấu hình metadataBase trong metadata cấp cao hơn:

export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
  title: "Frontend Blog",
};

Hãy thay https://example.com bằng domain thực tế của website.

6. Những lưu ý khi triển khai SEO trong Next.js

Mỗi trang nên có tiêu đề riêng. Những trang có nội dung khác nhau không nên sử dụng cùng một title chung chung.

Mô tả cần phản ánh đúng nội dung. Không nên nhồi nhét từ khóa hoặc viết mô tả gây hiểu lầm.

Không nhầm metadata với toàn bộ SEO. Nội dung có giá trị, cấu trúc heading hợp lý, liên kết nội bộ và khả năng truy cập cũng rất quan trọng.

Chú ý metadata động. Nếu website có nhiều bài viết hoặc sản phẩm, metadata nên được tạo từ dữ liệu tương ứng với từng trang.

Kiểm tra kết quả thực tế. Sau khi triển khai, hãy kiểm tra HTML được tạo ra, URL ảnh Open Graph và cách trang được công cụ tìm kiếm thu thập dữ liệu.

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

Hãy xây dựng một website blog nhỏ với ba trang: trang chủ, danh sách bài viết và chi tiết bài viết.

  1. Khai báo title và description mặc định trong layout.tsx.
  2. Đặt metadata riêng cho trang danh sách bài viết.
  3. Dùng generateMetadata để tạo title và description cho từng bài viết.
  4. Thêm Open Graph metadata cho trang chi tiết.
  5. Kiểm tra metadata sau khi chạy ứng dụng ở chế độ production.

8. Kết luận

Metadata & SEO là một phần quan trọng khi hoàn thiện website. Next.js giúp quản lý metadata thông qua API có cấu trúc, hỗ trợ cả nội dung tĩnh và nội dung động.

Tuy nhiên, metadata chỉ là một phần trong chiến lược SEO tổng thể. Để xây dựng website có khả năng tiếp cận người dùng tốt hơn, Developer cần kết hợp metadata với nội dung chất lượng, cấu trúc trang hợp lý và hiệu suất phù hợp.

Ở bài tiếp theo, chúng ta sẽ tìm hiểu Internationalization (i18n) và Localization (l10n), hai khái niệm quan trọng khi phát triển ứng dụng hướng đến nhiều ngôn ngữ và thị trường khác nhau.

 

Bài 2: Internationalization (i18n) & Localization (l10n) – Xây dựng ứng dụng đa ngôn ngữ

 

1. Mở đầu

Khi một ứng dụng chỉ phục vụ người dùng tại một quốc gia, Developer có thể bắt đầu với một ngôn ngữ và một định dạng hiển thị duy nhất. Nhưng khi sản phẩm mở rộng sang nhiều thị trường, việc chỉ dịch nội dung thường không còn đủ.

Ví dụ, một website thương mại điện tử có thể cần hiển thị tiếng Việt cho người dùng tại Việt Nam và tiếng Anh cho người dùng quốc tế. Ngoài ngôn ngữ, website còn phải xử lý sự khác biệt về ngày tháng, số thập phân, tiền tệ và cách trình bày nội dung.

Để giải quyết vấn đề này, chúng ta cần hiểu hai khái niệm: Internationalization (i18n) và Localization (l10n).

2. Internationalization (i18n) là gì?

Internationalization, thường viết tắt là i18n, là quá trình thiết kế và xây dựng ứng dụng sao cho có thể hỗ trợ nhiều ngôn ngữ và khu vực mà không phải thay đổi lớn trong kiến trúc hệ thống.

Tên viết tắt i18n xuất phát từ việc có 18 ký tự nằm giữa chữ “i” và chữ “n” trong từ “internationalization”.

Thay vì viết cố định nội dung trực tiếp trong giao diện, Developer thiết kế ứng dụng để có thể lựa chọn nội dung tương ứng với ngôn ngữ đang sử dụng.

Ví dụ, thay vì viết:

export default function Welcome() {
  return <h1>Chào mừng bạn đến với website</h1>;
}

Chúng ta có thể tách nội dung thành các thông điệp theo ngôn ngữ:

{
  "vi": {
    "welcome": "Chào mừng bạn đến với website"
  },
  "en": {
    "welcome": "Welcome to our website"
  }
}

Giao diện sẽ lấy thông điệp theo locale đang được lựa chọn thay vì cố định một câu duy nhất.

Một hệ thống được thiết kế tốt cho i18n thường cần tính đến:

  • Khả năng thay đổi ngôn ngữ.
  • Quản lý các bản dịch riêng biệt.
  • Hỗ trợ Unicode và nhiều hệ chữ viết.
  • Định dạng ngày tháng, số và tiền tệ theo locale.
  • Độ dài văn bản khác nhau và cách bố trí giao diện.
  • Khả năng mở rộng thêm ngôn ngữ trong tương lai.

3. Localization (l10n) là gì?

Localization, viết tắt là l10n, là quá trình điều chỉnh ứng dụng để phù hợp với ngôn ngữ, quy ước và bối cảnh của một thị trường cụ thể.

Nếu i18n chuẩn bị nền tảng cho việc hỗ trợ nhiều thị trường, thì l10n tập trung vào việc điều chỉnh trải nghiệm cho từng thị trường đó.

Ví dụ, một ứng dụng có thể cần:

  • Dịch nội dung sang tiếng Việt hoặc tiếng Anh.
  • Hiển thị ngày tháng theo định dạng phù hợp với người dùng.
  • Định dạng số và tiền tệ theo khu vực.
  • Điều chỉnh hình ảnh, ví dụ hoặc cách diễn đạt phù hợp với văn hóa địa phương.
  • Hỗ trợ bố cục từ phải sang trái đối với những ngôn ngữ cần thiết.

Ví dụ với JavaScript:

const date = new Date("2026-10-19T10:00:00Z");

const viDate = new Intl.DateTimeFormat("vi-VN", {
  dateStyle: "long",
  timeZone: "Asia/Ho_Chi_Minh",
}).format(date);

const enDate = new Intl.DateTimeFormat("en-US", {
  dateStyle: "long",
  timeZone: "America/New_York",
}).format(date);

console.log(viDate);
console.log(enDate);

Hai cách định dạng sử dụng cùng một thời điểm nhưng có thể hiển thị khác nhau do locale và múi giờ khác nhau.

Tương tự, có thể định dạng tiền tệ:

const price = 1500000;

const formattedPrice = new Intl.NumberFormat("vi-VN", {
  style: "currency",
  currency: "VND",
}).format(price);

console.log(formattedPrice);

Sử dụng Intl.NumberFormat giúp tránh phải tự xây dựng logic định dạng tiền tệ cho từng thị trường.

4. Phân biệt i18n và l10n

Tiêu chí Internationalization (i18n) Localization (l10n)
Mục tiêu Chuẩn bị ứng dụng hỗ trợ nhiều thị trường Điều chỉnh ứng dụng cho một thị trường cụ thể
Phạm vi Kiến trúc, dữ liệu, khả năng mở rộng Bản dịch, định dạng và trải nghiệm địa phương
Ví dụ Thiết kế hệ thống thông điệp đa ngôn ngữ Dịch giao diện sang tiếng Việt
Thời điểm Thường được tính đến trong quá trình thiết kế Thực hiện khi hỗ trợ một locale cụ thể

Hai khái niệm có liên quan chặt chẽ với nhau. i18n tốt giúp việc l10n trở nên dễ dàng hơn, còn l10n là bước đưa khả năng đa ngôn ngữ vào trải nghiệm thực tế.

5. Quản lý bản dịch trong ứng dụng frontend

Một cách đơn giản là lưu các thông điệp theo từng locale.

Ví dụ cấu trúc thư mục:

src/
  messages/
    vi.json
    en.json
  components/
    Welcome.tsx

File vi.json:

{
  "welcome": "Chào mừng bạn",
  "login": "Đăng nhập"
}

File en.json:

{
  "welcome": "Welcome",
  "login": "Log in"
}

Trong một ứng dụng thực tế, Developer thường sử dụng thư viện i18n hoặc một lớp quản lý thông điệp để tải bản dịch, xử lý biến nội suy và quản lý số nhiều.

Ví dụ, nội dung có biến:

{
  "greeting": "Xin chào, {name}"
}

Khi hiển thị, hệ thống thay {name} bằng tên người dùng. Với những nội dung phức tạp như số nhiều, nên sử dụng cơ chế hỗ trợ pluralization theo locale thay vì tự nối chuỗi thủ công.

6. Locale không chỉ là ngôn ngữ

Locale có thể đại diện cho ngôn ngữ và khu vực, chẳng hạn:

  • vi-VN: tiếng Việt tại Việt Nam.
  • en-US: tiếng Anh tại Hoa Kỳ.
  • en-GB: tiếng Anh tại Vương quốc Anh.
  • ja-JP: tiếng Nhật tại Nhật Bản.

en-US và en-GB cùng sử dụng tiếng Anh nhưng có thể khác nhau về chính tả, định dạng ngày tháng và một số quy ước hiển thị.

Không nên chỉ dựa vào locale để suy đoán chính xác quốc tịch hoặc vị trí địa lý của người dùng. Ứng dụng nên cho phép người dùng lựa chọn ngôn ngữ phù hợp và xử lý các tùy chọn đó một cách rõ ràng.

7. Những lỗi thường gặp khi xây dựng ứng dụng đa ngôn ngữ

Hard-code văn bản trong giao diện: Khi nội dung được viết trực tiếp ở nhiều component, việc bổ sung ngôn ngữ mới trở nên khó khăn.

Nối chuỗi thủ công: Cấu trúc ngữ pháp và số nhiều khác nhau giữa các ngôn ngữ. Nên sử dụng hệ thống thông điệp phù hợp.

Chỉ dịch văn bản: Ngày tháng, tiền tệ, số và bố cục cũng có thể cần được điều chỉnh.

Giả định mọi bản dịch có độ dài như nhau: Một câu tiếng Đức hoặc tiếng Anh có thể dài hơn bản dịch tiếng Việt, khiến giao diện bị tràn.

Không kiểm tra font và Unicode: Các ngôn ngữ khác nhau có thể cần font và cách hiển thị ký tự phù hợp.

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

Hãy mở rộng một trang giới thiệu sản phẩm để hỗ trợ tiếng Việt và tiếng Anh.

  1. Tạo hai bộ thông điệp vi.json và en.json.
  2. Tách văn bản khỏi các component.
  3. Xây dựng chức năng chuyển đổi ngôn ngữ.
  4. Định dạng ngày tháng và giá tiền bằng API Intl.
  5. Kiểm tra giao diện khi bản dịch dài hơn và khi nội dung có ký tự Unicode.

9. Kết luận

Internationalization và Localization là hai nền tảng quan trọng khi phát triển ứng dụng hướng đến nhiều thị trường. i18n giúp thiết kế hệ thống có thể mở rộng đa ngôn ngữ, trong khi l10n điều chỉnh nội dung và trải nghiệm phù hợp với từng locale.

Sau khi chuẩn bị được hệ thống thông điệp và định dạng theo locale, bước tiếp theo là tổ chức URL và điều hướng để người dùng có thể truy cập đúng phiên bản ngôn ngữ của trang. Đây là nội dung của bài tiếp theo về Next.js Internationalized Routing.

Bài viết khác

Series 7 — Web Quality – Engineering :Testing – Unit Testing, Integration Testing, Web Performance – Core Web Vitals, Code Splitting, Lazy Loading

Series 7 — Web Quality – Engineering Bài 3: Testing – Unit Testing, Integration Testing và End-to-End Testing trong ứng dụng web 1. Vì sao ứng dụng web cần Testing? Trong quá trình phát triển Frontend, chúng ta thường kiểm tra ứng dụng bằng cách chạy website trên trình duyệt, nhấn vào các nút, nhập […]

Series 7 — Web Quality – Engineering : Authentication & Authorization

Series 7 — Web Quality – Engineering Bài 1: Authentication & Authorization – Xác thực và phân quyền trong ứng dụng web 1. Mở đầu Khi xây dựng một ứng dụng web có chức năng đăng nhập, chúng ta thường nghĩ rằng chỉ cần tạo biểu mẫu nhập email, mật khẩu và kiểm tra thông […]

Series 6 – Next.js Advanced – Production: Next.js Build & Deployment

Series 6 – Next.js Advanced & Production Bài 5: Next.js Build & Deployment – Từ mã nguồn đến môi trường Production 1. Mở đầu Trong quá trình phát triển một ứng dụng web, việc chạy thành công trên máy tính cá nhân chưa có nghĩa là ứng dụng đã sẵn sàng để đưa đến người […]

Series 6 – Next.js Advanced – Production: Next.js Internationalized Routing Next.js Static Export

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í […]

Series 5 – Next.js Fundamentals: CSR, SSR, SSG, ISR và cách lựa chọn

Series 5 – Next.js Fundamentals   Bài 7 : CSR, SSR, SSG và ISR – Các chiến lược Rendering trong Next.js 1. Mở đầu Khi xây dựng một website bằng React, chúng ta thường tập trung vào việc tạo component, quản lý state và hiển thị giao diện trên trình duyệt. Tuy nhiên, khi ứng dụng […]

Series 5 – Rendering – Data Fetching: Server và Client,  Data Fetching, Caching và Revalidation

Series 5 – Rendering – Data Fetching   Bài 5: Server Components, Client Components và Component Boundaries trong Next.js 1. Mở đầu Khi làm việc với React, chúng ta thường xây dựng component bằng JSX, sử dụng useState để quản lý trạng thái và useEffect để xử lý các tác vụ phụ. Phần lớn logic giao […]

Leave a Reply

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