If you've ever spun up a new Astro project, deployed it to Vercel or Netlify, and realized your production site is still proudly displaying that default gradient rocket icon in the browser tab — you are not alone. I see this happen constantly when developers migrate from Next.js or raw HTML. Setting up an astro framework favicon isn't difficult, but Astro's specific asset pipeline means you can't just throw an `.ico` file anywhere and hope for the best.

Astro handles static assets differently than traditional bundlers. If you put your icon in the wrong folder, Vite will either ignore it, hash the filename, or throw a 404 error in production. We are going to fix that right now.

The Great Divide: public/ vs src/assets/

The most common mistake developers make in Astro is placing their favicon inside the src/assets/ directory. Astro's asset pipeline is fantastic for optimizing content images, but it aggressively hashes filenames for cache control (e.g., turning icon.svg into icon.a8b3c9.svg).

Browsers, RSS readers, and web crawlers expect your favicon to have a predictable, static URL — ideally right at the root of your domain. To achieve this, you must place your favicon files in the public/ directory.

Astro copies everything inside the public/ folder directly to your build output (usually the dist/ folder) without touching the filenames. This guarantees that public/favicon.svg becomes yoursite.com/favicon.svg.

Step 1: Prepare Your Modern Icon Stack

We are in 2026. Stop generating 30 different legacy icon sizes for devices that haven't existed in a decade. You only need three files for a perfect setup.

If you only have a high-resolution PNG of your logo, run it through Mzu favicondl to instantly generate this exact three-file stack. (For a deeper dive into why this specific stack wins, check out our guide on favicon best practices).

Step 2: Place Files in the Public Directory

Take the three files you just generated and drop them directly into the root of your Astro project's public/ folder. Your project structure should look like this:

├── public/
│   ├── favicon.svg
│   ├── favicon.ico
│   └── apple-touch-icon.png
├── src/
│   ├── components/
│   ├── layouts/
│   └── pages/
└── astro.config.mjs

Do not nest them in a public/images/ folder. Keep them at the root. This ensures tools that blindly request /favicon.ico without reading your HTML will still find what they need.

Step 3: Update Your BaseHead Component

Most modern Astro templates use a shared component for the <head> section, typically named BaseHead.astro or handled inside Layout.astro. Open that file.

You need to add the correct HTML link tags. Notice the leading slash in the href attributes — this is critical.

<!-- Inside src/components/BaseHead.astro -->
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width,initial-scale=1' />

<!-- Favicon Stack -->
<link rel='icon' href='/favicon.ico' sizes='32x32' />
<link rel='icon' href='/favicon.svg' type='image/svg+xml' />
<link rel='apple-touch-icon' href='/apple-touch-icon.png' />

<!-- Optional: Web App Manifest -->
<link rel='manifest' href='/site.webmanifest' />

Look at how GitHub handles their browser icons. They serve a crisp SVG for modern browsers that dynamically switches colors based on your system theme, but they keep the routing dead simple at the root level. By using Astro's public/ directory and absolute paths, we replicate this GitHub-level polish effortlessly.

Common Pitfalls in Astro

The Missing Leading Slash

If you write href='favicon.svg' instead of href='/favicon.svg', your icon will break on nested routes. For example, if a user visits yoursite.com/blog/my-post/, the browser will look for the icon at yoursite.com/blog/my-post/favicon.svg and fail. Always use the absolute path starting with a slash. If you're still having trouble, review our troubleshooting guide for missing favicons.

Base Path Configuration Issues

If you are deploying your Astro site to a subdirectory (like GitHub Pages) and have configured the base option in astro.config.mjs, standard absolute paths will break. You need to use Astro's built-in base helper.

Import import.meta.env.BASE_URL in your frontmatter and apply it to your links:

---
const baseUrl = import.meta.env.BASE_URL;
---
<link rel='icon' href=`${baseUrl}favicon.svg` type='image/svg+xml' />

This ensures your favicon paths dynamically adjust whether you are running locally on localhost or deployed to a nested production URL.

Getting your Astro framework favicon right takes about two minutes once you understand the routing rules. Stick to the public/ folder, use absolute paths, and let the browser handle the rest.