MossBySea

Astro build fails on Cloudflare Pages: fixes

Guide October 21, 2026

Built 2026-09-22
Stack Astro 5 static · Cloudflare Pages · Node 22 build
Total cost $10.46/year, all in

The deploy button says “failed” and the log is a wall of text. Most Astro-on-Cloudflare build failures are the same three causes, and the first one bit me on the very first push. The deploy walkthrough assumes the build is green; this is what to do when it isn’t.

First, a boundary: a green build that still serves nothing is a DNS problem, not a build problem — that’s covered in the Cloudflare setup gotchas. This page is about the build itself failing.

Cause 1 — Node version too old (the common one)

Cloudflare Pages builds on a default Node version that can predate what Astro 5 requires. Astro 5 needs Node 18.17.1, 20.3.0, or 22+. If the build container runs something older, npm run build fails with an engine error before your code ever runs:

error astro@5.x.x: The engine "node" is incompatible with this module.

Fix — set the Node version in one of two places:

  1. Dashboard: Settings → Builds & deployments → Build configuration → Node.js version, set to 22 (or 20).
  2. package.json, so it travels with the repo:
{
  "engines": {
    "node": ">=20.3.0"
  }
}

Do this before the first build and you never see the error.

Cause 2 — wrong output directory

The Astro preset sets the build output to dist. If you’ve changed outDir in astro.config.mjs, or pointed the Pages “Build output directory” at the wrong folder, two things happen: the build “succeeds” but the deploy has no pages, or the dashboard can’t find the output and fails.

Fix: confirm astro.config.mjs uses the default (no custom outDir), and the Pages build setting reads dist. If you did customize outDir, set the Pages output directory to match it exactly.

Cause 3 — dependency install fails or times out

The build runs npm install on a cold cache, and a missing lockfile or a flaky dependency can fail or time out. Free tier also caps you at 500 builds/month, but that’s a volume limit, not a timeout.

Fix:

  • Commit package-lock.json and use npm ci (reproducible, faster, fails loudly on mismatch) instead of npm install as the build command if installs are flaky.
  • Don’t pin a dependency to a version that doesn’t exist; the log will show a 404 from the registry, not a build error.

Cause 4 — memory or build timeout on free tier

Heavy sites can exceed the free tier’s build memory or the default timeout. Astro static output is light and rarely hits this, but if your build does, raise the timeout in Build configuration and, if memory is the wall, slim the build (fewer heavy integrations, no on-build image processing).

How to actually read the failure

Open Deployments → the failed deploy → Build log. The useful line is usually near the bottom: an engine error, a missing module, or a non-zero exit from npm run build. The “It deployed but the URL is dead” confusion lives elsewhere — if the build log says success and the site is unreachable, that’s DNS, not this page.

When a deploy “didn’t take” after a green build, also rule out a stale browser cache before you blame the pipeline.

What I’d tell myself

Set the Node version first — it’s the cause nine times out of ten. Confirm the output dir is dist. Commit the lockfile. Then read the bottom of the build log instead of the top. Most of these are configuration, not code.

If the build is green but a moved page 404s, that’s a redirect problem, not a build one — see Cloudflare Pages redirects.