Astro Site URL vs Base Path Confusion

Astro's `site` and `base` config fields look like they do the same thing but serve completely different purposes. Getting them wrong produces a site that deploys successfully but generates incorrect canonical URLs, broken sitemaps, or broken routing -- and the failure mode changes depending on wheth...

Tags

Astro Site URL vs Base Path Confusion

The Lesson

Astro's site and base config fields look like they do the same thing but serve completely different purposes. Getting them wrong produces a site that deploys successfully but generates incorrect canonical URLs, broken sitemaps, or broken routing -- and the failure mode changes depending on whether you're using a subdirectory path or a custom domain.

Context

HAx migrated from Cloudflare Pages to GitHub Pages. The initial deployment served the site at bonjohen.github.io/hax/. After fixing all internal links to use the /hax/ base path (a 24-file change), the site was later moved to a custom domain (hax.johnboen.com), which serves at the root / rather than a subdirectory. This required a second configuration correction to the same astro.config.mjs that had just been fixed.

What Happened

  1. Phase 10 set site to a placeholder via env var: process.env.SITE_URL || 'https://example.github.io/hax'. No base was configured.
  2. The first fix (commit 2a63499) hardcoded site: 'https://bonjohen.github.io' and added base: '/hax', and updated all 24 files with internal links to use import.meta.env.BASE_URL. This worked correctly for the GitHub Pages subdirectory deployment.
  3. A custom domain (hax.johnboen.com) was then configured for the GitHub Pages site. With a custom domain, GitHub Pages serves at the root /, not at /hax/.
  4. The second fix (commit e948387) was needed: remove base: '/hax' entirely and change site to 'https://hax.johnboen.com'. The Playwright tests also had to be updated to remove /hax/ from their baseURL and assertion paths.
  5. The result was three commits touching astro.config.mjs in quick succession -- each one a different understanding of how site and base interact.

Key Insights

Examples

GitHub Pages subdirectory (no custom domain):

// astro.config.mjs
export default defineConfig({
  site: 'https://bonjohen.github.io',
  base: '/hax',
});
// import.meta.env.BASE_URL === '/hax/'

GitHub Pages with custom domain:

// astro.config.mjs
export default defineConfig({
  site: 'https://hax.johnboen.com',
  // No base -- custom domain serves at root
});
// import.meta.env.BASE_URL === '/'

Wrong -- path baked into site:

// astro.config.mjs
export default defineConfig({
  site: 'https://bonjohen.github.io/hax',
  // Sitemap and canonical URLs will be wrong
});

Applicability

This applies to any Astro project deployed to a platform that supports both subdirectory and custom-domain hosting (GitHub Pages, GitLab Pages, Netlify with base path). The site vs base distinction is Astro-specific, but the underlying problem -- confusing the origin with the path prefix -- exists in Next.js (basePath), Vite (base), and other frameworks.

Does NOT apply to platforms that always serve at the root (Vercel, Cloudflare Pages with custom domain) -- there, base is always / and the distinction is moot.

Related Lessons

Related Lessons