astro lastVerified: 2026-05-08

Astro site/base mismatch: links 404 in production but work locally

Astro site/base mismatch — links work locally, 404 in production

On this page
  1. What causes this error
  2. How to fix it manually
  3. Copy this prompt into your AI coding agent
  4. Why this prompt works
  5. Variants by symptom
  6. Related errors and tools

What causes this error

Astro splits its URL configuration across two astro.config.ts fields that look interchangeable but answer different questions. site declares the canonical origin used to build absolute URLs in sitemaps, RSS feeds, and <link rel="canonical"> tags. base declares the subpath prefix prepended to internal links during the build. Misalign either with how the host actually serves the bundle and internal links 404 the moment the site leaves localhost.

The most common shape on Cloudflare Pages is a root-domain deploy (e.g., https://example.pages.dev/) where site: 'https://example.pages.dev' and base is unset (defaulting to /). Internal absolute links like <a href="/about"> resolve correctly because the host root and the build root agree. The trap appears when the project later moves to a subpath — say a documentation site mounted at /docs on a parent domain — and the author updates site but forgets base. Every internal absolute link still emits /about, but the host now serves the bundle at /docs/about, so every link 404s.

The second shape is the inverse: base: '/docs' is set during a subpath migration and works in production, then someone runs pnpm dev on localhost expecting plain /about and gets /docs/about instead. The third shape is build.format: 'file' versus 'directory' — file mode emits /about.html and is the right choice for Cloudflare Pages with trailingSlash: 'never', while directory mode emits /about/index.html and adds a 308 redirect from /about to /about/, which mangles <link rel="canonical"> if site has not been updated to match. The fourth shape is asset URLs — Astro builds asset paths off base, so a misconfigured base sends every <img> and <script> tag to a 404 even when the page HTML loads.

How to fix it manually

Decide which deploy mode is canonical: root deploy or subpath deploy. For root, set site: 'https://<your-domain>' and leave base unset. For subpath, set both — site to the parent origin and base to the subpath including the leading slash. Run pnpm build and inspect dist/ — internal links in the emitted HTML must match the URLs the host serves. Run curl -I https://<deploy-preview>/about and confirm a 200 response, not a 308 or 404.

Copy this prompt into your AI coding agent

astro-config-base-mismatch-fix-prompt.md
# Goal
Fix the Astro internal-link 404 issue caused by a site/base config mismatch.
Use the smallest safe change. Decide root-deploy or subpath-deploy first;
do not introduce a third deploy mode.

# Context

The deploy URL where 404s appear:

<paste the failing URL — e.g., https://example.pages.dev/docs/about>

The astro.config.ts current values:

- site: <current value or 'unset'>
- base: <current value or 'unset'>
- build.format: <'file' | 'directory'>
- trailingSlash: <'always' | 'never' | 'ignore'>

The Cloudflare Pages deploy mode: <root domain | subpath via custom domain | preview branch>

# In-scope

- astro.config.ts site, base, build.format, trailingSlash
- public/_redirects (if subpath redirects are needed)
- public/_headers (if affecting Link header)

# Out-of-scope

- Switching deploy targets (do not propose Vercel / Netlify migration)
- Adding SSR adapters or Pages Functions
- Refactoring the entire link structure of the site
- Renaming routes

# Verification

Run `pnpm build` and grep `dist/` for the link prefix:
`grep -r 'href="/' dist/ | head -20` — confirm prefixes match the deploy mode.
Run `pnpm preview` and click an internal link — confirm 200 response.
After deploy: `curl -I <deploy-url>/about` — confirm 200, not 308 or 404.

# Output format

1. One sentence: which deploy mode (root / subpath) and what the mismatch was.
2. The diff (only astro.config.ts and public/ changes).
3. The grep output and curl output, truncated.

Why this prompt works

The deploy-mode question is the disambiguating axis — every subsequent decision (whether to set base, whether trailingSlash: 'never' is right, whether _redirects is needed) flows from it. Putting “do not introduce a third deploy mode” in the Goal is defensive — Claude Code otherwise has a tendency to suggest SSR migration or a hybrid adapter when the static config is fine. The grep verification step is unusually concrete because the failure mode lives in the emitted HTML, not in a runtime stack trace — checking the build output before deploying saves a Cloudflare Pages cycle.

Variants by symptom

SymptomDiagnostic command
Root deploy, links 404grep site astro.config.ts (must match origin)
Subpath deploy, links 404grep base astro.config.ts (must include slash)
format: 'file' + 308 redirect on /aboutCheck trailingSlash: 'never' is set
format: 'directory' + canonical mismatchUpdate site to include trailing slash
Assets 404 (CSS/JS/img)base controls asset URLs — set it explicitly

Frequently asked questions

My links work locally but 404 in production — why?

A site or base mismatch. Internal absolute links emit /about while the host serves the bundle at /docs/about. Set base for a subpath deploy, or leave it unset for a root deploy.

When do I actually need to set base?

Only for subpath deploys such as /docs. A root-domain deploy should leave base unset and set site to the origin.