Deploy — Rules and Conventions
1. Philosophy
- Platform-agnostic build — Same build output deploys anywhere. Platform config only.
- Immutable deployments — Every deploy = unique URL. Rollback = switch URL.
- Env vars in platform — Never in repo. Platform dashboard or secrets manager.
- Preview deploys on PR — Every PR gets a preview URL. Review before merge.
- Observability built-in — Logs, metrics, alerts from day one.
2. Minimum Versions
| Technology |
Minimum Version |
| Node.js |
22+ |
| pnpm |
11+ |
| Git |
2.43+ |
| GitHub CLI |
2.40+ |
3. Preparation for Deploy
Build locally first
# Type check
pnpm typecheck
# Lint + format check
pnpm lint
pnpm format:check
# Build
pnpm build
# Verify output
ls -la dist/ # or .vercel/output, .netlify, etc.
Git hygiene
# Ensure clean working tree
git status
# Tag release (from main)
git switch main
git pull origin main
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0
4. Platform Strategy
| Platform |
Best For |
Static/SSR |
Config File |
| Vercel |
Next.js, Astro SSR, React SPA |
Both |
vercel.json |
| Netlify |
Astro, Vite, static sites |
Both |
netlify.toml |
| GitHub Pages |
Static only (Astro SSG, Vite SPA) |
Static only |
.github/workflows/pages.yml |
| Cloudflare Pages |
Static + edge functions |
Both |
wrangler.toml / dashboard |
Rule: Pick ONE primary platform. Others only for specific
needs (edge, cost, team preference).
5. Vercel (Canonical SSR/Static)
vercel.json
{
"buildCommand": "pnpm build",
"outputDirectory": "dist",
"devCommand": "pnpm dev",
"installCommand": "pnpm install",
"framework": "astro",
"regions": ["iad1"],
"functions": {
"src/pages/api/**/*.ts": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
Vercel CLI
# Preview deploy (auto on PR)
vercel
# Production deploy
vercel --prod
# List deployments
vercel ls
# Rollback
vercel rollback <deployment-url>
Rules
framework: "astro" — auto-detects build config
outputDirectory: "dist" — matches Astro/Vite default
- Headers — cache static assets long, HTML short
- Rewrites — SPA fallback for client-side routing
6. Netlify (Canonical Static/Hybrid)
netlify.toml
[build]
command = "pnpm build"
publish = "dist"
functions = "netlify/functions"
[build.environment]
NODE_VERSION = "22"
PNPM_VERSION = "11"
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
conditions = {Role = ["admin"]} # SPA fallback
[functions]
node_bundler = "esbuild"
included_files = ["src/**/*"]
Netlify CLI
# Preview deploy
netlify deploy
# Production deploy
netlify deploy --prod
# Rollback
netlify rollback <deploy-id>
Rules Nettly
publish = "dist" — matches Astro/Vite default
- Edge functions in
netlify/functions/ — see Netlify docs
- Headers — security headers + asset caching
- Redirects — SPA fallback with 200 status
7. GitHub Pages (Static Only)
Workflow (.github/workflows/pages.yml)
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
Rules GH
- Static only — no SSR, no server functions
output: "static" in Astro config, or Vite SPA
- Custom domain — configure in repo settings + DNS
- HTTPS enforced — automatic
8. Build Optimization
Build config owned by build tools — this skill references them.
| Tool |
Skill |
Key Optimizations |
| Vite |
vite |
minify: 'esbuild', cssCodeSplit, manualChunks, assetsInlineLimit |
| esbuild |
esbuild |
target: 'es2022', splitting, treeShaking, minify |
| Astro |
astro |
compressHTML, imageService, prefetch |
Universal rules
target: 'es2022' — modern browsers, smaller bundles
- Code splitting — vendor chunks + route lazy loading
- Asset hashing — automatic via Vite/esbuild/Astro
- Compression — gzip/brotli at platform level (enable in dashboard)
9. Environment Variables
Env conventions owned by package-manager, vite, astro — this skill defines platform mapping.
Platform mapping
Local (.env) |
Vercel |
Netlify |
GitHub Pages |
VITE_API_URL |
VITE_API_URL |
VITE_API_URL |
VITE_API_URL (build-time) |
SECRET_DB_URL |
SECRET_DB_URL (encrypted) |
SECRET_DB_URL (encrypted) |
❌ Not supported |
PUBLIC_* (Astro) |
PUBLIC_* |
PUBLIC_* |
PUBLIC_* (build-time) |
Rules Environment Variables
- Never commit
.env — .gitignore includes .env*
.env.example committed — template for contributors
- Platform dashboard — set production vars there
- Preview deploys — inherit production vars, override per PR if needed
- Build-time vs runtime — Astro
PUBLIC_/Vite VITE_ = build-time; secrets = runtime (SSR only)
10. CI/CD
CI owned by linting, package-manager — this skill
defines deploy pipeline.
Minimal deploy workflow (.github/workflows/deploy.yml)
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm format:check
- run: pnpm test
- run: pnpm build
deploy-preview:
needs: check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Platform-specific preview deploy (Vercel/Netlify action)
- uses: amondnet/vercel-action@v25
if: vars.VERCEL_TOKEN != ''
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--scope=${{ secrets.VERCEL_ORG_ID }}"
deploy-production:
needs: check
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Platform-specific production deploy
- uses: amondnet/vercel-action@v25
if: vars.VERCEL_TOKEN != ''
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: "--prod --scope=${{ secrets.VERCEL_ORG_ID }}"
Rules CI/CD
- Checks first — typecheck, lint, format, test, build MUST pass
- Preview on PR — automatic preview URL for review
- Production on main push — only after checks pass
- Secrets in GitHub —
VERCEL_TOKEN, NETLIFY_AUTH_TOKEN, etc.
11. Pre-Deploy Checklist
12. Rollback
Git-based (all platforms)
# Tag current production
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0
# Rollback to previous tag
git tag -d v1.2.0
git push origin :refs/tags/v1.2.0
git push origin v1.1.0:main --force-with-lease
Platform-based
| Platform |
Command |
| Vercel |
vercel rollback <deployment-url> or dashboard |
| Netlify |
netlify rollback <deploy-id> or dashboard |
| GitHub Pages |
Re-deploy previous workflow run |
| Cloudflare |
Dashboard → Deployments → Rollback |
Rules Rollback
- Git tag every release — enables git-based rollback
- Platform rollback — faster, preserves git history
- Force-push main only with lease —
git push --force-with-lease
13. Methodology
Before using ANY deploy config/pattern not documented in this skill:
- MCP Context7 (priority):
context7_resolve-library-id + context7_query-docs for Vercel, Netlify, GitHub Actions, etc.
- Official docs: vercel.com/docs, netlify.com/docs, GitHub Actions — verify current config.
- Project config:
vercel.json, netlify.toml, .github/workflows/*.yml — verify against actual setup.
- HARD RULE: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.
14. Prohibitions
- ❌ Do not commit
.env or secrets — use platform dashboard
- ❌ Do not deploy without passing checks (typecheck, lint, test, build)
- ❌ Do not use different build commands locally vs CI
- ❌ Do not skip preview deploy on PR
- ❌ Do not hardcode platform URLs in code — use env vars
- ❌ Do not disable HTTPS — all platforms enforce it
- ❌ Do not deploy
node_modules — build output only
- ❌ Do not skip security headers — configure in platform config
15. References
Note: For Git conventions, see Git
Note: For package manager conventions, see Package Manager
Note: For Vite build config, see Vite
Note: For esbuild config, see esbuild
Note: For Astro config, see Astro
Note: For Linting/CI, see Linting
Last updated: 2026-08
1---2name: deploy3description: Deployment rules - GitHub Pages, Vercel, Netlify, build optimization, CI/CD, env vars, custom domains4---56# Deploy — Rules and Conventions78---910## 1. Philosophy11121. **Platform-agnostic build** — Same build output deploys anywhere. Platform config only.132. **Immutable deployments** — Every deploy = unique URL. Rollback = switch URL.143. **Env vars in platform** — Never in repo. Platform dashboard or secrets manager.154. **Preview deploys on PR** — Every PR gets a preview URL. Review before merge.165. **Observability built-in** — Logs, metrics, alerts from day one.1718---1920## 2. Minimum Versions2122| Technology | Minimum Version |23| ---------- | --------------- |24| Node.js | 22+ |25| pnpm | 11+ |26| Git | 2.43+ |27| GitHub CLI | 2.40+ |2829---3031## 3. Preparation for Deploy3233### Build locally first3435```bash36# Type check37pnpm typecheck3839# Lint + format check40pnpm lint41pnpm format:check4243# Build44pnpm build4546# Verify output47ls -la dist/ # or .vercel/output, .netlify, etc.48```4950### Git hygiene5152```bash53# Ensure clean working tree54git status5556# Tag release (from main)57git switch main58git pull origin main59git tag -a v1.2.0 -m "Release v1.2.0"60git push origin v1.2.061```6263---6465## 4. Platform Strategy6667| Platform | Best For | Static/SSR | Config File |68| -------------------- | --------------------------------- | ----------- | ----------------------------- |69| **Vercel** | Next.js, Astro SSR, React SPA | Both | `vercel.json` |70| **Netlify** | Astro, Vite, static sites | Both | `netlify.toml` |71| **GitHub Pages** | Static only (Astro SSG, Vite SPA) | Static only | `.github/workflows/pages.yml` |72| **Cloudflare Pages** | Static + edge functions | Both | `wrangler.toml` / dashboard |7374> **Rule**: Pick ONE primary platform. Others only for specific75> needs (edge, cost, team preference).7677---7879## 5. Vercel (Canonical SSR/Static)8081### `vercel.json`8283```json84{85 "buildCommand": "pnpm build",86 "outputDirectory": "dist",87 "devCommand": "pnpm dev",88 "installCommand": "pnpm install",89 "framework": "astro",90 "regions": ["iad1"],91 "functions": {92 "src/pages/api/**/*.ts": {93 "maxDuration": 3094 }95 },96 "headers": [97 {98 "source": "/assets/(.*)",99 "headers": [100 {101 "key": "Cache-Control",102 "value": "public, max-age=31536000, immutable"103 }104 ]105 }106 ],107 "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]108}109```110111### Vercel CLI112113```bash114# Preview deploy (auto on PR)115vercel116117# Production deploy118vercel --prod119120# List deployments121vercel ls122123# Rollback124vercel rollback <deployment-url>125```126127### Rules128129- **`framework: "astro"`** — auto-detects build config130- **`outputDirectory: "dist"`** — matches Astro/Vite default131- **Headers** — cache static assets long, HTML short132- **Rewrites** — SPA fallback for client-side routing133134---135136## 6. Netlify (Canonical Static/Hybrid)137138### `netlify.toml`139140```toml141[build]142 command = "pnpm build"143 publish = "dist"144 functions = "netlify/functions"145146[build.environment]147 NODE_VERSION = "22"148 PNPM_VERSION = "11"149150[[headers]]151 for = "/assets/*"152 [headers.values]153 Cache-Control = "public, max-age=31536000, immutable"154155[[headers]]156 for = "/*"157 [headers.values]158 X-Frame-Options = "DENY"159 X-Content-Type-Options = "nosniff"160 Referrer-Policy = "strict-origin-when-cross-origin"161162[[redirects]]163 from = "/*"164 to = "/index.html"165 status = 200166 conditions = {Role = ["admin"]} # SPA fallback167168[functions]169 node_bundler = "esbuild"170 included_files = ["src/**/*"]171```172173### Netlify CLI174175```bash176# Preview deploy177netlify deploy178179# Production deploy180netlify deploy --prod181182# Rollback183netlify rollback <deploy-id>184```185186### Rules Nettly187188- **`publish = "dist"`** — matches Astro/Vite default189- **Edge functions** in `netlify/functions/` — see Netlify docs190- **Headers** — security headers + asset caching191- **Redirects** — SPA fallback with 200 status192193---194195## 7. GitHub Pages (Static Only)196197### Workflow (`.github/workflows/pages.yml`)198199```yaml200name: Deploy to GitHub Pages201202on:203 push:204 branches: [main]205 workflow_dispatch:206207permissions:208 contents: read209 pages: write210 id-token: write211212concurrency:213 group: "pages"214 cancel-in-progress: true215216jobs:217 build:218 runs-on: ubuntu-latest219 steps:220 - uses: actions/checkout@v4221 - uses: actions/setup-node@v4222 with:223 node-version: 22224 cache: "pnpm"225 - run: pnpm install --frozen-lockfile226 - run: pnpm build227 - uses: actions/upload-pages-artifact@v3228 with:229 path: dist230231 deploy:232 needs: build233 runs-on: ubuntu-latest234 environment:235 name: github-pages236 url: ${{ steps.deployment.outputs.page_url }}237 steps:238 - id: deployment239 uses: actions/deploy-pages@v4240```241242### Rules GH243244- **Static only** — no SSR, no server functions245- **`output: "static"`** in Astro config, or Vite SPA246- **Custom domain** — configure in repo settings + DNS247- **HTTPS enforced** — automatic248249---250251## 8. Build Optimization252253> **Build config owned by build tools** — this skill references them.254255| Tool | Skill | Key Optimizations |256| ------- | --------- | ------------------------------------------------------------------------ |257| Vite | `vite` | `minify: 'esbuild'`, `cssCodeSplit`, `manualChunks`, `assetsInlineLimit` |258| esbuild | `esbuild` | `target: 'es2022'`, `splitting`, `treeShaking`, `minify` |259| Astro | `astro` | `compressHTML`, `imageService`, `prefetch` |260261### Universal rules262263- **`target: 'es2022'`** — modern browsers, smaller bundles264- **Code splitting** — vendor chunks + route lazy loading265- **Asset hashing** — automatic via Vite/esbuild/Astro266- **Compression** — gzip/brotli at platform level (enable in dashboard)267268---269270## 9. Environment Variables271272> **Env conventions owned by `package-manager`, `vite`, `astro`** — this skill defines platform mapping.273274### Platform mapping275276| Local (`.env`) | Vercel | Netlify | GitHub Pages |277| ------------------ | --------------------------- | --------------------------- | --------------------------- |278| `VITE_API_URL` | `VITE_API_URL` | `VITE_API_URL` | `VITE_API_URL` (build-time) |279| `SECRET_DB_URL` | `SECRET_DB_URL` (encrypted) | `SECRET_DB_URL` (encrypted) | ❌ Not supported |280| `PUBLIC_*` (Astro) | `PUBLIC_*` | `PUBLIC_*` | `PUBLIC_*` (build-time) |281282### Rules Environment Variables283284- **Never commit `.env`** — `.gitignore` includes `.env*`285- **`.env.example` committed** — template for contributors286- **Platform dashboard** — set production vars there287- **Preview deploys** — inherit production vars, override per PR if needed288- **Build-time vs runtime** — Astro `PUBLIC_`/Vite `VITE_` = build-time; secrets = runtime (SSR only)289290---291292## 10. CI/CD293294> **CI owned by `linting`, `package-manager`** — this skill295> defines deploy pipeline.296297### Minimal deploy workflow (`.github/workflows/deploy.yml`)298299```yaml300name: Deploy301302on:303 push:304 branches: [main]305 pull_request:306 branches: [main]307308jobs:309 check:310 runs-on: ubuntu-latest311 steps:312 - uses: actions/checkout@v4313 - uses: actions/setup-node@v4314 with:315 node-version: 22316 cache: "pnpm"317 - run: pnpm install --frozen-lockfile318 - run: pnpm typecheck319 - run: pnpm lint320 - run: pnpm format:check321 - run: pnpm test322 - run: pnpm build323324 deploy-preview:325 needs: check326 if: github.event_name == 'pull_request'327 runs-on: ubuntu-latest328 steps:329 - uses: actions/checkout@v4330 # Platform-specific preview deploy (Vercel/Netlify action)331 - uses: amondnet/vercel-action@v25332 if: vars.VERCEL_TOKEN != ''333 with:334 vercel-token: ${{ secrets.VERCEL_TOKEN }}335 vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}336 vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}337 vercel-args: "--scope=${{ secrets.VERCEL_ORG_ID }}"338339 deploy-production:340 needs: check341 if: github.event_name == 'push' && github.ref == 'refs/heads/main'342 runs-on: ubuntu-latest343 steps:344 - uses: actions/checkout@v4345 # Platform-specific production deploy346 - uses: amondnet/vercel-action@v25347 if: vars.VERCEL_TOKEN != ''348 with:349 vercel-token: ${{ secrets.VERCEL_TOKEN }}350 vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}351 vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}352 vercel-args: "--prod --scope=${{ secrets.VERCEL_ORG_ID }}"353```354355### Rules CI/CD356357- **Checks first** — typecheck, lint, format, test, build MUST pass358- **Preview on PR** — automatic preview URL for review359- **Production on main push** — only after checks pass360- **Secrets in GitHub** — `VERCEL_TOKEN`, `NETLIFY_AUTH_TOKEN`, etc.361362---363364## 11. Pre-Deploy Checklist365366- [ ] `pnpm typecheck` passes367- [ ] `pnpm lint` passes (zero errors)368- [ ] `pnpm format:check` passes369- [ ] `pnpm test` passes370- [ ] `pnpm build` succeeds locally371- [ ] Env vars set in platform dashboard372- [ ] Custom domain configured (if applicable)373- [ ] Preview deploy verified on PR374- [ ] CHANGELOG updated for release375- [ ] Git tag created for release376377---378379## 12. Rollback380381### Git-based (all platforms)382383```bash384# Tag current production385git tag -a v1.2.0 -m "Release v1.2.0"386git push origin v1.2.0387388# Rollback to previous tag389git tag -d v1.2.0390git push origin :refs/tags/v1.2.0391git push origin v1.1.0:main --force-with-lease392```393394### Platform-based395396| Platform | Command |397| ------------ | ----------------------------------------------- |398| Vercel | `vercel rollback <deployment-url>` or dashboard |399| Netlify | `netlify rollback <deploy-id>` or dashboard |400| GitHub Pages | Re-deploy previous workflow run |401| Cloudflare | Dashboard → Deployments → Rollback |402403### Rules Rollback404405- **Git tag every release** — enables git-based rollback406- **Platform rollback** — faster, preserves git history407- **Force-push main only with lease** — `git push --force-with-lease`408409---410411## 13. Methodology412413Before using ANY deploy config/pattern not documented in this skill:4144151. **MCP Context7** (priority): `context7_resolve-library-id` + `context7_query-docs` for Vercel, Netlify, GitHub Actions, etc.4162. **Official docs**: vercel.com/docs, netlify.com/docs, GitHub Actions — verify current config.4173. **Project config**: `vercel.json`, `netlify.toml`, `.github/workflows/*.yml` — verify against actual setup.4184. **HARD RULE**: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.419420---421422## 14. Prohibitions423424- ❌ Do not commit `.env` or secrets — use platform dashboard425- ❌ Do not deploy without passing checks (typecheck, lint, test, build)426- ❌ Do not use different build commands locally vs CI427- ❌ Do not skip preview deploy on PR428- ❌ Do not hardcode platform URLs in code — use env vars429- ❌ Do not disable HTTPS — all platforms enforce it430- ❌ Do not deploy `node_modules` — build output only431- ❌ Do not skip security headers — configure in platform config432433---434435## 15. References436437> **Note:** For Git conventions, see [Git](../git/SKILL.md)438> **Note:** For package manager conventions, see [Package Manager](../package-manager/SKILL.md)439> **Note:** For Vite build config, see [Vite](../vite/SKILL.md)440> **Note:** For esbuild config, see [esbuild](../esbuild/SKILL.md)441> **Note:** For Astro config, see [Astro](../astro/SKILL.md)442> **Note:** For Linting/CI, see [Linting](../linting/SKILL.md)443444---445446Last updated: 2026-08