public/ + the head of every layout

Astro Favicon Generator

Astro copies public/ into dist untouched, so file placement is the easy half. The half that bites is the head: Astro sites grow multiple layouts, and each one owns its own head.

Drop an image
Drop a logo or image
PNG, JPG, WebP · square 512×512+ works best · non-square images are padded, never cropped · processed in your browser

Favicon Setup for Astro — Free, Fast & Private

Astro draws a hard line between src/assets and public/. Anything imported from src/ goes through the asset pipeline and comes out optimised, hashed and renamed — exactly what you do not want for a favicon, whose filename is load-bearing. public/ is the untouched-copy folder, and it is where all seven files belong.

Where each generated file goes in Astro

The generator above emits seven files with fixed names. Static assets in Astro live in public/, and the markup is written in src/layouts/Layout.astro (or a shared BaseHead.astro). This is the mapping between the two.

Generated fileGoes toNotes
favicon.icopublic/favicon.icoThe starter ships favicon.svg — this replaces or joins it.
favicon-16x16.pngpublic/favicon-16x16.pngLinked from the layout head.
favicon-32x32.pngpublic/favicon-32x32.pngLinked from the layout head.
favicon-48x48.pngpublic/favicon-48x48.pngOptional — already inside the .ico.
apple-touch-icon.pngpublic/apple-touch-icon.pngiOS home screen, 180×180.
android-chrome-192x192.pngpublic/android-chrome-192x192.pngManifest icon.
android-chrome-512x512.pngpublic/android-chrome-512x512.pngManifest icon and splash.

One BaseHead component, not one layout

Astro is a multi-page framework. Every route produces its own HTML document, and each document gets its head from whichever layout wrapped it. A brand-new project has a single Layout.astro and it is tempting to treat that as the head, but Astro sites accumulate layouts fast — a blog layout, a docs layout, a bare landing-page layout with no chrome — and the moment a second one appears, half your pages can quietly lose their icons.

The pattern that survives contact with a growing site is a BaseHead.astro component holding the tags every page needs: charset, viewport, favicons, and the props-driven title and description. Every layout imports it. Adding a layout then costs one import line, and there is exactly one place to edit when the icon set changes.

Astro does hoist and deduplicate the assets it injects itself, but it does not go hunting through your components for head tags to relocate. If a link element is rendered inside the body, that is where it stays. Keep them in the component that is actually placed inside <head>.

src/components/BaseHead.astro

---
const { title, description } = Astro.props;
const base = import.meta.env.BASE_URL;
---
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href={base + 'favicon.ico'} sizes="16x16 32x32 48x48" />
<link rel="icon" type="image/png" sizes="32x32" href={base + 'favicon-32x32.png'} />
<link rel="icon" type="image/png" sizes="16x16" href={base + 'favicon-16x16.png'} />
<link rel="apple-touch-icon" sizes="180x180" href={base + 'apple-touch-icon.png'} />
<link rel="manifest" href={base + 'site.webmanifest'} />
<title>{title}</title>
<meta name="description" content={description} />

The starter ships an SVG, and that is worth keeping

A fresh Astro project comes with public/favicon.svg and a single link tag with type="image/svg+xml". SVG favicons are supported across current desktop and mobile browsers and have two real advantages: one file at any resolution, and the ability to respond to the user colour scheme through a media query inside the SVG itself, so a dark mark can flip to a light one against a dark tab strip.

What they do not do is cover everything. Safari on iOS wants the raster apple-touch-icon for home screens, Android install prompts read the manifest PNGs, and enough older software still expects an .ico that the file remains worth shipping. The workable arrangement is to keep the SVG as the preferred icon, then declare the .ico and the PNGs behind it — browsers pick the first format they understand, so ordering the SVG first gives modern browsers the good one and everything else a working fallback.

Keeping the SVG in front of the raster fallbacks

<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

base, site, and subdirectory deployments

If you set the base option in astro.config.mjs — common when publishing a project site to GitHub Pages under a repository path — everything in public/ is served under that prefix. Astro does not rewrite hardcoded absolute paths inside your own markup, so a literal href="/favicon.ico" points at the domain root and misses. Read import.meta.env.BASE_URL and prefix with it, which is exactly why the BaseHead component above does so.

The separate site option is about absolute URLs for the sitemap, canonical tags and RSS. It has nothing to do with where icons resolve, and confusing the two is a reliable way to spend an afternoon. Set both, understand that only base affects asset paths, and test a built site served from a subdirectory before you rely on it.

Static output, SSR, and content collections

In the default static output mode, dist/ contains one HTML file per route plus the copied contents of public/ at the top level, which means the deployed tree looks exactly like a handwritten static site and /favicon.ico is a real file at a real path. With an SSR adapter the split changes — the server bundle lands in one directory and the client assets in another, and it is the client directory that gets served statically. Check where your adapter puts public/ files before assuming they are at the root.

Content collections do not change any of this. A markdown post rendered through a layout inherits that layout head, favicons included. What does change things is a page that renders no layout at all, which is easy to write by accident when prototyping. Such a page has no favicon markup, and the browser falls back to requesting /favicon.ico from the root — which works, but only because the .ico is sitting there.

Whatever you paste, the artwork itself never leaves your machine. The set above is rendered with the Canvas API and the .ico container is assembled in JavaScript on your device, so an unreleased logo stays unreleased. If the 16px preview looks like mush, that is the honest signal to simplify the mark before it ships to a Astro tab near you.

How it works

  1. Generate the set: Drop a square logo into the tool above; the whole set is rendered in your browser.
  2. Copy into public/: Not src/assets — anything imported from src is hashed and renamed, which breaks the fixed favicon filenames.
  3. Create or edit BaseHead.astro: Put the link tags in one shared component and import it from every layout, so adding a layout later cannot drop them.
  4. Prefix with BASE_URL if needed: If astro.config.mjs sets base, read import.meta.env.BASE_URL rather than hardcoding a leading slash.

Frequently asked questions

Do Astro favicons go in public/ or src/assets?
public/. Files imported from src/ go through the asset pipeline and come out hashed and renamed, which is the opposite of what a favicon needs — the filenames are load-bearing, especially /favicon.ico. public/ is copied into dist untouched.
Where do the link tags belong in an Astro project?
In a shared BaseHead.astro component imported by every layout, placed inside the head. Astro is multi-page and each layout owns its own head, so putting them in a single Layout.astro means any second layout you add later silently ships without icons.
Should I keep the favicon.svg the starter shipped?
Yes, as the first icon declaration. SVG favicons scale to any size from one file and can flip colour with prefers-color-scheme via a media query inside the SVG. Declare the .ico and the apple-touch-icon after it as fallbacks for the platforms that do not read SVG.
My favicon 404s on GitHub Pages.
The base option in astro.config.mjs prefixes everything in public/ with your repository path, and Astro does not rewrite hardcoded absolute paths inside your markup. Read import.meta.env.BASE_URL and prefix the hrefs with it instead of writing a bare leading slash.
What is the difference between site and base?
site is the absolute origin used for canonical URLs, sitemaps and RSS. base is the subpath the app is served from and is the only one that affects where asset URLs resolve. Setting site does nothing for a broken icon path.
Does Astro hoist head tags out of components?
It manages and deduplicates the assets it injects itself, but it does not relocate arbitrary tags you wrote. A link element rendered inside the body stays in the body. Keep icon tags in a component that is placed inside head.
Do content collection pages get the favicon automatically?
Yes, as long as they render through a layout that includes your head component. A page written without any layout has no icon markup at all, and only works because browsers still request /favicon.ico from the root by convention.

All Image Tools

Solutions by use case