Private Notes on Cloudflare Pages (Basic Auth Guide)

How Breath's private notes work and how to protect them with HTTP Basic Auth on Cloudflare Pages

🌲 Evergreen·created: ·category:meta

Private Notes on Cloudflare Pages (Basic Auth Guide)

Breath ships a private notes capability: a note marked private: true is excluded from every public surface (notes index, tags, categories, search API, backlinks, OG cache) but is still built as a static page, so it can be served behind an HTTP Basic Auth wall.

Why not GitHub Pages? GitHub Pages cannot enforce HTTP auth. Cloudflare Pages (or Workers) can, with a ~25-line middleware — the auth layer lives at the edge, in front of your static files.

How it works

  • One repo, one build. Private notes live under content/notes/private/ and carry private: true in the frontmatter.
  • They render at /notes/private/{slug}/ — normal static HTML, no app server.
  • A Cloudflare Pages Function (functions/private/_middleware.js) gates /notes/private/*: requests without a valid Authorization: Basic ... header get a 401.
  • Public pages never list private notes; the URL is only reachable with credentials.

1. Content contract

---
title: "My private capture"
stage: seedling
private: true            # ← hidden from all public listings
created: 2026-08-27
---

Raw thoughts here. Not public yet.

Rules:

  • Place private notes under content/notes/private/ so the URL carries the gated prefix.
  • private: true removes them from the notes index, tags, categories, search API (/api/notes.json), and OG cache scan.
  • Wikilinks still resolve between private notes; links from public notes to private notes render but lead to the auth wall.

2. Cloudflare Pages setup

  1. Create a Pages project (dashboard → Workers & Pages → Create → Pages → connect your Git repo). Framework preset: Astro, build command npm run build, output directory dist.
  2. Add environment variables in Settings → Variables and Secrets:
VariableDefaultNotes
AUTH_USERadminBasic Auth username
AUTH_PASSWORDchangemeChange before going live
AUTH_REALMPrivate NotesShown in the browser’s login dialog

The middleware file functions/private/_middleware.js ships with the theme — no code changes needed. (If you prefer a standalone Worker gate instead of a Pages Function, move the same logic to a Worker and point its route at /notes/private/*; the secrets story is identical.)

3. Rotating the password

The password is a Cloudflare secret, never a repo file. Rotate it any time — no code change, no redeploy:

# CLI (after wrangler auth): value is read from stdin
printf '%s\n' 'new-password' | npx wrangler pages secret put AUTH_PASSWORD --project-name=<name>

…or edit the variable in the dashboard. The example note in this theme shows the documented defaults (admin / changeme) — treat them as placeholders.

4. GitHub Actions pipeline (disabled by default)

The theme ships .github/workflows/cloudflare.yml.disabled. Enable it:

  1. git mv .github/workflows/cloudflare.yml.disabled .github/workflows/cloudflare.yml
  2. Add repository secrets (Settings → Secrets and variables → Actions):
SecretValue
CLOUDFLARE_API_TOKENAPI token with Cloudflare Pages:Edit permissions
CLOUDFLARE_ACCOUNT_IDYour Cloudflare account ID
CLOUDFLARE_PROJECT_NAMEThe Pages project name
AUTH_USERBasic Auth username (default admin)
AUTH_PASSWORDBasic Auth password (default changeme — rotate)

The workflow builds with SITE_URL / BASE_PATH / CONTENT_DIR (all overridable via workflow_dispatch inputs), deploys with Wrangler (--functions=functions bundles the auth gate), and pushes the auth secrets to the Pages project.

Keep only one pipeline active. The default is .github/workflows/github-pages.yml (GitHub Pages). Enable Cloudflare only when you deploy there.

5. Verify

curl -I https://your-site.pages.dev/notes/private/example-capture/   # 401
curl -u admin:changeme -I https://your-site.pages.dev/notes/private/example-capture/  # 200

Security notes

  • Change the default password before going live.
  • Cloudflare Pages forces HTTPS on your custom domain (free).
  • Private notes are still present in the built artifact; the middleware is the enforcement boundary. Keep your Git repo private as a second layer.
  • GitHub Pages cannot enforce auth — a private note deployed only to GitHub Pages is public. Use Cloudflare (or delete the private content) there.

6. Captured Notes & PARA Content Model (ADR-006)

Breath supports an AI-assisted research workflow:

  1. Capture: Raw notes land in content/notes/private/ with stage: captured, para (projects | areas | resources | archives), and source provenance.
  2. Organize: Notes are connected via [[wikilinks]] across related private/public notes.
  3. Distill: Authors refine raw captures into concise seedling or budding notes.
  4. Express: Mature concepts reach evergreen notes or full blog posts.
---
title: "Research on Distributed Consensus"
stage: captured
para: resources
source: "Claude 3.7 research session"
tags: ["consensus", "architecture"]
created: 2026-08-27
private: true
---

7. LLM-Wiki & Local Graphify Workflow (ADR-007)

To let AI coding assistants query and synthesize ideas across your entire garden:

  1. Generate the graph locally: Run /graphify in your workspace to build graphify-out/.
  2. Privacy boundary: graphify-out/ is gitignored and is NEVER deployed with the static site. It includes private and captured notes for local AI query fidelity without risking public data leakage.
  3. Build warnings: If a public note accidentally links to a private note, Astro’s build process logs an advisory warning so you can remove or refactor the link before deploying.

This note is part of the Digital Garden — a collection of connected, evolving thoughts.