| name | vercel-github-actions |
| description | Vercel deployment and GitHub Actions CI/CD patterns. Use when deploying to Vercel, managing environments, creating staging URLs, or setting up GitHub Actions workflows. |
Vercel + GitHub Actions Skill
Patterns for deploying to Vercel with GitHub Actions CI/CD pipelines.
Three-Environment Model
| Environment | Branch | Purpose |
|---|---|---|
| Preview | feature/* |
Auto-generated per PR, ephemeral |
| Staging | staging |
Persistent URL for UAT testing |
| Production | main |
Live user-facing deployment |
Vercel CLI Commands
Authentication & Setup
# Login to Vercel
vercel login
# Link project to Vercel (creates .vercel/project.json)
vercel link
# Pull environment variables to local .env.local
vercel env pull
vercel env pull --environment=production
vercel env pull --environment=preview
Deployments
# Deploy to preview (default)
vercel
# Deploy to production
vercel --prod
# Deploy prebuilt artifacts (faster, for CI)
vercel deploy --prebuilt
vercel deploy --prebuilt --prod
# Build locally for prebuilt deploy
vercel build
vercel build --prod
Staging Alias Management
Vercel aliases create persistent URLs pointing to specific deployments.
# Create staging alias (one-time setup)
vercel alias <deployment-url> <project>-staging.vercel.app
# Example:
vercel alias my-app-abc123-team.vercel.app my-app-staging.vercel.app
# Update alias after new staging deploy
vercel alias <new-deployment-url> <project>-staging.vercel.app
# List current aliases
vercel alias ls
# Remove an alias
vercel alias rm <alias-url>
Important: Aliases point to specific deployments, not branches. After each staging branch deploy, update the alias manually or via CI.
Rollback & Promotion
# Rollback production to previous deployment
vercel rollback
# Rollback to specific deployment
vercel rollback <deployment-id>
# Promote a preview deployment to production
vercel promote <deployment-url>
# List deployments
vercel ls
vercel ls --prod
Domains
# Add custom domain
vercel domains add <domain>
# List domains
vercel domains ls
# Inspect domain configuration
vercel domains inspect <domain>
GitHub Actions Workflow Patterns
Required Secrets
Configure these in GitHub Repository Settings → Secrets and variables → Actions:
| Secret | Source | Purpose |
|---|---|---|
VERCEL_TOKEN |
Vercel Dashboard → Settings → Tokens | CLI authentication |
VERCEL_ORG_ID |
.vercel/project.json after vercel link |
Team identifier |
VERCEL_PROJECT_ID |
.vercel/project.json after vercel link |
Project identifier |
Preview Deployment Workflow
# .github/workflows/preview.yml
name: Preview Deployment
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
push:
branches-ignore:
- main
- staging
pull_request:
types: [opened, synchronize, reopened]
jobs:
deploy-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Install Vercel CLI
run: npm install -g vercel
- name: Pull Vercel Environment
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy to Preview
id: deploy
run: |
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> $GITHUB_OUTPUT
- name: Comment PR with Preview URL
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview deployed: ${{ steps.deploy.outputs.url }}`
})
Production Deployment Workflow
# .github/workflows/production.yml
name: Production Deployment
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
deploy-production:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vercel CLI
run: npm install -g vercel
- name: Pull Vercel Environment
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy to Production
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
Staging with Alias Update
# .github/workflows/staging.yml
name: Staging Deployment
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
STAGING_ALIAS: my-project-staging.vercel.app # Customize this
on:
push:
branches:
- staging
jobs:
deploy-staging:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vercel CLI
run: npm install -g vercel
- name: Pull Vercel Environment
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy to Staging
id: deploy
run: |
url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> $GITHUB_OUTPUT
- name: Update Staging Alias
run: vercel alias ${{ steps.deploy.outputs.url }} ${{ env.STAGING_ALIAS }} --token=${{ secrets.VERCEL_TOKEN }}
- name: Output Staging URL
run: echo "Staging deployed to https://${{ env.STAGING_ALIAS }}"
E2E Tests Against Preview
# .github/workflows/e2e.yml
name: E2E Tests
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Build Application
env:
# Add your build-time env vars here (non-secrets only)
NODE_ENV: test
run: npm run build
- name: Run E2E Tests
env:
# Point to localhost for self-hosted tests
PLAYWRIGHT_BASE_URL: http://localhost:4321
run: npx playwright test
- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload Test Videos on Failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/
retention-days: 7
vercel.json Configuration
{
"framework": "astro",
"buildCommand": "npm run build",
"github": {
"enabled": false,
"silent": true
},
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }
]
}
]
}
Note: Set github.enabled: false when using custom GitHub Actions workflows to prevent duplicate deployments.
Common Patterns
Check Deployment Status
# Get latest deployment info
vercel inspect <deployment-url>
# Check if deployment is ready
curl -s -o /dev/null -w "%{http_code}" https://<deployment-url>/api/health
Environment Variable Management
# Add environment variable
vercel env add <NAME> <VALUE> --environment=production
# List environment variables
vercel env ls
# Remove environment variable
vercel env rm <NAME> --environment=production
Conditional Workflow Steps
# Skip job if secrets not configured
- name: Check secrets
id: check
run: |
if [ -z "${{ secrets.SOME_SECRET }}" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Conditional step
if: steps.check.outputs.skip != 'true'
run: echo "Running because secrets are configured"
PR Comment with Deployment URL
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const status = '${{ job.status }}' === 'success' ? '✅' : '❌';
const url = '${{ steps.deploy.outputs.url }}';
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## ${status} Deployment\n\nPreview: ${url}`
});
Troubleshooting
"You don't have access to the domain"
The domain isn't configured in Vercel. Use a .vercel.app subdomain instead:
vercel alias <url> <project>-staging.vercel.app
Build works locally but fails in CI
- Check Node.js version matches (
node -v) - Verify all env vars are set in GitHub Secrets
- Run
vercel env pulland compare with CI environment
Deployment succeeds but site shows errors
- Check if env vars are set for the correct environment (preview vs production)
- Verify build command produces expected output
- Check Vercel function logs:
vercel logs <deployment-url>
Security Reminders
- Never commit
.vercel/project.jsonwith sensitive data - Never echo secrets in workflow logs
- Use GitHub's automatic secret masking
- Rotate
VERCEL_TOKENperiodically - Use environment-specific secrets (TEST_, STAGING_, PROD_ prefixes)
Patterns extracted from production deployments. Last updated: December 2025