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
typeattribute on SVG. Withouttype="image/svg+xml", some browsers refuse to load the SVG favicon. Always set it. - Putting files in
/srcinstead of/public. Files in/srcget bundled by Vite; files in/publicget 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
/publicwhile 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.
Other frameworks
- SvelteKit SvelteKit's favicon setup lives in two places: /static for the files and src/app.html for the link tags. Here is the current recommended configuration.
- Next.js How to ship a complete modern favicon set in a Next.js App Router project. Where files go, which metadata API to use, and the specific gotchas of the /app directory.