Skip to content
chatAgent
Esc
↑↓navigate↵open⌘Jpreview
On this page

Docs Site

How this documentation site is built with Blume and deployed to Cloudflare Pages behind Cloudflare Access.

This site is the repository’s own docs/ folder, published by Blume and deployed to Cloudflare Pages on every push to main. It sits behind Cloudflare Access (Zero Trust), so only people the Access policy allows can read it.

How it is put together

Piece Where
Site config blume.config.ts (root)
Content docs/ — an Obsidian-style vault; obsidian() resolves the [[wikilinks]]
Landing page docs/index.mdx (MDX, so it can use Blume components)
Build bun run docs:build → static site in dist/
Deploy .github/workflows/docs.yml → wrangler pages deploy dist
CI .github/workflows/ci.yml → typecheck, unit + integration tests, worker bundle

Blume reads docs/ in place: the Markdown in this repository is the single source of truth, and the site adds nothing to it except index.mdx. A .mdx page uses Blume components with no imports; the .md pages are plain Markdown, which is also why the vault source and the MDX source are configured separately.

Local preview

bun install                # repository root
bun run docs:dev           # dev server with hot reload
bun run docs:build         # production build into dist/
bun run docs:preview       # serve the built site exactly as a static host would
bun run docs:validate      # check internal, anchor, and asset links
bunx blume doctor          # diagnose configuration and content problems

Deploying

.github/workflows/docs.yml builds the site and uploads it to the Pages project chat-agent-docs on every push to main. Nothing about this runs on the app’s VPS: the pipeline is GitHub Actions → Cloudflare, so the runtime host needs no new environment variables.

Repository secrets

Settings → Secrets and variables → Actions → Secrets:

Secret Value
CLOUDFLARE_API_TOKEN An API token scoped to the account that owns the Pages project (policies below)
CLOUDFLARE_ACCOUNT_ID That account’s ID (Cloudflare dashboard → Workers & Pages → Account ID in the right sidebar, or wrangler whoami)

Token policies

Create the token at My Profile → API Tokens → Create Token → Create Custom Token and give it exactly these permissions, with Account Resources set to the one account:

Permission Scope Why
Cloudflare Pages — Edit (API name: Pages Write) Account Create the project and upload deployments
Account Settings — Read Account Let Wrangler resolve the account behind the token
Access: Apps and Policies — Edit (API name: Access: Apps and Policies Write) Account Only needed if you want the Access application created by API instead of by hand

An account API token (rather than a user token) with the first two rows is enough for the workflow. The token is stored only as a GitHub secret — never in this repository.

Repository variables (optional)

Settings → Secrets and variables → Actions → Variables:

Variable Default Meaning
PAGES_PROJECT chat-agent-docs Pages project name, if you want a different one
DOCS_SITE_URL https://chat-agent-docs.pages.dev Canonical origin — set it once the site has a custom domain, so canonicals, the sitemap, and Open Graph images point at the real host

The workflow creates the Pages project if it does not exist yet (wrangler pages project create), then deploys with wrangler pages deploy dist --project-name chat-agent-docs. Every deploy is a production deploy on main; pull requests only build the site, so a broken page fails the PR instead of the site. Until both secrets exist the deploy steps skip and say so in the run summary.

:::note Cloudflare exposes only the current deployment’s URL (CF_PAGES_URL) on Pages, which changes on every deploy — hence the pinned deployment.site in blume.config.ts and the DOCS_SITE_URL override. :::

Cloudflare Access (Zero Trust)

Access is what makes the site private; it is enforced at Cloudflare’s edge before any file is served, and it covers the whole site — pages, the search index, the .md mirrors, llms.txt, and the Open Graph images.

The Access application has to exist for the hostname the site answers on:

  1. Cloudflare dashboard → Zero Trust → Access → Applications → Add an application → Self-hosted.
  2. Application domain: the Pages hostname (chat-agent-docs.pages.dev, and the custom domain too if one is attached).
  3. Session duration: whatever suits the team.

From the API (needs the Access: Apps and Policies — Edit policy above):

curl -X POST \
  "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/access/apps" \
  -H "authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "content-type: application/json" \
  --data '{
    "name": "chatAgent docs",
    "domain": "chat-agent-docs.pages.dev",
    "type": "self_hosted",
    "session_duration": "24h"
  }'

The policy

An Access application with no policy denies everyone, so the site is unreachable until a policy is added — the safe state while it is being set up. Add one at Zero Trust → Access → Applications → chatAgent docs → Policies:

Field Value
Action Allow
Selector Emails (explicit addresses), Emails ending in (a domain), or Cloudflare account member for everyone in the Cloudflare account
Value The addresses, domain, or — for the account-member rule — nothing
Session duration Inherits the application’s; shorter means more re-auth

Add a second policy with the selector Service Token (any_valid_service_token) if a machine needs to fetch the site — CI jobs, monitoring, or an agent. Create the token under Zero Trust → Access → Service Auth, then send its CF-Access-Client-Id / CF-Access-Client-Secret headers.

A few consequences worth knowing before the policy goes on:

  • Agents and link previews stop working: llms.txt, the .md mirrors, and the MCP surface all live behind the same protection. Slack unfurls and X cards will show nothing.
  • To keep some pages public later, publish them as a separate site — Access covers the whole hostname, not per-page.

What the build emits

Alongside the HTML pages, Blume writes the agent-facing surface at the site root:

File Purpose
llms.txt, llms-full.txt Index and full corpus for coding agents
<route>.md, <route>.mdx A Markdown twin of every page
sitemap.xml, robots.txt Crawler files (built from deployment.site)
agent-readability.json, .well-known/ai-catalog.json Agent discovery manifests
_headers, vercel.json Host configuration files — Pages reads _headers, and ignores the rest

Back to Architecture · Turn Router Middleware

Last updated on October 2, 2026