CI/CD
Explainer uses a single GitHub Actions workflow for continuous deployment. All three apps (Docs, Blog, Website) are managed in one unified workflow with smart change detection.
Workflow file
| File | Apps | Triggers |
|---|---|---|
.github/workflows/deploy.yml | Docs, Blog, Website | push to main, workflow_dispatch |
Trigger modes
The workflow supports two trigger modes:
- Push to
main— only builds and deploys apps affected by the changed files (path-based filtering) - Manual (
workflow_dispatch) — allows selecting which apps to deploy via checkboxes
on:
push:
branches: [main]
workflow_dispatch:
inputs:
docs:
description: 'Deploy Docs'
type: boolean
default: true
blog:
description: 'Deploy Blog'
type: boolean
default: true
website:
description: 'Deploy Website'
type: boolean
default: true
Change detection
A changes job uses dorny/paths-filter to detect which apps need rebuilding:
| App | Triggers on changes to |
|---|---|
| Docs | apps/docs/**, packages/**, pnpm-lock.yaml |
| Blog | apps/blog/**, packages/**, pnpm-lock.yaml |
| Website | apps/website/**, packages/**, pnpm-lock.yaml |
Changes to packages/** trigger all app builds. This is intentional — shared packages like @explainer/ui or @explainer/mdx affect all apps, so they all need to be rebuilt and deployed.
Build and deploy pattern
Each app follows a two-stage pattern: build then deploy. Build jobs run in parallel when multiple apps are affected.
build-docs:
needs: changes
if: needs.changes.outputs.docs == 'true' || (github.event_name == 'workflow_dispatch' && inputs.docs)
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @explainer/docs build
env:
PUBLIC_WEBSITE_URL: ${{ vars.PUBLIC_WEBSITE_URL }}
PUBLIC_DOCS_URL: ${{ vars.PUBLIC_DOCS_URL }}
PUBLIC_BLOG_URL: ${{ vars.PUBLIC_BLOG_URL }}
- uses: actions/upload-artifact@v4
with:
name: docs-dist
path: apps/docs/dist/
The built artifacts are uploaded and consumed by the deployment job.
Environment variables and secrets
Public variables
These are required for cross-app navigation links. Set them in GitHub Settings → Variables:
| Variable | Description | Example |
|---|---|---|
PUBLIC_WEBSITE_URL | Website app URL | https://explainer.dev |
PUBLIC_DOCS_URL | Docs app URL | https://docs.explainer.dev |
PUBLIC_BLOG_URL | Blog app URL | https://blog.explainer.dev |
DEPLOY_TARGET | Deployment platform | cloudflare |
All three PUBLIC_* variables are passed to every app build. This ensures navbar links between apps always point to the correct URLs, regardless of the deployment platform.
Secrets
| Type | Name | Used by |
|---|---|---|
| Secret | CLOUDFLARE_API_TOKEN | Cloudflare |
| Secret | CLOUDFLARE_ACCOUNT_ID | Cloudflare |
| Secret | VERCEL_TOKEN | Vercel |
| Secret | VERCEL_ORG_ID | Vercel |
| Secret | VERCEL_DOCS_PROJECT_ID | Vercel (docs) |
GitHub Pages uses built-in GITHUB_TOKEN permissions — no additional secrets are needed.
DEPLOY_TARGET pattern
The workflow uses a repository variable DEPLOY_TARGET to select the deployment platform:
deploy-docs:
needs: build-docs
if: vars.DEPLOY_TARGET == 'cloudflare'
# ...
Set DEPLOY_TARGET once in your repository variables and the correct deployment job runs automatically.