Fix Astro API route 404 on Cloudflare Workers (/api/contact)
The contact form looked perfect in the browser. Click submit, and the network tab showed POST /api/contact → 404. HTML pages loaded fine. That combination usually means your API route never made it into the Worker bundle, not that Resend or Turnstile failed.
On Astro 6 with @astrojs/cloudflare v13, API routes live in src/pages/api/ and must stay server-rendered (prerender = false). A green build can still deploy a Worker that only serves static assets if the API file was prerendered or never exported.
Symptom: 404 only in production (or only on POST)
Check these quickly:
| Observation | Likely cause |
|---|---|
GET /contact works, POST /api/contact is 404 | API route not deployed or wrong path |
Works with astro dev, fails after wrangler deploy | prerender = true on the API file, or build output missing the route |
| 404 in browser but no Worker log line | Request never hit your script (static asset served instead) |
If you see no log at all for /api/contact, read Why Cloudflare Worker logs only show some routes first—then come back here.
Fix 1: API route file location and export
The handler must live at:
src/pages/api/contact.ts
and export the HTTP method you call from the client:
export const prerender = false;
export const POST: APIRoute = async ({ request, locals }) => {
// ...
};
Without export const POST, Astro will not register a POST handler. Without prerender = false, the route may be treated as static at build time and omitted from the Worker.
My contact page posts JSON to the same origin:
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, message, turnstileToken }),
});
Keep the path consistent: file api/contact.ts → URL /api/contact.
Fix 2: Confirm the API route is not static-only
After:
npm run build
check dist/client/:
- There should be no
dist/client/api/contact/HTML (or similar prerendered API output). - You should see pre-rendered pages like
dist/client/blog/.../index.htmlanddist/client/index.html. - There should be no
dist/client/contact/index.html—contact.astrousesprerender = false, so/contactis rendered on the Worker.
If the API path only exists as a static file, POST requests will never reach your handler. Rebuild after changing prerender, adapter, or file location.
Fix 3: Adapter and output mode
This site uses:
adapter: cloudflare({ imageService: 'compile' }),
Do not set output: 'static' if you need server routes. Hybrid/server output with the Cloudflare adapter is what registers API endpoints on the Worker.
If you recently copied a template from a static-only Astro project, you can get a green astro build and still deploy a Worker that only serves assets.
Fix 4: curl the API directly
Bypass the form to isolate routing:
curl -i -X POST "https://mayfield.io/api/contact" \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"API route probe"}'
Interpretation:
- 404 — routing / deploy / prerender issue (this post)
- 400 with validation text — route works; payload or Turnstile token issue
- 403 — Turnstile verification failed
- 502 with email text — route works; see Fix Resend 403 and 422
Fix 5: Redeploy and confirm Wrangler sees the script
Run a dry-run in CI or locally:
npx wrangler deploy --dry-run
You should see your Worker upload size and bindings (e.g. env.ASSETS, and on Astro 6 often env.SESSION for the adapter). wrangler.json main should be @astrojs/cloudflare/entrypoints/server. If the dry-run fails, production may still be serving an older bundle without the API route.
I run dry-run on every PR for this reason—see Wrangler deploy dry-run in CI.
Fix 6: Trailing slash mismatches
If you enable trailingSlash in astro.config.mjs, use the same shape in the client fetch and in curl tests (/api/contact vs /api/contact/). This site leaves the default and posts to /api/contact without a trailing slash.
When it is not a 404 problem
Once POST returns 400/403/502, the API route exists. Switch troubleshooting:
- Turnstile → Fix Turnstile verification failed
- Secrets → Astro secrets on Cloudflare Workers
- Resend → Fix Resend 403 and 422
404s are almost always build output and routing, not email provider configuration.
Hero photo: Midge cat and computer by dougwoods, CC BY 2.0.