Astro base Path: Fix Broken Assets and Links Under a Subfolder
If an Astro site works at https://example.com/ but loses CSS, images, or internal links at https://example.com/project/, check base before changing the web server. Astro needs to know the public subpath so it can build route and asset URLs consistently.
The direct fix is to set site to the deployed origin, set base to the subfolder, and use import.meta.env.BASE_URL for URLs that you write yourself. Then inspect the production dist/ output and a real deployed URL; a local root preview does not prove that a project-page deployment is correct.
Separate site from base
These two settings answer different questions:
| Setting | Answers | Example |
|---|---|---|
site | What is the final public origin? | https://octocat.github.io |
base | Which path is the site root below that origin? | /my-repo |
For a GitHub Pages project site, the configuration normally looks like this:
import { defineConfig } from 'astro/config';
export default defineConfig({ site: 'https://octocat.github.io', base: '/my-repo', trailingSlash: 'always',});The public URL is now https://octocat.github.io/my-repo/, not https://octocat.github.io/. A user site whose repository is named octocat.github.io is the important exception: it is published at the origin root, so it usually does not need a repository base.
Do not use site as a substitute for base. site helps Astro produce absolute URLs such as canonical and sitemap URLs; it does not tell Astro that every page and asset lives below /my-repo.
Astro’s GitHub Pages deployment guide shows the same distinction. If you later move the site to a custom domain, remove the repository base and update site after verifying every internal link.
Prefix URLs that you own
Astro can process imported assets, but URLs typed by hand are still your responsibility. The common failures are:
href="/about/"points to the domain root instead of the project root.src="/logo.svg"looks for a file outside the repository subpath.fetch("/data/posts.json")sends the request to the wrong origin path.- A client-side router or third-party widget builds links without knowing the base.
Use BASE_URL at the boundary where your code creates the URL:
---const aboutUrl = import.meta.env.BASE_URL + 'about/';const logoUrl = import.meta.env.BASE_URL + 'logo.svg';---
<a href={aboutUrl}>About</a><img src={logoUrl} alt="Site logo" />The value of BASE_URL follows Astro’s trailing-slash configuration. Keep the path fragments in the example relative to that value; do not add another leading slash unless your helper intentionally normalizes it.
For client code, expose the base through the generated bundle rather than assuming that / is the application root:
const assetUrl = import.meta.env.BASE_URL + 'data/posts.json';const response = await fetch(assetUrl);If a library requires a browser-relative URL, document.baseURI can be a useful second check. The key is that the generated request must contain the same public prefix as the page that made it.
Watch the difference between imported and public files
An image imported from a component is part of Astro’s build pipeline. A file placed in public/ is copied as a static URL, so references such as /favicon.svg or /robots.txt still need a deployment-aware path when they are used inside page markup or client code.
For a public file, verify both sides:
---const manifestUrl = import.meta.env.BASE_URL + 'manifest.webmanifest';---
<link rel="manifest" href={manifestUrl} />For a component image, prefer the normal Astro asset import and let Astro generate the final file URL. Do not convert every image into a manually assembled string just to add base; that can bypass image processing and create a second path bug.
The same rule applies to Pagefind. A local preview can find dist/pagefind/ even when the deployed HTML points at /pagefind/ instead of /my-repo/pagefind/. Compare the generated URL with the actual published artifact using Pagefind on GitHub Pages: Fix Missing Search Assets After Deploy.
Test the generated site at its real path
Build first, then inspect the files and HTML:
pnpm buildfind dist -maxdepth 3 -type f | sortrg -n 'href="/|src="/|url\(/' distrg -n '/my-repo/' distThe first search is not automatically an error: it can match canonical URLs or intentionally root-relative data. Use it to find candidates, then check whether the URL should include /my-repo/. The second search should show the configured prefix in pages and asset references where it is required.
Test a deployed project page as well:
curl -I https://octocat.github.io/my-repo/curl -I https://octocat.github.io/my-repo/about/curl -I https://octocat.github.io/my-repo/logo.svgIf the HTML loads but the stylesheet or image returns 404, inspect the failing request URL in the browser network panel. If every URL has the right prefix but the server still returns 404, the host or deployment artifact is the next boundary. The Astro static route 404 guide covers the separate case where the host cannot map a generated route to its index file.
Avoid two common configuration traps
The repository name changed
Changing /my-repo to /new-repo is a public URL change. Update base, rebuild, and check canonical URLs, sitemap entries, RSS links, asset URLs, and any hard-coded links in the same change. If the old path was indexed, plan a redirect rather than silently serving a second URL.
A custom domain is added later
When GitHub Pages serves the same project through a custom domain, the repository path is no longer part of the public URL. Keep the custom domain in site, remove the repository base, and regenerate the site. Leaving base: '/my-repo' in place makes the new domain continue requesting files from a path that no longer exists.
The safest check is not a configuration diff. It is a production build followed by a small set of requests for the homepage, one nested route, one public asset, and one client-side data request.
FAQ
Q: Do I need both site and base for Astro on GitHub Pages?
A: A project site usually needs both: site identifies the GitHub Pages origin and base identifies the repository subpath. A user site at <username>.github.io is published at the origin root and usually does not need a repository base.
Q: Why do my Astro links work locally but lose the repository name after deployment?
A: Root-relative URLs such as /about/ and /logo.svg start at the domain root. Use import.meta.env.BASE_URL when building URLs yourself, then check the production HTML and network requests for the expected prefix.
Q: Should I set base to the full deployed URL?
A: No. Set site to the full origin and base to the path portion, such as /my-repo. A full URL in base describes the wrong configuration boundary.
References:
Astro configuration reference:
siteandbase
Report a typo or broken link, or suggest a related topic.