App Router · file conventions + metadata export

Next.js Favicon Generator

Make the complete set here, then wire it up the Next way — app/icon.png and app/apple-icon.png, or public/ files declared in the metadata export. Both routes are shown, with the caveats that bite.

Drop an image
Drop a logo or image
PNG, JPG, WebP · square 512×512+ works best · non-square images are padded, never cropped · processed in your browser

Favicon Setup for Next.js — Free, Fast & Private

Next.js is the only framework in this list that will generate the <head> markup for you, and that is exactly where people get tripped up: pick one mechanism and stay in it. If you drop app/icon.png in AND declare icons in the metadata export, you get two competing <link rel="icon"> tags in the rendered head and a browser picking one at random.

Where each generated file goes in Next.js

The generator above emits seven files with fixed names. Static assets in Next.js live in app/ (file conventions) or public/, and the markup is written in app/layout.js — metadata export. This is the mapping between the two.

Generated fileGoes toNotes
favicon.icoapp/favicon.icoRoot app segment only. Next emits the link tag for you.
favicon-32x32.pngapp/icon.pngRename it. Next reads the real pixel size and writes sizes= itself.
apple-touch-icon.pngapp/apple-icon.pngRename it. Becomes rel="apple-touch-icon" automatically.
favicon-16x16.pngapp/icon0.png / icon1.pngNumbered suffixes when you want more than one <link rel="icon">.
favicon-48x48.pngpublic/favicon-48x48.pngAlready inside favicon.ico. Keep loose only if you link it by hand.
android-chrome-192x192.pngpublic/android-chrome-192x192.pngNever linked from HTML — referenced from the manifest.
android-chrome-512x512.pngpublic/android-chrome-512x512.pngManifest icon for PWA install and Android splash.

Route one: the app/ file conventions

Since the App Router landed, Next.js treats certain filenames inside app/ as route handlers that emit metadata. Drop a file named icon.png, icon.svg, icon.jpg or icon.ico into a segment folder and Next serves it and injects the matching <link rel="icon"> into every page rendered under that segment. Do the same with apple-icon.png and you get rel="apple-touch-icon". You write no markup at all.

favicon.ico is the exception with a real constraint attached: it is only honoured in the root app/ directory, not in nested segments. Put app/(marketing)/favicon.ico somewhere deeper and it is silently ignored. The icon and apple-icon conventions, by contrast, work at any depth, which is how a docs section or a whitelabelled dashboard route can carry its own icon while the rest of the site keeps the default.

The best part of this route is free cache busting. Next appends a content hash to the emitted URL — /icon.png?f8a2c1 — so when you replace the file the query string changes and browsers fetch the new icon instead of serving the sticky cached one for a week. That single behaviour solves the most common favicon support ticket there is.

File layout — the convention route

app/
  favicon.ico                 ← root segment only; 16+32+48 inside
  icon.png                    ← rename favicon-32x32.png
  apple-icon.png              ← rename apple-touch-icon.png
  layout.js
public/
  android-chrome-192x192.png
  android-chrome-512x512.png
  site.webmanifest

Route two: public/ plus the metadata export

The convention route hides the markup, which is fine until you need control over it — a sizes attribute Next did not guess the way you wanted, an icon served from a CDN, or a set you want to keep byte-identical to what the rest of your infrastructure expects. In that case put all seven generated files in public/ under their original names and declare them explicitly from the metadata export in app/layout.js.

Anything in public/ is served from the URL root verbatim: public/favicon.ico is /favicon.ico. Next does not fingerprint or transform these files, which is the flip side of the convention route — no automatic hash means you own the cache-busting problem. If you ship a redesign, either version the filename or accept that returning visitors keep the old icon until their cache expires.

One deployment detail that catches teams shipping under a path prefix: if basePath is set in next.config.js, public/ assets are served under that prefix, but strings you write into the metadata export are not rewritten. Either include the prefix in the URLs yourself or read it from a shared constant. This is the failure mode where the favicon works locally and 404s in staging.

app/layout.js — explicit declaration

export const metadata = {
  icons: {
    icon: [
      { url: '/favicon.ico', sizes: '16x16 32x32 48x48' },
      { url: '/favicon-32x32.png', type: 'image/png', sizes: '32x32' },
      { url: '/favicon-16x16.png', type: 'image/png', sizes: '16x16' },
    ],
    apple: [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
  },
  manifest: '/site.webmanifest',
};

The manifest, and why two PNGs never appear in your HTML

android-chrome-192x192.png and android-chrome-512x512.png are not <link> targets. They belong in the web app manifest, which is what Chrome on Android reads when a user taps "Add to Home Screen" and what supplies the splash screen image on launch. You can ship a static public/site.webmanifest and point at it from metadata.manifest, or use the app/manifest.js file convention and return the object from code — useful when the app name or theme colour comes from an environment variable.

Getting this wrong is invisible in a desktop browser and obvious the moment someone installs the app: a generic Chrome icon on the home screen, or a blank white splash. If you support installs at all, test it on a real Android device rather than trusting the tab icon.

app/manifest.js — generated at build time

export default function manifest() {
  return {
    name: 'Your App',
    short_name: 'App',
    start_url: '/',
    display: 'standalone',
    icons: [
      { src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
      { src: '/android-chrome-512x512.png', sizes: '512x512', type: 'image/png' },
    ],
  };
}

Pages Router, and static export

None of the app/ conventions exist in the Pages Router. There, public/ is the only home for the files and the tags go inside a next/head block — in pages/_document.js if you want them on every page, which is almost always what you want for favicons. Putting them in _app.js works too but re-runs the head reconciler on every client navigation for no benefit. Projects part-way through an App Router migration should check both trees; a leftover _document.js still renders for any route the Pages Router still owns.

With output: "export" the story is simpler than people expect. public/ is copied into out/ untouched, and the icon route handlers are pre-rendered into static files with their hashed URLs baked into the HTML. A statically exported Next site needs no special favicon handling — just confirm your host is not stripping the query string, because some naive static servers treat /icon.png?f8a2c1 as a missing file.

Whatever you paste, the artwork itself never leaves your machine. The set above is rendered with the Canvas API and the .ico container is assembled in JavaScript on your device, so an unreleased logo stays unreleased. If the 16px preview looks like mush, that is the honest signal to simplify the mark before it ships to a Next.js tab near you.

How it works

  1. Generate the set: Drop a square 512px logo into the tool above. You get favicon.ico plus the six PNGs, all rendered in your browser.
  2. Pick a mechanism: Either the app/ file conventions (rename to icon.png and apple-icon.png) or public/ files plus an explicit metadata export. Not both.
  3. Wire the manifest: Put the two android-chrome PNGs in public/ and reference them from app/manifest.js or a static site.webmanifest.
  4. Verify in the built HTML: Run next build, then view source on a rendered page and confirm exactly one <link rel="icon"> and one apple-touch-icon are present.

Frequently asked questions

Do I put the favicon in app/ or public/ in Next.js?
Both work. app/favicon.ico, app/icon.png and app/apple-icon.png are file conventions that make Next generate the head tags and append a content hash for cache busting. public/ is the plain static route where you declare the tags yourself through the metadata export. Choose one — using both emits duplicate link tags.
Why is my Next.js favicon not updating after I replaced the file?
If you used public/, nothing in the URL changed, so browsers serve the cached icon. The app/icon.png convention avoids this by appending a content hash to the URL. On the public/ route, clear .next, hard-refresh, and if it matters to you, version the filename.
Does app/favicon.ico work inside a route group or nested segment?
No. favicon.ico is honoured only in the root app/ directory. The icon.png and apple-icon.png conventions do work in nested segments, which is how a sub-route can carry a different icon from the rest of the site.
How do I add several icon sizes with the file convention?
Use numbered suffixes: icon.png, icon0.png, icon1.png and so on. Next emits one link tag per file and reads each image to fill in the sizes attribute. For finer control over type and sizes, switch to the metadata export instead.
Where do the android-chrome PNGs go in a Next.js app?
public/, and they are referenced from the web app manifest rather than from any link tag. Either ship a static public/site.webmanifest and set metadata.manifest, or use the app/manifest.js file convention and return the icons array from code.
Do favicons work with output: "export"?
Yes. public/ is copied into out/ as-is, and the icon route handlers are pre-rendered to static files with their hashed URLs already written into the HTML. The only thing to check is that your static host does not choke on the query string in /icon.png?hash.
What changes in the Pages Router?
The app/ conventions do not exist. Put every file in public/ and write the link tags inside a next/head block in pages/_document.js so they render on every page. If you are mid-migration, check both trees for leftover duplicate tags.

All Image Tools

Solutions by use case