CI/CD Integration
Integrate Capyseo into your CI/CD pipeline to catch SEO issues before deployment.
Quick Start
# Run analysis in CI mode
capyseo analyze ./dist --ci --min-score 80
Exit code 1 if score is below threshold.
GitHub Actions
Basic Workflow
# .github/workflows/seo.yml
name: SEO Analysis
on:
push:
branches: [main]
pull_request:
jobs:
seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: Build site
run: bun run build
- name: SEO Analysis
run: bunx @capyseo/cli analyze ./dist --ci --min-score 80
With AI Analysis
- name: SEO Analysis with AI
run: bunx @capyseo/cli analyze ./dist --ai --ci --min-score 80
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
SARIF Report
Upload results to GitHub Security tab:
- name: SEO Analysis
run: bunx @capyseo/cli analyze ./dist --ci --format sarif -o seo-report.sarif
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: seo-report.sarif
Full Example
name: SEO Analysis
on:
push:
branches: [main]
pull_request:
jobs:
seo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: Build
run: bun run build
- name: SEO Analysis
run: |
bunx @capyseo/cli analyze ./dist \
--ci \
--min-score 80 \
--ai \
--format sarif \
-o seo-report.sarif
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: seo-report.sarif
- name: Upload HTML Report
uses: actions/upload-artifact@v4
if: always()
with:
name: seo-report
path: seo-report.html
GitLab CI
# .gitlab-ci.yml
seo:
stage: test
image: oven/bun:latest
script:
- bun install
- bun run build
- bunx @capyseo/cli analyze ./dist --ci --min-score 80
variables:
GEMINI_API_KEY: $GEMINI_API_KEY
artifacts:
reports:
sast: seo-report.sarif
paths:
- seo-report.html
when: always
Jenkins
// Jenkinsfile
pipeline {
agent any
environment {
GEMINI_API_KEY = credentials('gemini-api-key')
}
stages {
stage('Build') {
steps {
sh 'bun install'
sh 'bun run build'
}
}
stage('SEO Analysis') {
steps {
sh 'bunx @capyseo/cli analyze ./dist --ci --min-score 80'
}
}
}
post {
always {
archiveArtifacts artifacts: 'seo-report.html', allowEmptyArchive: true
}
}
}
CircleCI
# .circleci/config.yml
version: 2.1
jobs:
seo:
docker:
- image: oven/bun:latest
steps:
- checkout
- run: bun install
- run: bun run build
- run:
name: SEO Analysis
command: bunx @capyseo/cli analyze ./dist --ci --min-score 80
- store_artifacts:
path: seo-report.html
workflows:
main:
jobs:
- seo
Exit Codes
| Code |
Meaning |
0 |
Success, score meets threshold |
1 |
Score below threshold or errors found |
2 |
Configuration or file error |
CI Options
capyseo analyze ./dist \
--ci \ # Enable CI mode (required)
--min-score 80 \ # Minimum score (default: 0)
--format sarif \ # Output format
-o report.sarif \ # Output file
--fail-on error \ # Fail on severity
--quiet # Minimal output
--ci
Enables CI mode:
- Non-interactive output
- Exit code based on score
- Suitable for pipelines
--min-score
Minimum acceptable score (0-100):
--min-score 80 # Fail if below 80
--min-score 90 # Stricter threshold
--fail-on
Fail on specific severity:
--fail-on error # Fail only on errors
--fail-on warning # Fail on warnings or errors
--fail-on info # Fail on any issue
--quiet
Minimal output for logs:
--quiet # Only errors and final score
Caching
Cache AI responses to speed up CI:
- uses: actions/cache@v4
with:
path: .capyseo-cache
key: capyseo-${{ hashFiles('dist/**/*.html') }}
restore-keys: capyseo-
Config:
// capyseo.config.js
export default {
ai: {
cacheDir: '.capyseo-cache',
cacheTTL: 86400000, // 24 hours
},
};
Branch Protection
Require SEO checks to pass:
- Go to repository Settings → Branches
- Add branch protection rule for
main
- Enable "Require status checks"
- Select "seo" job
PR Comments
Add SEO summary to pull requests:
- name: SEO Analysis
id: seo
run: |
bunx @capyseo/cli analyze ./dist --ci --format json -o seo.json
SCORE=$(jq '.score' seo.json)
echo "score=$SCORE" >> $GITHUB_OUTPUT
- name: Comment PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const score = '${{ steps.seo.outputs.score }}';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## SEO Score: ${score}/100`
});
Secrets Setup
GitHub
- Go to Settings → Secrets → Actions
- Add
GEMINI_API_KEY (or other provider key)
GitLab
- Go to Settings → CI/CD → Variables
- Add
GEMINI_API_KEY (masked)
Best Practices
- Run on PRs - Catch issues before merge
- Set realistic thresholds - Start at 70, increase over time
- Cache AI responses - Reduce API calls and time
- Upload reports - Archive for debugging
- Use SARIF - Integrate with security dashboards
- Fail fast - Use
--fail-on error for critical issues
Troubleshooting
"Score below threshold"
Error: SEO score 65 is below minimum 80
- Review the report for issues
- Fix critical errors first
- Lower threshold temporarily if needed
"API key not found"
Error: No AI provider configured
- Add secret to CI environment
- Check secret name matches env var
Slow builds
- Enable caching
- Use
--no-ai for faster checks
- Analyze only changed pages
Example Config
// capyseo.config.js
export default {
rules: {
'meta-title': { severity: 'error' },
'meta-description': { severity: 'error' },
'image-alt': { severity: 'warning' },
},
ai: {
enabled: true,
provider: 'gemini',
cacheDir: '.capyseo-cache',
},
ci: {
minScore: 80,
failOn: ['error'],
},
};