Skip to content
Astro as project 4 min read

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.svg or favicon.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.

Aspectpublic/src/ (imported)
Build processingNone — copied as-isOptimized, minified, transformed
Filename in outputUnchangedContent-hashed (e.g. logo.a1b2c3.svg)
Cache bustingManualAutomatic via hash
How you reference itAbsolute URL string (/logo.svg)import / <Image />
Build-time validationNone (broken paths fail silently)Errors if the file is missing
Best forFavicons, robots.txt, fonts, verification filesImages 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 serve security.txt or 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 base when 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 under public/; Astro copies dot-prefixed folders.
Last updated June 14, 2026
Was this helpful?