Tôi build website Astro hai repository và deploy GitHub Pages như thế nào
Tôi giữ nội dung và mã nguồn ở một nơi, còn repository public chỉ nhận artifact tĩnh đã qua kiểm tra.
Website tĩnh được vận hành theo mô hình hai repository: mã nguồn được kiểm thử nghiêm ngặt tại source repo trước khi đưa artifact đã build sang GitHub Pages.
🧭 Mô hình tổng thể
Source Repo (Markdown + TS)
|
v (npm run build)
Astro Static Engine -> dist/
|
v (GitHub Actions)
Public Repo (<user>.github.io)
Quy trình vận hành tách rời làm hai vùng độc lập: toàn bộ bài viết Markdown, mã nguồn component và kiểm thử được quản lý tại source repository. Máy chủ CI chạy build tạo thư mục tệp tĩnh dist/, sau đó đẩy artifact duy nhất này sang public repository để GitHub Pages phân phối trực tiếp tới người đọc.
🌌 WHAT — Astro là gì?
Truyền thống:
Browser <-- Tải JS App <-- Server / DB
Astro Static:
Browser <-- HTML tĩnh <-- Build ở CI
Astro là web framework mã nguồn mở tối ưu cho các website nội dung. Với website này, Astro đóng vai trò bộ biên dịch tĩnh (static compiler): đọc Markdown, TypeScript, component rồi kết xuất toàn bộ thành HTML, CSS và JavaScript tối thiểu trong thư mục dist/.
Hệ thống không sử dụng server runtime hay database ở phía người dùng. Astro công bố năm 2021 triết lý render HTML tĩnh mặc định, loại bỏ hoàn toàn client runtime không cần thiết. Astro Islands
💡 WHY — Vì sao chọn Astro cho website này?
+------------------------------------+
| Trang đọc bài (HTML + CSS tĩnh) |
| +----------------------------+ |
| | Island JS: Search / Filter | |
| +----------------------------+ |
+------------------------------------+
Kiến trúc này giải quyết đúng các đòi hỏi cốt lõi của website tri thức:
- Tải tức thì & SEO: Trình duyệt nhận trực tiếp HTML hoàn chỉnh, tối ưu tốc độ và vẫn đọc tốt khi tắt JavaScript.
- Islands Architecture: Chỉ tải JavaScript cho các vùng cần tương tác động (Pagefind search, đồ thị), không kéo theo runtime cho toàn trang.
- Chi phí vận hành 0 đồng: Tệp tĩnh tương thích tối đa với CDN GitHub Pages, dễ cache, bảo mật cao và không phát sinh chi phí server. Static rendering và Astro 7 build mechanism
🛠️ HOW — Vận hành hai repository và GitHub Pages
🧰 Tech stack
| Lớp | Công nghệ | Vai trò |
|---|---|---|
| Site generator | Astro 7, static output | Render HTML tĩnh từ content và component. |
| Ngôn ngữ | TypeScript strict | Kiểm tra type cho code build-time. |
| Content | Markdown + Astro Content Collections | Giữ bài viết trong Git và validate frontmatter. |
| Markdown pipeline | @astrojs/markdown-satteri, remark/rehype |
Link base-path-safe, heading anchor và bảo vệ Markdown. |
| Search | Pagefind | Lập index tiếng Việt sau build, không cần service search. |
| UI | CSS thuần | Ít dependency, HTML vẫn đọc được khi tắt JavaScript. |
| Quality | Prettier, ESLint, Vitest, Playwright, axe, Lighthouse | Format, lint, unit/E2E/a11y và kiểm tra hiệu năng. |
| CI/CD | GitHub Actions + GitHub Pages | Kiểm tra source và xuất artifact. |
Node 24 và npm 11 được cố định phiên bản để môi trường local và CI hoàn toàn đồng nhất.
🗂️ Phân chia hai repository
source-repo/ # Nguồn làm việc
├── content/ (Markdown)
├── src/ + tests/
└── .github/workflows/
public-repo/ # GitHub Pages
└── dist/ (HTML, CSS, JS, Search)
Mô hình tách biệt giúp giữ sạch repository public: người đọc chỉ tiếp cận tệp tĩnh đã build, không lộ mã nguồn nháp, kịch bản test hay cấu hình vận hành nội bộ.
1. Khởi tạo và cấu hình base path
User site:
domain.github.io/ -> base = '/'
Project site:
domain.github.io/repo/ -> base = '/repo/'
File cấu hình astro.config.mjs nhận BASE_PATH từ biến môi trường để linh hoạt giữa chạy máy local và deploy Pages:
import { defineConfig } from 'astro/config';
const base = process.env.BASE_PATH || '/my-site';
const site = process.env.SITE_URL || 'http://localhost:4321';
export default defineConfig({
output: 'static',
site,
base,
trailingSlash: 'always',
});
Lưu ý: Mọi liên kết nội bộ phải đi qua helper base path của Astro, không được hard-code dạng /assets/... vì sẽ gây lỗi 404 trên project subpath.
2. Quản lý nội dung Markdown làm nguồn chân lý
draft/ -> Soạn thảo (không build)
|
reviewing/ -> Rà soát chất lượng
|
done/ -> Canonical (sync sang src/)
Hệ thống quản lý 5 domain bài viết: 1.ufo/, 2.meta/, 3.ai/, 4.soul/ và 5.ling/. Chỉ các bài đã duyệt ở done/ mới được đồng bộ vào src/content/articles/ để build. Không bao giờ chỉnh sửa trực tiếp vào thư mục projection.
3. Chuẩn hóa script local trước khi lên CI
npm run validate -> Lint, typecheck, schema
|
npm test -> Unit test logic
|
npm run build -> Build static + Pagefind
|
npm run preview -> Soát dist/ ở local
Đây là hợp đồng nghiêm ngặt: mọi kịch bản phải chạy pass 100% ở máy local trước khi đưa vào pipeline tự động của CI.
4. Thiết lập token và bảo mật deploy
Source Repo (chứa DEPLOY_TOKEN)
|
v (Ghi artifact dist/ sang)
Public Repo (không lộ token hay source)
- Bật tính năng GitHub Pages trên public repo (ví dụ:
<account>.github.io). - Khởi tạo token chỉ có quyền ghi
contentsvào public repo đó. - Lưu token vào GitHub Secrets của source repo dưới tên
DEPLOY_TOKEN. - Cấu hình biến môi trường
SITE_URLvàBASE_PATHtương ứng trong workflow.
5. Workflow GitHub Actions deploy
name: Build and deploy GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages-deploy
cancel-in-progress: false
jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
BASE_PATH: /
SITE_URL: https://<account>.github.io
ASTRO_TELEMETRY_DISABLED: '1'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run validate
- run: npm test
- run: npm run build
- name: Publish only the static artifact
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.DEPLOY_TOKEN }}
external_repository: <account>/<account>.github.io
publish_branch: main
publish_dir: ./dist
force_orphan: true
Lệnh npm ci bảo đảm chính xác phiên bản dependency từ lockfile. Cờ force_orphan: true giúp public repo chỉ lưu snapshot bản build mới nhất, không làm phình lịch sử Git.
6. Pull Request Gate trước khi Merge
PR Branch
|
+-> validate + test + build preview
| (Merge khi đạt)
Main Branch
|
+-> deploy workflow -> Public Repo
Workflow PR kiểm tra toàn diện chất lượng nhưng không được cấp quyền DEPLOY_TOKEN. Chỉ khi PR được merge vào nhánh main, pipeline deploy mới nhận secret để đẩy bản phát hành.
📦 Phạm vi tính năng và lộ trình
Lộ trình phát triển được phân rã thành các lát cắt độc lập, bảo đảm tính toàn vẹn của nền tảng publish trước khi mở rộng tính năng:
| Nhóm | Trạng thái hôm nay | Giá trị và lý do tách riêng |
|---|---|---|
| Phase 1 nền tảng | Đã có | Astro static, content collection, route đọc bài, base path, GitHub Pages và quality gate tạo nền publish an toàn trước khi thêm UI nâng cao. |
| v0.2–v0.6: tag, phân loại, series | Đã có | Tách taxonomy khỏi prose để search/discovery có quy tắc, nhưng không tự kết luận nội dung nghiên cứu là đúng hay sai. |
| v0.7–v0.9: thời gian đọc, black tag, search | Đã có | Cải thiện khả năng đọc và tìm, vẫn giữ HTML/no-JS fallback làm trải nghiệm nền. |
| v1.0: Lighthouse và K6 | Đã có | Public snapshot hiệu năng có điều kiện rõ ràng; tách lab audit và controlled load report để không nhầm chúng với trải nghiệm thực tế của mọi người dùng. |
v1.1: check-done |
Đã chốt, chưa phát triển | Command chuẩn bị publish giảm lỗi cơ học khi move bài vào done/; tách khỏi review biên tập vì script không được suy diễn hay sửa dữ kiện. |
| v1.2: lịch publish | Đã chốt, chờ ADR/review trước khi phát triển | Tách trạng thái plan khỏi published, để bài đã review có thể chờ ngày public mà không lộ sớm; đây là thay đổi schema/workflow nên cần quyết định kiến trúc rõ. |
| v1.3: Playwright public report | Đang hoàn thiện | Tách kết quả E2E thành snapshot/report tĩnh để minh bạch chất lượng UI mà không chạy test trên trình duyệt người đọc. |
- Đã có: Tính năng đã merge và chạy ổn định trong source.
- Đang hoàn thiện: Đang triển khai trong worktree hiện tại.
- Đã chốt: Đã duyệt thiết kế kiến trúc, sẵn sàng triển khai tiếp theo.
🔍 Khắc phục sự cố thường gặp
| Triệu chứng | Kiểm tra đầu tiên | Cách xử lý |
|---|---|---|
| CSS/asset 404 | BASE_PATH và internal link |
Dùng helper base path; build lại đúng target. |
| Pagefind không có kết quả | Thứ tự Astro build rồi Pagefind | Chạy index sau khi dist/ đã tồn tại. |
| Deploy bị từ chối | DEPLOY_TOKEN, owner/repo, quyền contents |
Cấp quyền tối thiểu đúng public repo. |
| Bài draft lại xuất hiện | Rule chọn thư mục canonical | Chỉ đưa done/ vào pipeline sync. |
| CI khác local | Node, npm, lockfile | Cố định version và dùng npm ci. |
✅ Kết luận
Mô hình hai repository kết hợp cùng Astro static tạo ra sự phân tách rạch ròi: source repo là nơi bảo chứng chất lượng mã nguồn và nội dung qua quality gates, còn public repo là kênh phân phối artifact tĩnh nhẹ, nhanh và an toàn.
Việc ưu tiên HTML tĩnh giúp website tối ưu chi phí vận hành ở mức 0 đồng, loại bỏ rủi ro bảo mật máy chủ mà vẫn sẵn sàng mở rộng các thành phần tương tác động (islands) khi cần thiết.
🎒 Tóm tắt bỏ túi (Take away)
| Điểm chính | Hành động |
|---|---|
| Astro xuất static site | Chọn output: 'static'; không cần backend cho website nội dung. |
| Astro nhẹ ở client | Để phần đọc là HTML/CSS; chỉ hydrate island thật sự cần tương tác. |
| Markdown là canonical | Tách draft/, reviewing/, done/; chỉ build từ vùng đã duyệt. |
| Hai repo có ranh giới rõ | Source chứa code; public repo chỉ nhận dist/. |
| CI dùng cùng script local | Chạy npm ci, validate, test, build theo thứ tự. |
| Pages dễ lỗi base path | Dùng BASE_PATH và kiểm tra trên URL deploy thật. |
| Secret không vào source | Lưu token deploy trong GitHub Secrets, giới hạn quyền vào public repo. |
| Roadmap không phải claim | Ghi rõ đã có, đang làm và đã chốt trước khi nói feature hoàn thành. |