Deploying Explainer to production: a complete guide
From local development to production deployment — learn how to ship your Explainer documentation to Cloudflare Pages, Vercel, or any static hosting provider with CI/CD automation.
Published March 16, 2026
You’ve written your documentation, customized the theme, and everything looks great locally. Now it’s time to share it with the world. In this guide, we’ll cover the full deployment story — from build configuration to CI/CD pipelines.
Understanding the build output
Explainer apps are standard Astro static sites. Running pnpm build produces a dist/ directory with plain HTML, CSS, and JavaScript files. No server runtime required — you can host the output anywhere that serves static files.
# Build all three apps
pnpm build
# Or build individually
pnpm --filter @explainer/docs build
pnpm --filter @explainer/blog build
pnpm --filter @explainer/website build
Independent apps
Each app builds independently with its own dist/ output. You can deploy them to different providers or subdomains — they don’t need to live on the same server.
Environment variables
Before building for production, configure your cross-app URLs. These ensure navbar links point to the correct domains:
PUBLIC_WEBSITE_URL=https://explainer.dev
PUBLIC_DOCS_URL=https://docs.explainer.dev
PUBLIC_BLOG_URL=https://blog.explainer.dev
For the contributors section, you can optionally provide a GitHub token for higher API rate limits:
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
Without a token, the GitHub API allows 60 requests per hour — more than enough for a single build. The token is mainly useful during active development when you’re rebuilding frequently.
Deploying to Cloudflare Pages
Cloudflare Pages is our recommended deployment target. It offers a generous free tier, global CDN, and automatic preview deployments.
Create Cloudflare Pages projects
Create three projects in your Cloudflare dashboard — one for each app:
explainer-docsexplainer-blogexplainer-website
No build configuration needed in Cloudflare — we handle builds in GitHub Actions.
Configure secrets
In your GitHub repository settings, add these secrets:
CLOUDFLARE_API_TOKEN— your Cloudflare API token with Pages edit permissionCLOUDFLARE_ACCOUNT_ID— your Cloudflare account ID
And these variables:
PUBLIC_DOCS_URL,PUBLIC_BLOG_URL,PUBLIC_WEBSITE_URL— your production URLsDEPLOY_TARGET— set tocloudflare
Push to main
The included GitHub Actions workflow handles everything automatically. On each push to main, it:
- Detects which apps have changed (path filtering)
- Builds only the affected apps
- Deploys to Cloudflare Pages
- run: pnpm --filter @explainer/docs build
env:
PUBLIC_DOCS_URL: ${{ vars.PUBLIC_DOCS_URL }}
PUBLIC_BLOG_URL: ${{ vars.PUBLIC_BLOG_URL }}
PUBLIC_WEBSITE_URL: ${{ vars.PUBLIC_WEBSITE_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Deploying to Vercel
Vercel’s zero-config approach works well with Explainer. Each app can be imported as a separate Vercel project.
Import the repository
In the Vercel dashboard, import your repository three times — once per app. For each project, configure:
- Root directory:
apps/docs,apps/blog, orapps/website - Build command:
cd ../.. && pnpm build --filter @explainer/docs - Output directory:
dist
Set environment variables
Add PUBLIC_DOCS_URL, PUBLIC_BLOG_URL, PUBLIC_WEBSITE_URL, and optionally GITHUB_TOKEN in each project’s environment settings.
Configure custom domains
Assign your subdomains to each Vercel project: docs.explainer.dev, blog.explainer.dev, and explainer.dev.
Smart CI/CD with path filtering
The monorepo workflow includes intelligent path filtering — only apps with actual changes get rebuilt and deployed:
- uses: dorny/paths-filter@v4
with:
filters: |
docs:
- 'apps/docs/**'
- 'packages/**'
- 'pnpm-lock.yaml'
blog:
- 'apps/blog/**'
- 'packages/**'
- 'pnpm-lock.yaml'
Shared packages trigger all builds
Changes to packages/** trigger rebuilds for all apps, since shared UI or MDX changes can affect any app. This is intentional — it ensures consistency across your documentation ecosystem.
Custom domains and DNS
A typical setup uses subdomains:
| App | Domain | DNS Record |
|---|---|---|
| Website | explainer.dev | CNAME to provider |
| Docs | docs.explainer.dev | CNAME to provider |
| Blog | blog.explainer.dev | CNAME to provider |
Both Cloudflare Pages and Vercel handle SSL certificates automatically.
Docker deployment
For self-hosted environments, Explainer supports Docker deployment with a multi-stage build:
FROM node:22-alpine AS build
RUN corepack enable
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @explainer/docs build
FROM nginx:alpine
COPY --from=build /app/apps/docs/dist /usr/share/nginx/html
You can serve all three apps behind a reverse proxy (nginx, Caddy, Traefik) with path-based or subdomain routing.
Performance checklist
Before going live, verify these optimizations are in place:
Pagefind search
Search indexes are generated at build time. Verify the dist/pagefind/ directory exists after building docs.
OG thumbnails
Every page gets an auto-generated Open Graph image. Test with the Twitter Card Validator.
RSS feed
The blog generates RSS feeds per locale at /{locale}/rss.xml. Test with a feed reader.
Sitemap
Astro generates a sitemap automatically. Verify it’s accessible at /sitemap-index.xml.
What’s next
With your documentation deployed, you can focus on what matters — writing great content. The CI/CD pipeline ensures every push to main gets your changes live within minutes.
Need help?
Check out the deployment documentation for detailed provider-specific guides, or open an issue on GitHub if you run into any problems.
Happy shipping!