← Back to writing

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-docs
  • explainer-blog
  • explainer-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 permission
  • CLOUDFLARE_ACCOUNT_ID — your Cloudflare account ID

And these variables:

  • PUBLIC_DOCS_URL, PUBLIC_BLOG_URL, PUBLIC_WEBSITE_URL — your production URLs
  • DEPLOY_TARGET — set to cloudflare

Push to main

The included GitHub Actions workflow handles everything automatically. On each push to main, it:

  1. Detects which apps have changed (path filtering)
  2. Builds only the affected apps
  3. 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, or apps/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:

AppDomainDNS Record
Websiteexplainer.devCNAME to provider
Docsdocs.explainer.devCNAME to provider
Blogblog.explainer.devCNAME 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!