Building a region‑aware, SEO‑friendly global web app

Bài tóm tắt Mercari 2025 4. Điều chỉnh Độ khó: Trung bình

Mercari · 2025 · 4. Điều chỉnh · Độ khó: Trung bình — bài gốc

Một bài trong series Global App (ngoài sheet). Bối cảnh: Mercari 2025. Bài tiếp của cùng tác giả: Mercari 2025; chi tiết lấy dữ liệu: Mercari 2025.

Bối cảnh

  • 2024: để khách nước ngoài mua trực tiếp, đội nhỏ chọn dùng lại web Nhật (jp.mercari.com): khách Đài Loan đăng ký, xem, mua qua đối tác Buyee; đã có i18n nên thêm ngôn ngữ dễ. Ra nhanh, số liệu tốt, rồi mở cho Hồng Kông và thêm giỏ hàng gộp vận chuyển. Nhưng phát triển ngày càng vất vả; mở rộng tiếp web Nhật không bền.
  • Web Nhật: ban đầu là JAMstack trên Gatsby với pre-render động cho SEO (dựng headless browser cho mỗi request, đắt), vài năm trước chuyển Next.js Pages Router nhưng vẫn là app client lớn được bơm dữ liệu để SSR trang SEO. Trang chi tiết sản phẩm cần hơn 20 lần fetch, có chuỗi nối tiếp qua API gateway.
  • Trang dùng chung giữa các vùng: tiền tệ suy ra từ IP nên cùng URL trả HTML khác nhau theo nơi gọi; bot search thường gọi từ Mỹ nên SEO theo vùng gần như bất khả; QA phải dùng VPN theo vùng; tính năng chỉ dành cho Nhật hay lọt sang khách quốc tế.

Thay đổi chính

  • Một BFF dùng chung cho web, iOS, Android: backend nghĩ theo resource, frontend nghĩ theo view model; ở web Nhật, có tính năng mới mà frontend cần hơn 100 dòng chỉ để biến đổi dữ liệu. Cân nhắc GraphQL nhưng chọn Protobuf + ConnectRPC, cùng giao thức với giao tiếp giữa module backend, để ít công nghệ hơn. BFF viết bằng Go, nằm ở backend nhưng sở hữu chung giữa client và backend. Theo tác giả, lần viết lại web Nhật trước mất khoảng 18 tháng, lần này phát triển tính năng xong trong 6 tháng, phần lớn nhờ điều phối và logic nghiệp vụ nằm ở BFF.
  • Next.js App Router + React Server Components cho app mới (dựng bằng công cụ Web Bootstrap của đội Frontend Enabling, sinh cả PR Terraform và manifest Kubernetes): RSC render ra chuỗi, không phải gửi cả app để hydrate. Hệ sinh thái RSC còn non: test chuyển từ Jest + React Testing Library sang Storybook với wrapper cho component async lồng nhau, chạy trên CI bằng Vitest. Theo real user metrics, dù chưa tối ưu hay cache gì, hiệu năng đã tốt hơn rõ so với web Nhật.
  • Subdomain theo vùng (tw.mercari.com, hk.mercari.com) thay cho ccTLD hay thư mục theo vùng: tín hiệu rõ cho bot, giống jp.mercari.com, dễ quản lý hơn nhiều ccTLD, định tuyến tới server gần qua DNS, sau này tách riêng một vùng dễ. App Router chỉ hiểu đường dẫn nên middleware rewrite hk.mercari.com/en thành /hk/en nội bộ; thư mục app/[region]/[locale]; tham số vùng truyền xuống BFF (ví dụ giá theo HKD). Local dùng tw.localhost.
  • <html lang="en-TW"> và link rel="alternate" hreflang cho mọi biến thể để các subdomain không cạnh tranh nhau trên kết quả search.

Vì sao

  • Mỗi trang phải tất định theo URL để SEO và QA được; logic điều phối nhân ba trên ba client là nguồn chậm và lỗi.

Bài học cho SWE

  • Dùng lại sản phẩm cũ cho thị trường mới là đường nhanh nhất để kiểm nhu cầu; khi đã chứng minh, chi phí đắp thêm tăng nhanh hơn chi phí làm lại.
  • Nội dung phụ thuộc vị trí người gọi mà không nằm trong URL phá cả cache, SEO lẫn QA.
  • BFF chung cho nhiều client chuyển logic ghép dữ liệu khỏi ba nơi về một.

Liên quan