App Router bỏ cấu hình i18n tự động và yêu cầu bạn tự dựng routing bằng một dynamic segment:
app/[locale]/layout.tsx
app/[locale]/page.tsx
app/[locale]/products/[slug]/page.tsxBa mảnh cần ghép:
1. Middleware chọn ngôn ngữ. Khi vào / không có locale, đọc Accept-Language hoặc cookie đã lưu rồi redirect sang /vi hoặc /en. Ghi lựa chọn vào cookie để lần sau không đoán lại.
2. generateStaticParams trả về danh sách locale để mỗi ngôn ngữ được build tĩnh:
export function generateStaticParams() {
return [{ locale: 'vi' }, { locale: 'en' }]
}3. Tín hiệu SEO trong generateMetadata: đặt alternates.languages để sinh thẻ hreflang, và alternates.canonical trỏ về chính URL của ngôn ngữ đó.
alternates: {
canonical: `https://example.com/${locale}`,
languages: { 'vi': 'https://example.com/vi', 'en': 'https://example.com/en', 'x-default': 'https://example.com/vi' },
}Hai lỗi hay gặp: cùng một URL trả nội dung khác nhau tuỳ Accept-Language (Google chỉ crawl được một bản, các bản còn lại không bao giờ được index) — mỗi ngôn ngữ phải có URL riêng; và hreflang khai một chiều — trang vi trỏ sang en thì en cũng phải trỏ ngược lại, nếu không Google bỏ qua.