SvelteKit
Favicons in SvelteKit — static folder and app.html
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.
SvelteKit splits favicon setup across two files: /static for the icon files and src/app.html for the <link> tags. This guide is the current modern setup, covering every adapter.
The file layout
static/
├── 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 /static are served at the root: static/favicon.svg becomes /favicon.svg. No processing, no import statements.
app.html
Inside src/app.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<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" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
Favicon tags render before %sveltekit.head%, which means they ship in the initial HTML regardless of route. The browser sees the favicon immediately.
Why not +layout.svelte
SvelteKit supports <svelte:head> inside +layout.svelte for route-level head tags. You can put favicons there, but there is no reason to.
app.htmlfavicons ship in the SSR HTML on every route automatically.+layout.sveltefavicons hydrate after JavaScript loads. First paint can show the wrong (or no) icon.- Route-level head tags are for things that vary by route (page title, canonical), not for stable site-wide assets.
Keep favicons in app.html. Keep per-page metadata in <svelte:head>.
The manifest
static/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" }
]
}
Reference from app.html with <link rel="manifest" href="/site.webmanifest" />.
Adapter notes
- adapter-static.
/staticfiles copy tobuild/. All favicon paths work identically. - adapter-vercel. Static files are served from the CDN with long-lived cache headers. Rename or hash favicons on change.
- adapter-node. Your Node server serves
/staticdirectly. No special config. - adapter-cloudflare. Static files are served from Workers Assets. Same paths, same behaviour.
Common pitfalls
- Putting files in
/src/lib/assetsinstead of/static. Files in/srcget bundled by Vite with content hashes. Favicons need stable root paths like/favicon.svg, not/_app/immutable/assets/favicon-abc123.svg. Use/static. - Using
<svelte:head>for favicons. They ship late. Useapp.html. - Missing MIME type for .webmanifest. If you self-host with a raw Node process, configure the static serve layer to emit
application/manifest+jsonfor.webmanifest. Adapters handle this automatically. - Dev server caching. Restart
vite devafter changing files in/static. The dev server sometimes misses new assets.
Generate the set
Drop your logo SVG into Faviconry, unzip the download into /static, paste the five <link> tags and the theme-color meta into src/app.html.
Frequently asked questions
Is it /static or /public in SvelteKit?
SvelteKit uses /static, not /public. Files there are served at the root in production. The Vite preset and most community starters ship with /static preconfigured. You can rename it via kit.files.assets in svelte.config.js but there is no reason to.
Can I put favicon links in +layout.svelte instead of app.html?
You can, using <svelte:head>, but app.html is better. app.html renders before any JavaScript hydrates, so the favicon appears instantly on first load and is visible even if JS fails. Layout-level head tags work but are strictly worse for favicons.
Does adapter-static need anything different?
No. /static copies to the build output for every adapter, including adapter-static, adapter-vercel, adapter-node, and adapter-cloudflare. The favicon setup is adapter-agnostic.
Other frameworks
- Astro 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.
- Vue / Nuxt Vue with Vite uses /public for favicons and index.html for link tags. Nuxt 3 adds app.head config for declarative setup. This guide covers both.