You've wired up your SvelteKit routes, polished the layouts, and deployed the build. But when you open your live tab, you see the default Svelte logo — or worse, a blank white square. The fix usually lives in one specific file: app.html.

app.html is the HTML shell that SvelteKit uses to render every page on both the server and client. According to the SvelteKit project structure docs, this template sits at the root of src/ and is the only place where you can inject markup that survives hydration on every route. That makes it the perfect home for your favicon <link> tags.

Why app.html and not the layout?

You can add favicon tags inside a +layout.svelte file's <svelte:head> block, and it will work for most routes. But app.html is rendered first, before any layout or component code runs. That gives it two real advantages:

Prerequisites

You'll need:

If you don't have those files yet, generate them with Mzu favicondl from any source image — it outputs the full size package in one click.

Step-by-step: Add favicon link tags to app.html

1. Locate your app.html file

Open src/app.html. By default, it looks roughly like this:

<!doctype html>
<html lang='en'>
  <head>
    <meta charset='utf-8' />
    <link rel='icon' href='%sveltekit.assets%/favicon.png' />
    <meta name='viewport' content='width=device-width, initial-scale=1' />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data='hover'>
    <div style='display: contents'>%sveltekit.body%</div>
  </body>
</html>

Notice the %sveltekit.assets% and %sveltekit.head% placeholders — those get replaced at build time. You can keep them and add more tags right above or below.

2. Replace the default favicon line

The starter template ships with a single favicon.png line. Delete it and replace it with a complete set of tags. The HTML spec defines rel='icon' as the standard way to declare icons, so stick to that attribute.

<link rel='icon' href='%sveltekit.assets%/favicon.svg' type='image/svg+xml' />
<link rel='icon' href='%sveltekit.assets%/favicon.ico' sizes='32x32' />
<link rel='apple-touch-icon' href='%sveltekit.assets%/apple-touch-icon.png' sizes='180x180' />
<link rel='manifest' href='%sveltekit.assets%/site.webmanifest' />

The SVG goes first so modern browsers prefer it. The ICO acts as a fallback for older browsers. Apple Touch and manifest come last because they target specific platforms.

3. Add dark mode support

If your SVG icon needs a different look in dark tabs (and these days, who isn't running a dark theme?), declare two media-specific sources. This is the same technique we cover in our favicon dark mode guide:

<link rel='icon' href='%sveltekit.assets%/favicon-light.svg' type='image/svg+xml' media='(prefers-color-scheme: light)' />
<link rel='icon' href='%sveltekit.assets%/favicon-dark.svg' type='image/svg+xml' media='(prefers-color-scheme: dark)' />

The browser evaluates the media attribute and picks the matching icon.

4. Drop your files into static/

SvelteKit serves everything inside static/ from the site root. Place each favicon file there with the exact filename referenced in your tags. A clean structure looks like:

static/
├── favicon.svg
├── favicon.ico
├── favicon-light.svg
├── favicon-dark.svg
├── apple-touch-icon.png
└── site.webmanifest

5. Restart the dev server

Vite caches the HTML shell aggressively. After editing app.html, stop your dev server with Ctrl+C and run npm run dev again. Otherwise you may keep seeing stale markup.

Common pitfalls

Putting favicon tags in the body. Browsers only look at <head> for icon declarations. Anything inside <body> is ignored, even when the file is named app.html. If yours aren't appearing, this is the first thing to check.

Mismatched filenames between tags and static/. Case sensitivity matters on Linux deploys. favicon.ICO and favicon.ico are different files. Match the names exactly.

Stale icons after deployment. Browsers cache favicons aggressively — sometimes for weeks. If you swap files and don't see updates, append a query string or content hash, then redeploy. The same pattern works for any static asset in static/.

Double-encoding SVG attributes. If you copy a snippet that uses type="image/svg+xml" with double quotes into the app.html shell, Vite can mangle it. Stick with single quotes inside HTML attributes to keep the build happy.

Wrapping up

The app.html template is the cleanest place to declare favicons in SvelteKit because it runs before any JavaScript and ships with every page response. Drop a complete SVG + ICO + Apple Touch stack into static/, wire the corresponding <link> tags into <head>, and you'll have a crisp icon on every browser tab from the very first paint.

For a broader look at SvelteKit favicon options — including the +layout.svelte approach and dynamic icons — see our main SvelteKit favicon guide. And if you need the actual icon files, Mzu favicondl generates the full size stack from any source image in seconds.