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.html favicons ship in the SSR HTML on every route automatically.
  • +layout.svelte favicons 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. /static files copy to build/. 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 /static directly. No special config.
  • adapter-cloudflare. Static files are served from Workers Assets. Same paths, same behaviour.

Common pitfalls

  • Putting files in /src/lib/assets instead of /static. Files in /src get 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. Use app.html.
  • Missing MIME type for .webmanifest. If you self-host with a raw Node process, configure the static serve layer to emit application/manifest+json for .webmanifest. Adapters handle this automatically.
  • Dev server caching. Restart vite dev after 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.