Deploy to Cloudflare Pages
Upload a Reed build to Cloudflare Pages, with _headers and _redirects at the root of the upload.
This recipe is written from Cloudflare’s Pages documentation and Reed’s output layout. It
has not been run as a live deploy. After your first deploy, check two things: a .md
mirror comes back as text/markdown, and a nested page URL such as
/docs/ship/deploy/ resolves. The commands are in the last section.
Cloudflare Pages serves a directory of static files. It reads _headers and _redirects
from the root of that directory, and it serves 404.html for unknown paths. A Reed build
already fits that shape. The only work is putting the files where Pages looks for them.
Choose how the files reach Pages
Section titled “Choose how the files reach Pages”Pages offers two ways to deploy.
Direct Upload. You build somewhere else and upload the finished directory with Wrangler. This is the better fit for Reed. Your CI already has a Reed build that works offline (see Offline builds in CI), and Pages only receives the output. Cloudflare notes that a Direct Upload project cannot be switched to Git integration later.
Git integration. Pages clones your repository and runs a build command. You set the
Build command and Build output directory in the project settings. For Reed, the
command must make the reed CLI available in Pages’ build image, run reed prefetch, then
run reed build --out dist. The output directory is dist, or stage on a site with
base (see below).
The rest of this page uses Direct Upload.
1. Build
Section titled “1. Build”For a site served at the root of its hostname, the build is the deploy directory:
reed build --out distdist/ already has _headers, 404.html and, if you declared redirects, _redirects at
its root. Nothing else to stage.
2. Stage the tree if you set base
Section titled “2. Stage the tree if you set base”A Pages project is served at the root of its hostname: <project>.pages.dev or a custom
domain you attach. With base: "/docs", Reed prefixes every link with /docs, but the
files still sit at the root of dist/. Upload dist/ as it is and /docs/ maps to
nothing. The deploy succeeds and every page 404s.
Give the upload a real docs/ directory:
set -euo pipefail
reed build --out dist
rm -rf stagemkdir -p stage/docscp -R dist/. stage/docs/
# Pages reads _headers and _redirects only at the root of the uploaded directory.mv stage/docs/_headers stage/_headers# _redirects exists only when docs.json declares redirects.if [ -f stage/docs/_redirects ]; then mv stage/docs/_redirects stage/_redirects; fi
# A top-level 404.html also keeps Pages out of single-page-app mode.cp stage/docs/404.html stage/404.htmlThe resulting tree:
Directorystage/!
- _headers rules match served paths
- _redirects only with redirects, see below
- 404.html copy of docs/404.html
Directorydocs/!
- index.html
- 404.html
- llms.txt
Directory_astro/
Why the extra 404.html: Pages treats a project with no top-level 404.html as a
single-page application and answers unknown paths from the root. The copy at
stage/404.html turns that off. Unknown paths under /docs/ still get
docs/404.html, because Pages looks for the nearest 404.html up the directory tree.
Reed’s 404 page references its assets with absolute /docs/... paths, so the root copy
renders correctly too.
If you have a site at a path on a domain that something else already serves, for example
example.com/docs next to a marketing site, a Pages project alone does not give you that.
Cloudflare Workers routes a single path to a
static-assets Worker, and it uses the same stage/ layout.
3. _headers and _redirects stay at the root
Section titled “3. _headers and _redirects stay at the root”Reed writes this _headers file into every build:
Content-Type: text/markdown; charset=utf-8
/llms.txt Content-Type: text/plain; charset=utf-8
/*/llms.txt Content-Type: text/plain; charset=utf-8Pages reads it from the root of the uploaded directory and does not serve it as a file.
The rules match served URL paths. A * splat matches across /, so /*.md covers
/docs/ship/deploy.md, and /*/llms.txt covers /docs/llms.txt.
That is why the staging script moves the file up out of stage/docs/, and _redirects
with it. Left inside the mount directory, neither is read. Details on what each rule protects are on
Response headers.
4. Deploy
Section titled “4. Deploy”Create the project once, then deploy the directory:
npx wrangler pages project createnpx wrangler pages deploy stage --project-name=my-docsFor a site without base, deploy dist instead of stage. To deploy a preview, add
--branch=<name>. Pages serves each branch at <branch>.<project>.pages.dev.
Real redirects from docs.json
Section titled “Real redirects from docs.json”When docs.json declares redirects, reed build writes a _redirects file with one
line per entry, base already applied to both columns:
/docs/old-guide /docs/guides/getting-started 301Pages applies a matching _redirects rule before it serves a file at the same path, so
the rule wins over the meta-refresh page Reed also writes at the old path. Readers and
crawlers get a real 301, or a 302 for an entry with permanent: false. See
Redirects for exactly what is emitted.
Keep the file at the root of the upload, as the staging script above does. For a site
without base, it is already at the root of dist/.
Rules Reed does not write
Section titled “Rules Reed does not write”Some rules are not page moves, such as sending the bare hostname to the docs mount. Add
those to the same file, after reed build and before you deploy, because the build
replaces its output directory:
printf '/ /docs/ 302\n' >> stage/_redirectsWhen you write a rule by hand:
- Write the base yourself.
_redirectsis read against served paths./old-guidealone matches nothing on a based site. - State the status. Pages defaults to 302 when you leave it out.
- Order matters. When two rules share a source, the first one applies, so Reed’s lines come first.
Verify after the first deploy
Section titled “Verify after the first deploy”Check that a Markdown mirror has the right content type:
curl -s -o /dev/null -w "%{content_type}\n" https://my-docs.pages.dev/docs/ship/deploy.mdExpect text/markdown; charset=utf-8. If you get application/octet-stream or no
charset, _headers is not at the root of the upload.
Check that a nested page resolves:
curl -s -o /dev/null -w "%{http_code}\n" https://my-docs.pages.dev/docs/ship/deploy/Expect 200. A 404 here, on a site with base, means you uploaded dist/ instead of
stage/.
Then confirm an unknown URL returns a 404 status and Reed’s page, and that a string from this build is on the live site. Deploy explains why the exit code is not enough.
Related
Section titled “Related”- Netlify reads the same
_headersand_redirectsfiles, with different redirect rules. - Vercel needs the header rules rewritten as JSON.
- site and base for what
basechanges and what it does not. - llms.txt for what the plain-text content type protects.