zudo-text

検索したい単語を入力

いつでも検索バーを開ける

Documentation Site Domains

How the two documentation sites got their own hostnames, and how to redo it.

SiteSourcePages projectHostname
Developer docsdoc/zudo-text-docdoc.zudo-text.app
End-user manual (JA)manual/zudo-text-manualmanual.zudo-text.app

Done — both domains are live

Both Pages projects exist, both custom domains are attached and serving, andDeploy Doc Site / Deploy Manual Site are green on main. Nothing on this page is outstanding.

It is kept as the procedure of record: the reasoning in "Why" below still governs how any future site is added, and sections 1–4 are the steps to repeat if a domain is ever re-attached or a third site appears. The optional follow-ups at the end are genuinely still open.

How it is wired

  • Both sites build root-mounted (base: "/") and carry a siteUrl, so <link rel=canonical> and og:url point at their own hostnames.

  • .github/workflows/deploy-doc.yml and deploy-manual.yml build and deploy each site on every push to main that touches it, and deploy a per-PR preview on pull requests (pr-N.zudo-text-doc.pages.dev and its manual counterpart).

  • The combined zudo-text-preview artifact no longer contains /doc/ or /manual/, and those paths are not redirected anywhere. The app is pre-release with no external users, so there was no link to keep working (root CLAUDE.md, "Pre-Release: No Backward Compatibility"). Both paths now fall through to that artifact's SPA catch-all and render the mock app.

  • The one in-app link (Settings → API tokens → the personal-access-token doc link) points at https://doc.zudo-text.app/.

  • Both sites build root-mounted (base: "/") and carry a siteUrl, so <link rel=canonical> and og:url already point at the new hostnames.

  • .github/workflows/deploy-doc.yml and deploy-manual.yml build and deploy each site on every push to main that touches it, and deploy a per-PR preview on pull requests.

  • The combined zudo-text-preview artifact no longer contains /doc/ or /manual/, and those paths are not redirected anywhere. The app is pre-release with no external users, so there is no link to keep working (root CLAUDE.md, "Pre-Release: No Backward Compatibility"). Both paths now fall through to that artifact's SPA catch-all and render the mock app.

  • The one in-app link (Settings → API tokens → the personal-access-token doc link) already points at https://doc.zudo-text.app/.

Why the domain attach is not automated

A [[routes]] entry or an API-driven domain attach requires the deploy token to hold zone-level permissions on zudo-text.app. The shared CLOUDFLARE_API_TOKEN is deliberately account-scoped only. A dashboard-attached custom domain persists across every ordinary deploy with that narrower token untouched — the same reasoning that governs editor.zudo-text.app and the apex landing page in the Custom Domain & Email Ops Runbook §2.

Why each site needs its own project

zfb bakes base into every emitted URL and offers no way to vary it per build: there is no --base flag, no environment variable, and zfb.config.ts is evaluated inside an embedded V8 where process is undefined. One content tree can therefore serve /doc/ or /, never both. Giving each site its own Pages project also restores the per-PR previews it used to get from the shared artifact.

1. Confirm the zone is active

Nothing below works until zudo-text.app resolves through Cloudflare. It should already — https://editor.zudo-text.app returns 200 today. Confirm:

curl -s -o /dev/null -w '%{http_code}\n' https://editor.zudo-text.app/

200 means the zone is live and you can proceed. Anything else means stop and work through the Custom Domain & Email Ops Runbook §1 first.

2. Create the two Pages projects

Already done for both. One command each, run locally; this only registers the project and uploads nothing.

npx wrangler@4 pages project create zudo-text-doc --production-branch main
npx wrangler@4 pages project create zudo-text-manual --production-branch main

You will be prompted to log in if wrangler has no session. Creating a project that already exists fails harmlessly — if you see "project already exists", that step was already done.

Note

The project names are not free-form. deploy-doc.yml and deploy-manual.ymlpass them to wrangler pages deploy --project-name=…, so a typo here surfaces as a failing deploy, not as a wrong URL.

3. Trigger the first deploy, then attach the domains

Already done for both. Order matters when repeating it: attach the custom domain after a successful first deploy, so the hostname has content to serve the moment DNS resolves.

3a. Deploy

Either push a commit touching doc/ or manual/, or dispatch the workflows by hand:

gh workflow run deploy-doc.yml --ref main
gh workflow run deploy-manual.yml --ref main
gh run watch

Each run ends by publishing to https://zudo-text-doc.pages.dev/ and https://zudo-text-manual.pages.dev/ respectively. Open both and confirm the site renders with working CSS — a page that loads as unstyled HTML means the base path is wrong, and you should stop rather than attach a domain to it.

3b. Attach

For each project, in the Cloudflare dashboard:

  1. Workers & Pages → select the project (zudo-text-doc / zudo-text-manual).

  2. Open the Custom domains tab. This is a Pages-project tab, and is a different screen from the Settings → Domains & Routes page used for Workers — do not go looking for it there.

  3. Set up a custom domain → enter doc.zudo-text.app (respectively manual.zudo-text.app) → confirm.

  4. Cloudflare adds the CNAME itself because the zone is on the same account. Wait for the status to go Active. This is usually under a minute, but certificate issuance can take longer.

Do not add a [[routes]] block or a custom_domain key to any committed config as part of this — see "Why the domain attach is not automated" above.

4. Verify

Both answer 200 and serve their own site at the root — last confirmed serving 262 KB titled zudo-text and 148 KB titled zudo-text マニュアル respectively, so this checks content and not just a status code:

curl -s -o /dev/null -w 'doc     %{http_code}\n' https://doc.zudo-text.app/
curl -s -o /dev/null -w 'manual  %{http_code}\n' https://manual.zudo-text.app/

Then spot-check that a deep link survives the move — this is the URL now embedded in the app's API-tokens settings pane:

curl -s -o /dev/null -w '%{http_code}\n' \
  https://doc.zudo-text.app/docs/guide/personal-access-tokens/

If any of these return 000 from your own machine while the sites are demonstrably up, read the last bullet of section 5 before investigating anything server-side.

5. What the attach looked like

Recorded from the real cutover, because the intermediate states are alarming if you have not seen them:

  • Before the attach: dig +short doc.zudo-text.app returned nothing at all, while the *.pages.dev project URL already served the site. No DNS record means the attach never happened — Cloudflare writes the CNAME immediately when the zone is on the same account, so this is not a propagation wait.

  • Just after the attach: DNS resolved to the Cloudflare edge (104.21.74.111 / 172.67.157.118, the same pair as editor.zudo-text.app) but both hosts answered 522 for a few minutes. That is the edge accepting the request before the Pages binding finishes — it clears on its own; nothing to fix.

  • A stale local resolver outlives the fix. macOS caches the earlier negative lookup, so curl can keep failing instantly (time_namelookup=0.000000s) long after the domain works from everywhere else. dig bypasses that cache, and curl --resolve <host>:443:104.21.74.111 proves the origin independently. sudo dscacheutil -flushcache clears it.

Optional follow-ups

Neither is required; both are one-line changes.

  • Sitemaps. sitemap is off in both configs. Now that each site has a real canonical origin, enabling sitemap: true makes the siteUrl do more work — zudo-doc will also add a Sitemap: line to robots.txt.

  • Indexing of the developer docs. doc.zudo-text.app publishes the generated claude-* categories (this project's CLAUDE.md, commands and skills, ~19 skill pages). They were already public on pages.dev, but a branded domain makes them findable rather than merely reachable. If that is not wanted, either drop claudeResources from doc/zfb.config.ts or add a robots.txt disallow for those paths.