Skip to content

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: dist

Phầ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 teamflow

Step 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 →

Powered by CodyMaster × VitePress