Chương 17: CI/CD — Từ Code Đến Cloudflare

"Tuấn: 'Deploy bằng tay: SSH → git pull → npm build → pm2 restart → cầu nguyện.' Đức: 'Từ nay: git push → CI chạy test → auto deploy → thông báo Slack. Không cần cầu nguyện.'"
🎯 Mục tiêu
- GitHub Actions CI/CD pipeline
- 8-gate deploy process
- Cloudflare Pages deployment
- 🐛 Bug #10: Missing error boundary, Bug #15: Memory leak
Phần 1: 8-Gate Pipeline (25 phút)
yaml
# .github/workflows/deploy.yml
name: Deploy Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
gate-1-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run lint
gate-2-typecheck:
needs: gate-1-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx tsc --noEmit
gate-3-unit-tests:
needs: gate-2-typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run test:gate
gate-4-security:
needs: gate-3-unit-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm audit --production
- run: npx secretlint "**/*"
gate-5-build:
needs: gate-4-security
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run build
gate-6-deploy:
needs: gate-5-build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci && npm run build
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
projectName: teamflow
directory: distPhần 2: Cloudflare Pages Setup (25 phút)
Step 1: Create Cloudflare Project
bash
# Install Wrangler
npm install -g wrangler
# Login
wrangler login
# Create Pages project
wrangler pages project create teamflow
# Deploy manually first
wrangler pages deploy dist --project-name teamflowStep 2: Environment Variables
bash
# Set secrets in Cloudflare Dashboard → Pages → Settings → Environment
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=eyJ...Step 3: Custom Domain
Cloudflare Dashboard → Pages → teamflow → Custom domains
→ Add: teamflow.yourdomain.com
→ DNS automatically configured ✅Phần 3: Bug Fixes (25 phút)
Bug #10: Missing Error Boundary
javascript
// 🐛 BUG: Unhandled errors crash the entire app
// No global error handler for fetch failures
// ✅ Fix: Global error boundary
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason)
showToast('Something went wrong. Please try again.', 'error')
event.preventDefault()
})
// Also: wrap all fetch calls
async function safeFetch(url, options) {
try {
const res = await fetch(url, options)
if (!res.ok) {
const error = await res.json()
throw new Error(error.error || 'Request failed')
}
return await res.json()
} catch (err) {
showToast(err.message, 'error')
throw err
}
}Bug #15: Memory Leak in WebSocket
javascript
// 🐛 BUG: Supabase channels never unsubscribed on page leave
// → Memory leak, zombie connections
// ✅ Fix: Cleanup on navigation
let activeChannel = null
function subscribeToBoard(projectId) {
// Clean up previous subscription
if (activeChannel) {
supabase.removeChannel(activeChannel)
}
activeChannel = supabase
.channel(`board-${projectId}`)
.on('postgres_changes', { event: '*', schema: 'public', table: 'tasks' }, handleChange)
.subscribe()
}
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (activeChannel) supabase.removeChannel(activeChannel)
})Phần 4: Deploy Verification (15 phút)
bash
# Identity guard: make sure deploying to correct account
antigravity "@cm-identity-guard Verify deploy target:
- GitHub repo: correct org
- Cloudflare: correct account
- Supabase: correct project
- No cross-account contamination"
# Post-deploy smoke test
antigravity "@cm-post-deploy-canary Run smoke tests on https://teamflow.yourdomain.com:
1. Health check returns 200
2. Login page renders
3. Static assets load
4. API responds"Homework
- [ ] CI/CD pipeline running
- [ ] Cloudflare Pages deployed
- [ ] All 8 gates passing
- [ ] Bugs #10 and #15 fixed
Chương tiếp: Landing Page & CRO →