I've been building a static Astro site on Cloudflare Pages over the last few weeks. Sharing the 3 deployment bugs that cost me the most time, in case they save anyone else the same loop.
Setup
Astro 5 + Cloudflare Pages + Tailwind 4. Content lives in a few JSON files; each page is a dynamic route mapped over the data. Free-tier hosting, no backend. Standard static-first stack.
Bug 1: Trailing-slash 307 chain
I started with trailingSlash: 'never' in Astro config. Build output went to dist/foo/index.html. Result: Astro emitted canonical tags as /foo (no slash), but Cloudflare Pages served /foo/ (auto-adding the slash via 307). Google Search Console flagged pages as "Redirect error" because the canonical URL pointed at a redirect chain instead of a real 200.
I first tried build.format: 'file' to get flat dist/foo.html output, hoping that would bypass the trailing slash. That made it worse — Cloudflare still 307-stripped, but now to a non-existent .html file → 404.
Fix: stop fighting the platform.
js
// astro.config.mjs
export default defineConfig({
trailingSlash: 'always',
// ...
});
trailingSlash: 'always' plus default directory build aligns the canonical URL with what Pages actually serves. The redirect errors resolved on next re-crawl.
Bug 2: _redirects rejected at deploy
I tried to do a www → apex 301 in public/_redirects:
https://www.example.com/* https://example.com/:splat 301!
Cloudflare rejected the deploy with three validation errors:
Line 13: Only relative URLs are allowed.
Line 22: Duplicate rule for path /foo.
Line 23: Duplicate rule for path /bar.
Pages tightened _redirects validation — absolute-URL sources aren't accepted anymore. The duplicate errors were because Astro's own redirects config in astro.config.mjs generates HTML meta-refresh files that Pages parses as implicit redirect rules — conflicting with my explicit ones.
Fix: delete _redirects entirely. Use a Cloudflare Redirect Rule from the dashboard for cross-host 301s (Wildcard pattern, Dynamic destination with ${1} to preserve the path). Astro's HTML-redirect generation handles the in-config aliases on its own.
Bug 3: Asset manifest staleness across deploys
After switching between build.format: 'file' and back to directory output, some nested paths started returning 308 → no-slash → 404. Even brand-new URLs that had never been deployed before. Local build output was correct — the live response was stripping the trailing slash on specific nested patterns.
Turned out to be a stale asset manifest from the previous build mode. Pages was still serving 308s for old .html file paths even though those files no longer existed in the current deploy.
Fix: push any commit that triggers a full Pages rebuild — the manifest regenerates cleanly. If you don't have a pending change, an empty commit works:
sh
git commit --allow-empty -m "force rebuild"
git push
Lessons
- Pick a URL convention (slash or no slash) before deploying to Pages. Switching mid-flight leaves cached redirects that take a full rebuild to clear.
-
_redirectsis more restricted than the docs suggest. Use dashboard Redirect Rules for anything cross-host. - GSC's "Redirect error" category is the canary for canonical / serve-URL mismatches. Worth checking weekly.
Build-in-public log lives on my site if you want to follow along.
Top comments (0)