Astro

Favicons in Astro — the public folder pattern

Every Astro site ships favicons the same way: drop files in /public, link them from a BaseLayout. This guide is the modern five-file setup for Astro projects.

Astro has the simplest favicon story of any modern framework. Drop files in /public, link them from your BaseLayout, done. This guide is the current recommended setup.

The file layout

public/
├── favicon.svg
├── favicon.ico
├── favicon-96x96.png
├── apple-touch-icon.png
├── web-app-manifest-192x192.png
├── web-app-manifest-512x512.png
└── site.webmanifest

Files in /public copy to the build output unchanged at matching paths. No processing, no import statements, no content collection. public/favicon.svg becomes /favicon.svg.

The BaseLayout

Inside src/layouts/BaseLayout.astro:

---
const { title, description, canonical } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title}</title>
    <meta name="description" content={description} />
    <link rel="canonical" href={canonical} />

    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
    <link rel="manifest" href="/site.webmanifest" />
    <meta name="theme-color" content="#0a0a0a" />
  </head>
  <body>
    <slot />
  </body>
</html>

Every page wraps its content in <BaseLayout> and gets the full favicon set automatically.

Optional: Astro Content Collections metadata

If you want favicon paths to live in frontmatter, define them in a content collection and pass through your layout:

// src/content.config.ts
import { defineCollection, z } from "astro:content";

const site = defineCollection({
  type: "data",
  schema: z.object({
    favicons: z.object({
      svg: z.string(),
      apple: z.string(),
    }),
  }),
});

Overkill for most sites. Stick with hardcoded paths in BaseLayout unless you run multiple brands off one codebase.

The manifest

public/site.webmanifest:

{
  "name": "Your Site",
  "short_name": "YourSite",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#0a0a0a",
  "background_color": "#0a0a0a",
  "icons": [
    { "src": "/web-app-manifest-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
    { "src": "/web-app-manifest-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}

Astro serves .webmanifest files with the correct application/manifest+json content type when deployed to Vercel, Netlify, or Cloudflare Pages. If you self-host, configure the MIME type on your server.

Common pitfalls

  • Forgetting the type attribute on SVG. Without type="image/svg+xml", some browsers refuse to load the SVG favicon. Always set it.
  • Putting files in /src instead of /public. Files in /src get bundled by Vite; files in /public get copied verbatim. Favicons belong in /public.
  • Trying to import favicon as an ES module. Do not import favicon from "../assets/favicon.svg". Reference it by public path instead.
  • Missing dev server restart. Adding a new file to /public while the dev server is running sometimes gets missed. Restart if a new file 404s.

Generate the set

Drop your logo SVG into Faviconry, download the zip, unzip into /public. Reference from BaseLayout. First deploy serves the set.

Frequently asked questions

Do I need anything special for Astro's static output?

No. Files in /public are copied verbatim to the build output at the same paths. A favicon.svg in /public is served at /favicon.svg in production. No imports, no Astro-specific syntax.

Should favicon links live in a layout or in individual pages?

Always a layout. Pages import BaseLayout, BaseLayout renders <head>. Duplicating favicon tags across every page is a maintenance hazard. If a single page needs a different icon, pass a prop and override inside the layout.

Why is my SVG favicon not updating in dev?

Astro's dev server caches /public aggressively. Restart the dev server after changing /public/favicon.svg, or append a query string like /favicon.svg?v=2 in the <link> tag during development.