nextjs

Next.js Favicon

Add icons correctly with App Router file conventions, the Metadata API, generated images, or the Pages Router, then verify the production build.

FaviconMaker Editorial Team

9 min read
#nextjs#app-router#metadata#setup#troubleshooting
Share this guide
Next.js Favicon
On this page28 sections

For a new App Router project, the simplest setup is to place favicon.ico, icon.png, and apple-icon.png in the root app directory. Next.js detects those reserved filenames and generates the <link> elements for you.

app/
├── apple-icon.png
├── favicon.ico
├── icon.png
├── layout.tsx
└── page.tsx

You do not need to import these files or write manual head markup. Run a production build, open the rendered page, and verify the generated links and their responses.

That is the short answer. The right production choice depends on whether you need a full cross-platform pack, nested-route branding, code-generated artwork, or the older Pages Router.

This guide is for App Router projects on Next.js 13.3 and later, with a separate section for the Pages Router. If you are editing a framework-free document, use the HTML installation guide instead of translating Next.js metadata code back into manual tags.

Choose the setup before copying files

NeedBest optionWhy
One global icon with minimal codeApp Router file conventionsNext discovers and describes the files automatically.
A complete pack under /public/faviconsMetadata APIYou control sizes, types, manifest, and paths explicitly.
An icon rendered from code or tenant dataapp/icon.tsx or apple-icon.tsxImageResponse can generate image bytes at build or request time.
Different icons for route segmentsNested icon.* filesMetadata follows the App Router segment hierarchy.
A project using pages/, not app/next/head in the Pages RouterThe App Router metadata conventions do not apply.

Do not implement all four “just in case.” Duplicate declarations make cache debugging and browser selection harder. Pick one primary system, then add only the fallbacks your platform coverage requires.

Method 1: use App Router file conventions

The official icon file-convention reference recognizes these static files:

  • app/favicon.ico
  • app/icon.ico, app/icon.jpg, app/icon.jpeg, app/icon.png, or app/icon.svg
  • app/apple-icon.jpg, app/apple-icon.jpeg, or app/apple-icon.png

There is one important asymmetry: favicon.ico is only supported at the top level of app/. icon and apple-icon files can live farther down the route tree.

src/
└── app/
    ├── apple-icon.png   # 180x180 is a common Apple touch size
    ├── favicon.ico      # multi-size legacy/browser fallback
    ├── icon.png         # square browser and search icon
    ├── layout.tsx
    └── page.tsx

If your project does not use src/, put the same files under the root app/ folder. Do not also copy them into public/ unless another declaration deliberately references those copies.

Next.js reads image metadata and emits tags similar to these:

<link rel="shortcut icon" href="/favicon.ico?..." />
<link rel="icon" href="/icon.png?..." type="image/png" sizes="64x64" />
<link rel="apple-touch-icon" href="/apple-icon.png?..." type="image/png" sizes="180x180" />

The exact generated URL and attributes are framework output; inspect your own build rather than hard-coding the example. The static file convention is especially useful because image dimensions become metadata automatically.

Multiple static browser icons

Numbered filenames let you provide more than one candidate:

app/
├── icon1.png
├── icon2.svg
└── icon3.ico

Use this only when the formats serve a real purpose. A multi-resolution ICO plus one modern PNG or SVG is usually easier to reason about than a pile of near-identical PNGs.

Per-section branding

An icon in a nested segment applies within that metadata segment:

app/
├── favicon.ico
├── icon.png
├── layout.tsx
├── page.tsx
└── dashboard/
    ├── icon.png
    ├── layout.tsx
    └── page.tsx

This is useful for genuinely separate products or tenant areas. It does not give Google Search a different icon for every folder on one hostname: Google's Search documentation supports one favicon per hostname. Use route-level icons for the browser experience, not as a promise of path-level Search branding.

A short visual walkthrough of the App Router file placement. The framework evolves, so use the current documentation and your generated HTML as the source of truth.

Watch on YouTube

Method 2: declare a full pack with the Metadata API

Use this method when a generator gives you named files, a web manifest, and several intentional sizes. Put browser-fetchable assets under public/:

public/
└── favicons/
    ├── apple-touch-icon.png
    ├── favicon-16x16.png
    ├── favicon-32x32.png
    ├── favicon-48x48.png
    ├── favicon-64x64.png
    ├── favicon-96x96.png
    ├── favicon-128x128.png
    ├── favicon-256x256.png
    ├── favicon-512x512.png
    ├── favicon.ico
    └── site.webmanifest

Files in public/favicons/ are requested from /favicons/, without the word public. This follows Next.js's public folder convention.

Declare the pack in the root layout:

import type { Metadata, Viewport } from 'next';

export const metadata: Metadata = {
  manifest: '/favicons/site.webmanifest',
  icons: {
    icon: [
      { url: '/favicons/favicon.ico', sizes: 'any' },
      { url: '/favicons/favicon-16x16.png', type: 'image/png', sizes: '16x16' },
      { url: '/favicons/favicon-32x32.png', type: 'image/png', sizes: '32x32' },
      { url: '/favicons/favicon-48x48.png', type: 'image/png', sizes: '48x48' },
      { url: '/favicons/favicon-64x64.png', type: 'image/png', sizes: '64x64' },
      { url: '/favicons/favicon-96x96.png', type: 'image/png', sizes: '96x96' },
      { url: '/favicons/favicon-128x128.png', type: 'image/png', sizes: '128x128' },
      { url: '/favicons/favicon-256x256.png', type: 'image/png', sizes: '256x256' },
      { url: '/favicons/favicon-512x512.png', type: 'image/png', sizes: '512x512' },
    ],
    apple: [{ url: '/favicons/apple-touch-icon.png', type: 'image/png', sizes: '180x180' }],
  },
};

export const viewport: Viewport = {
  themeColor: '#ffffff',
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Use the viewport export for themeColor; it is no longer a general metadata field. Keep the hex value aligned with your app chrome and manifest.

A practical web manifest

{
  "name": "Example App",
  "short_name": "Example",
  "icons": [
    {
      "src": "/favicons/favicon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/favicons/favicon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    }
  ],
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "display": "standalone"
}

A manifest serves installed web-app surfaces; it does not replace rel="icon". The MDN manifest icon reference documents sizes, type, and purpose.

If your generated pack uses 192px instead of the 128px or 256px examples above, declare the files you actually have. Never describe a nonexistent size in metadata.

Method 3: generate an icon with code

Create app/icon.tsx when the icon needs text, colors, or artwork produced by code:

import { ImageResponse } from 'next/og';

export const size = { width: 64, height: 64 };
export const contentType = 'image/png';

export default function Icon() {
  return new ImageResponse(
    <div
      style={{
        alignItems: 'center',
        background: '#0f766e',
        color: '#ffffff',
        display: 'flex',
        fontSize: 38,
        fontWeight: 800,
        height: '100%',
        justifyContent: 'center',
        width: '100%',
      }}
    >
      E
    </div>,
    size
  );
}

Next.js generates the image and its metadata route. According to the file-convention docs, generated icons are statically optimized by default unless the code uses dynamic APIs or uncached data.

Constraints worth knowing:

  • Generate icon or apple-icon; favicon cannot be generated this way.
  • The output must fit the underlying ImageResponse size limit documented by Next.js.
  • Remote fonts or images add failure modes; bundle or fetch them deliberately.
  • Dynamic tenant data can turn an otherwise static icon into request-time work.
  • Always inspect the route response after next build, not just in development.

For several programmatic variants, use generateImageMetadata to return IDs and sizes, then render the requested variant in the default icon function.

Pages Router setup

If the application uses pages/ and has no app/ directory, put the pack in public/favicons/ and declare it through next/head in pages/_app.tsx:

import type { AppProps } from 'next/app';
import Head from 'next/head';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <link rel="icon" href="/favicons/favicon.ico" sizes="any" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png" />
        <link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
        <link rel="manifest" href="/favicons/site.webmanifest" />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

Do not carry this next/head pattern into an App Router layout. Use metadata there. Mixing the APIs is a common source of duplicate or unexpectedly ordered tags.

What our production-style test found

We used the Metadata API configuration above in a real Next.js 16.0.10 app, built the page, and requested each emitted URL on July 19, 2026.

Output groupEmitted linksResult
Manifest1HTTP 200, manifest content type
ICO browser fallback1HTTP 200, icon content type
PNG browser icons8HTTP 200, image/png
Apple touch icon1HTTP 200, image/png
Total11All targets reachable

The useful lesson was not the count. It was that a correct TypeScript object does not prove the assets were copied into the deployment. Checking every emitted href caught that whole class of failure.

Verify the build, not only next dev

Run the production path locally:

npm run build
npm run start

Then verify in this order:

  1. Open the page and inspect <head> in DevTools.
  2. Count icon and manifest links; remove unintended duplicates.
  3. Open every href directly.
  4. Confirm HTTP 200 and the expected Content-Type.
  5. Inspect intrinsic width and height for PNGs.
  6. Reload with DevTools cache disabled.
  7. Run the public URL through the Favicon Checker.

Useful terminal checks:

curl -Ls http://localhost:3000/ | grep -oE '<link[^>]+(icon|manifest)[^>]*>'
curl -sS -D - -o /dev/null http://localhost:3000/favicons/favicon-64x64.png
curl -sS http://localhost:3000/favicons/site.webmanifest

Common failures and exact fixes

The file is in public/, but the URL is 404

Remove /public from the URL. public/favicons/icon.png is served as /favicons/icon.png.

Also check filename case. macOS can hide a mismatch such as Favicon.png versus favicon.png; a Linux deployment commonly will not.

app/favicon.png does nothing

favicon is reserved specifically as favicon.ico. Rename a PNG to icon.png, or use a real favicon.ico.

The icon works locally but not after export or deploy

Inspect the deployment artifact and the public URL. A Docker copy stage, monorepo root, ignore rule, or static-host publish directory can omit public/ even when development works.

The browser shows the old image

First open the icon URL directly and verify its pixels. If the server is current, test a private browser profile and disable cache in DevTools. During a brand migration, a new filename can prove selection, but keep the final search-facing URL stable once the migration is complete.

The favicon cache-buster tool helps distinguish cached responses from incorrect markup.

Two or more icon tags point to different logos

Search for every source of metadata:

find app pages -type f \( -name 'layout.*' -o -name '_app.*' -o -name '_document.*' -o -name 'icon.*' -o -name 'favicon.*' \)

Look for a static convention file, an icons metadata field, next/head, and third-party layout code. Remove the system you no longer use.

It can affect that browser route, but Google supports one favicon per hostname. Use a subdomain if a separately indexed product needs distinct Search branding.

A base path changes the asset location

File-convention metadata is framework-managed. Explicit /favicons/... URLs in your Metadata API point to the host root. If the site is mounted under a base path, verify the actual deployed URL and include that base path in explicit public-file URLs where required. Never assume the development root matches the production mount.

Release checklist

  • Choose file conventions, Metadata API, or generated icons as the primary system.
  • Use a real square source image with legible small-size artwork.
  • Keep favicon.ico at the top-level app/ if using that convention.
  • Reference public/ assets without /public in the URL.
  • Declare only sizes that exist.
  • Keep manifest paths absolute from the deployed site root or mount point.
  • Use viewport for themeColor.
  • Run next build and the production server.
  • Inspect emitted tags and request every target.
  • Check mobile saved-icon and installed-app surfaces when they matter.
  • Audit the public domain after the deploy.

Frequently asked questions

Is app/favicon.ico enough?

It is enough for a basic browser icon. Add icon.png or SVG for a modern source, apple-icon.png for iOS saved shortcuts, and a manifest with suitable images when the site is installable.

Should the files go in app/ or public/?

Use app/ for reserved file conventions. Use public/ when you want stable, explicit URLs controlled by the Metadata API or Pages Router markup. Both work; accidental duplication is the problem.

What size should app/icon.png be?

Use a square source comfortably above small UI sizes, such as 64x64 or larger, and preview it at 16x16. For a complete multi-platform pack, include purpose-built sizes rather than relying on every consumer to resample one file.

Can I use an animated favicon?

Some browsers can display animated formats, but behavior and accessibility vary, and Search needs representative, stable branding. A static icon is the dependable default.

Why does Next.js append a query string to a convention icon?

The framework manages generated metadata URLs and cache invalidation. Do not copy a build-specific URL into source code. Declare the convention file and let Next.js emit the current link.

Does the App Router need a manual <head> element?

No. Use static metadata files, the metadata export, or generateMetadata. The framework composes the final head.

Primary references

For files and tags outside the framework abstraction, continue with how to add the pack to HTML. It makes the path-resolution and response-header checks explicit.

Put this guide into practice

Build a complete favicon pack, then test the live result before you ship.

Written by

FaviconMaker Editorial Team

Practical favicon guidance for designers, developers, and site owners, with a focus on files you can verify in production.

Follow new guides via RSS

Worth sharing?

Send this guide to someone fixing their site icon.

Keep learning

wix3 min

How to Change Favicon on Wix

Step-by-step: change your Wix favicon from the dashboard Settings, understand Premium + domain requirements, choose the right file format/size, and troubleshoot caching and Google display.

Read guide
shopify4 min

How to Change Favicon on Shopify

Step-by-step: change your Shopify store favicon via Theme settings, pick the right 16×16/32×32 image, understand plan limitations, and fix common caching and Google display issues.

Read guide
wordpress4 min

How to Change Favicon in WordPress

Step-by-step: set (or change) your WordPress Site Icon / favicon in WordPress 6.5+ and older versions, choose the right image size, and troubleshoot caching and Google search display.

Read guide