SEO và ảnh chia sẻ mạng xã hội
SRC-1211 (chủ dự án, 03.10.2026): link một trang chương trình dán lên Facebook hay Zalo phải hiện đúng ảnh, tiêu đề và mô tả của chương trình đó, ở cả nemo12.com lẫn learn.nemo12.com; và mọi trang nemo12.com phải qua một lượt rà SEO, kèm một cổng tự động để lỗi không quay lại.
Ảnh chia sẻ (og:image)
| Phần | Ở đâu | Việc |
|---|---|---|
| Bảng thẻ | apps/web/src/site/shareCards.ts | Chương trình nào có thẻ (mọi chương trình trong programs.ts không soon), đường dẫn nào thuộc chương trình nào (đoạn đầu, sau /parents), tên file. |
| Bộ vẽ | apps/web/scripts/og-images.mjs | Lúc build: ảnh hero của trang gốc chương trình (public/art/pages/<gốc>.svg, xem ảnh minh họa) + tên chương trình + câu hứa (PROGRAMS[id].line), vẽ ra PNG 1200x630 bằng @resvg/resvg-wasm với font Be Vietnam Pro. Ra public/og/*.png (gitignore, sinh lại mỗi build), mỗi ảnh khoảng 80 tới 130 KB, quá 300 KB là build đỏ. |
| Gắn vào trang | apps/web/scripts/prerender.mjs (withShareCard) | Mọi trang dưới gốc một chương trình (cả hai vai, mọi tab, trang nội dung) mang thẻ của chương trình: og:image, og:image:alt, og:image:type, twitter:image. Trang khác giữ thẻ chung /og-image.png. |
PNG chứ không SVG: Facebook và Zalo không hiện og:image dạng SVG. Tên file là URL công khai ổn định (learn và các link đã chia sẻ trỏ vào), nên không đổi theo đường dẫn: NEMO WALK luôn là walk.png dù trang www đang ở /nemo-walk hay /walk.
Thẻ hiện có: ielts, sat, ap, grammar, essay, speak, walk, math, ai-teen, cùng hai bản tiếng Anh ielts-en và walk-en cho learn (luật ngôn ngữ trong CLAUDE.md: learn /ielts tiếng Anh, learn /walk tiếng Anh rất đơn giản).
learn.nemo12.com
learn là SPA phục vụ bằng static assets với not_found_handling: "single-page-application", nên mọi URL trả cùng một index.html; bot xem trước link không chạy JavaScript nên thấy cùng một tiêu đề cho mọi chương trình. apps/learn/scripts/share-meta.mjs chạy sau vite build và ghi một vỏ HTML cho mỗi trang vào của chương trình (dist/ielts.html, dist/sat.html...): đúng index.html đã build, chỉ khác <html lang> và các thẻ trong <head>. Cloudflare phục vụ ielts.html tại /ielts (html_handling mặc định); đường sâu hơn (/ielts/reading/...) vẫn rơi về index.html, nên SPA không đổi gì. Hệ quả nhỏ: /ielts/ (có gạch chéo cuối) nay 307 về /ielts.
Mười một trang vào: /ielts, /walk (en, og:locale en_US), /sat, /ap, /grammar, /essay, /speak, /math, /ai-teen, /programs, /books (vi, vi_VN). Ảnh trỏ sang https://nemo12.com/og/*.png. public/robots.txt của learn vẫn chặn máy tìm kiếm, nhưng cho các bot xem trước link (facebookexternalhit, Facebot, Zalo, Twitterbot, LinkedInBot...) đọc đúng mười một đường dẫn ấy; test src/shareMeta.test.ts giữ hai danh sách khớp nhau.
Facebook giữ ảnh cũ
Facebook (và Zalo) cache bản xem trước theo URL. Link đã từng được chia sẻ trước bản deploy này vẫn hiện ảnh cũ cho tới khi được quét lại: mở Facebook Sharing Debugger (developers.facebook.com/tools/debug), dán URL, bấm "Scrape Again". Việc này chủ dự án làm tay, mỗi URL quan trọng một lần.
Cổng SEO
apps/web/scripts/seo-gate.mjs, chạy cuối npm run build (sau prerender), đọc đúng HTML trong dist/ mà bot nhận được. Build đỏ khi:
- trang được lập chỉ mục thiếu
<title>, meta description, canonical, hay một trong các thẻog:title/description/url/image/image:width/image:height/image:alt/type/locale/site_name,twitter:card; - hai trang tự canonical trùng tiêu đề hoặc trùng mô tả;
- canonical trỏ vào một bí danh 301, vào trang không tồn tại, hoặc vào trang lại canonical tiếp đi chỗ khác;
og:urlkhác canonical; og:imagekhông phải URL https tuyệt đối, là SVG, file không có trongdist/, hoặc trang chương trình không mang thẻ của chương trình mình;- sitemap có URL chuyển hướng, URL không có trang, URL
noindex, hoặc URL không phải canonical của chính nó; - link nội bộ
<a href>dẫn tới 404 (link qua một 301 thì đếm và in ra, không chặn); - JSON-LD không parse được, breadcrumb trong JSON-LD trỏ vào bí danh 301, hoặc trang có số
h1khác 1; robots.txtthiếu dòngSitemaphoặc chặn cả site.
Độ dài (tiêu đề trên 65 ký tự, mô tả ngoài khoảng 70 tới 170) chỉ là cảnh báo: vài tên chương trình dài có chủ đích. npm run seo:gate -- --report in toàn bộ.
Trang bài viết dựng từ D1 (worker/articles) nằm ngoài dist/, nên cùng luật head được giữ bằng test worker/articles/handler.test.ts cho ba loại trang: hub, category, bài.
Kết quả lượt rà 03.10.2026
Đo trên bản build của origin/main trước khi sửa: 639 trang, 577 trang tự canonical, 577 URL sitemap. Phần lớn nền đã đúng từ các lượt trước (SRC-694, SRC-1150, SRC-1190): canonical, sitemap chỉ chứa bản chính, một h1, JSON-LD.
| Lỗi | Trước | Sau |
|---|---|---|
Link nội bộ gãy (/speak/resources trỏ /speak/nemo-walk) | 1 | 0 |
Link nội bộ đi qua 301 (/start trỏ /courses/ielts-writing) | 1 | 0 |
| Trang gốc chương trình có tiêu đề của MỤC TIÊU ("IELTS · Luyện IELTS, SAT, Grammar") | 10 | 0 |
| Trang chương trình dùng ảnh chia sẻ chung thay vì ảnh của chương trình | mọi trang | 0 |
Trang gốc chương trình không khai Course trong JSON-LD | 14 | 0 |
| Cảnh báo độ dài tiêu đề/mô tả (chủ yếu mô tả sinh tự động quá ngắn) | 83 | 7 |
| Trang learn có thẻ chia sẻ riêng | 0/11 | 11/11 |
Bảy cảnh báo còn lại là chữ viết tay, để chủ dự án quyết nếu muốn: mô tả /ap/courses (204 ký tự), /speak/curriculum (171), /speak/resources (182), /teachers/tefl/compare (181), dòng /parents/ap (63, dùng chung với thẻ trên trang mục tiêu), và /login (noindex, không quan trọng).