Start an Astro project

Choose src or public for an asset

Put processed project assets under src and reserve public for files that must keep their exact name and bytes.

Every image, PDF, or font in an Astro project goes in one of two places: public/ or src/. The choice depends on one question. Do you want Astro to process the file, or to leave it alone?

Public files keep their name and bytes

A file in public/ is copied to the output exactly as it is. Its path inside public/ becomes its URL:

public/favicon.svg → /favicon.svg
public/downloads/guide.pdf → /downloads/guide.pdf

You reference it by that root URL, in a plain HTML attribute:

<a href="/downloads/guide.pdf">Download the guide</a>

Be careful with one common mistake here. The URL is /downloads/guide.pdf, not /public/downloads/guide.pdf. The word public never appears in the browser.

Source assets get imported

A file under src/ is imported like a module. Put an image in src/assets/ and import it in the component script:

---
import diagram from '../assets/request-flow.png'
---

Now diagram is an object Astro knows about. It knows the file exists, its width and height, and its format. At build time Astro can optimize it, resize it, and give the output a fingerprinted filename so browsers can cache it forever.

How to choose

Use public/ when the exact path matters, or when you don’t want any processing. robots.txt, a web manifest, a font file, a PDF people link to from elsewhere.

Use src/ for everything that belongs to the project. Photos, illustrations, screenshots, stylesheets, scripts. Anything that benefits from optimization and build-time checks.

My default is src/. I reach for public/ only when I have a reason.

The real difference is when you find out

Here’s the part that matters most. A broken URL to a public file is just a string, so the build can’t tell it’s wrong. npm run build succeeds, and the visitor gets a 404.

A broken import fails right away:

Could not import "../assets/request-flow.png"

The build stops, and you fix it before it ships. That early feedback is the main reason I keep assets under src/.

Try this on the astro-notes project: rename one file in public/ and one imported file in src/assets/ without updating the references. Run npm run build. One error shows up in the terminal. The other one waits for you in the browser.

Lesson completed