The public Directory
The public/ folder is Astro’s escape hatch for files that must be served exactly as they are, untouched by the build pipeline. Anything placed here is copied verbatim to the root of your final output, keeping its filename, byte content, and URL path intact. This is the right home for assets that browsers, crawlers, and third-party tools expect to find at a predictable, stable address — favicons, robots.txt, web manifests, and verification files. Understanding when to reach for public/ versus importing through src/ is one of the most important distinctions in an Astro project.
How public files are served
Every file inside public/ is mapped 1:1 to your site root. A file at public/favicon.svg becomes available at /favicon.svg, and public/fonts/inter.woff2 is served from /fonts/inter.woff2. The folder name itself never appears in the URL — Astro strips it during the build and copies the contents straight into dist/.
public/
├── favicon.svg → /favicon.svg
├── robots.txt → /robots.txt
├── manifest.webmanifest → /manifest.webmanifest
└── fonts/
└── inter.woff2 → /fonts/inter.woff2
Because these paths are absolute and predictable, you reference them with a leading slash in your markup. There is no import, no hashing, and no transformation.
---
// src/layouts/BaseLayout.astro
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="manifest" href="/manifest.webmanifest" />
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
The
public/path is always relative to the site root, never to the current page. Use/favicon.svg, not./favicon.svgorfavicon.svg, or links will break on nested routes.
public versus src for assets
The key decision is whether you want Astro to process an asset or leave it alone. Files imported from src/ (or referenced via the <Image /> component and astro:assets) flow through the build: they are optimized, hashed for cache-busting, bundled, and may be inlined. Files in public/ skip all of that.
| Aspect | public/ | src/ (imported) |
|---|---|---|
| Build processing | None — copied as-is | Optimized, minified, transformed |
| Filename in output | Unchanged | Content-hashed (e.g. logo.a1b2c3.svg) |
| Cache busting | Manual | Automatic via hash |
| How you reference it | Absolute URL string (/logo.svg) | import / <Image /> |
| Build-time validation | None (broken paths fail silently) | Errors if the file is missing |
| Best for | Favicons, robots.txt, fonts, verification files | Images in content, CSS, JS, anything optimizable |
As a rule, if a tool or spec demands a fixed URL — search engines reading /robots.txt, a payment provider polling a verification file — use public/. For everything you author and reference inside components, prefer importing from src/ so you get optimization and cache-busting for free.
---
// Prefer this for content imagery: optimized + hashed
import { Image } from 'astro:assets';
import hero from '../assets/hero.png';
---
<Image src={hero} alt="Product hero" width={1200} height={630} />
<!-- Use public/ only when a stable, literal URL is required -->
<img src="/og-default.png" alt="Default social card" />
Common public files
A few files are conventionally placed in public/ because external systems look for them at the root:
robots.txt
sitemap-index.xml (if generated to public, or via @astrojs/sitemap)
favicon.svg
apple-touch-icon.png
manifest.webmanifest
.well-known/security.txt
A minimal public/robots.txt:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap-index.xml
Folders beginning with a dot, such as
public/.well-known/, are copied too. This is how you servesecurity.txtor domain-verification files that must live under a hidden path.
Configuring the directory
The location and base URL of public assets are configurable. By default the folder is public/, but you can change it with publicDir, and you can serve the whole site under a sub-path with base.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://example.com',
base: '/docs', // site served under /docs
publicDir: './public' // default; change only if you must
});
When base is set, a file at public/favicon.svg is served from /docs/favicon.svg. Use import.meta.env.BASE_URL to build correct links instead of hardcoding the prefix:
---
const base = import.meta.env.BASE_URL; // "/docs/" in the example above
---
<link rel="icon" href={`${base}favicon.svg`} />
Best Practices
- Reserve
public/for files that genuinely need a fixed, unprocessed URL — favicons,robots.txt, manifests, and verification files. - Import content images and other optimizable media from
src/so you get automatic optimization and content-hashed cache-busting. - Always reference public assets with a leading slash relative to the site root, and account for
basewhen your site lives under a sub-path. - Remember that public paths are not validated at build time; double-check filenames, since a typo fails silently with a 404.
- Keep
public/flat and intentional — large unprocessed assets here bypass minification and ship at full size. - Put hidden-path requirements (like
.well-known/security.txt) directly underpublic/; Astro copies dot-prefixed folders.