Giao diện
Cấu trúc thư mục
chuyenchon2/
├── chuyenchon.com/ # Monorepo pnpm của sản phẩm
│ ├── apps/
│ │ ├── api/ # Cloudflare Worker — api.chuyenchon.com
│ │ │ ├── migrations/ # Migration của D1, chạy theo thứ tự tên file
│ │ │ ├── seed/ # data.mjs (nguồn) + seed.sql, search.sql (sinh ra)
│ │ │ ├── scripts/ # build-seed.mjs
│ │ │ ├── src/
│ │ │ │ ├── lib/ # helper HTTP, dựng chỉ mục tìm kiếm
│ │ │ │ ├── routes/ # mỗi thực thể một file
│ │ │ │ ├── env.ts
│ │ │ │ └── index.ts # điểm vào, CORS, xử lý lỗi
│ │ │ └── wrangler.toml
│ │ └── web/ # Vite + React — www.chuyenchon.com
│ │ ├── public/ # robots.txt, _headers, _redirects
│ │ ├── scripts/prerender.mjs
│ │ └── src/
│ │ ├── components/ui/ # primitive theo phong cách shadcn
│ │ ├── components/ # layout, tìm kiếm, motion, khối trang dùng chung
│ │ ├── lib/ # client API, hook query, SEO, nhãn, tiện ích
│ │ ├── pages/ # một file mỗi màn hình
│ │ ├── App.tsx # bảng định tuyến
│ │ ├── main.tsx # điểm vào trình duyệt (hydrate hoặc mount)
│ │ └── entry-server.tsx# điểm vào lúc prerender
│ └── packages/shared/ # kiểu dữ liệu và hàm chuẩn hoá tiếng Việt dùng chung
└── docs.chuyenchon.com/ # Tài liệu VitePress (trang bạn đang đọc)Vì sao tách packages/shared
Hàm normalize() bỏ dấu tiếng Việt phải giống hệt nhau ở ba nơi: lúc dựng chỉ mục tìm kiếm, lúc xử lý câu truy vấn, và lúc sinh file seed. Đặt chung một chỗ để tránh lệch.
Riêng scripts/build-seed.mjs chạy bằng Node thuần nên không import được file .ts; nó giữ một bản sao có ghi chú rõ ràng, và bất kỳ thay đổi nào ở packages/shared/src/text.ts cũng phải chép sang đó.