How to Add a Favicon in Next.js (App Router & Pages Router)

The complete guide to adding favicons in Next.js. Covers the App Router file-based approach, the metadata API, the Pages Router method, PWA manifest setup, and common troubleshooting — with code examples for every method.

Quick Answer

App Router (Next.js 13+): Place favicon.ico in your app/ directory. Done. Next.js handles the rest automatically.

Pages Router: Place favicon.ico in public/ and add a <link> tag in _document.tsx.

Contents

1. App Router — File-Based Favicons (Recommended)

Next.js 13+ with the App Router has built-in support for favicons through special file conventions. Just place files with specific names in your app/ directory and Next.js automatically generates the correct <link> tags.

File Convention

FileFormatGenerated Tag
app/favicon.icoICO<link rel="icon" href="/favicon.ico">
app/icon.pngPNG, JPG, SVG<link rel="icon" type="image/png" href="/icon.png">
app/icon.svgSVG<link rel="icon" type="image/svg+xml" href="/icon.svg">
app/apple-icon.pngPNG<link rel="apple-touch-icon" href="/apple-icon.png">

Minimal Setup

For the simplest setup, you only need one file:

File structure
app/
├── favicon.ico      ← Just drop this in
├── layout.tsx
└── page.tsx

That's it. Next.js automatically adds <link rel="icon" href="/favicon.ico"> to every page. No imports, no configuration, no Head component needed.

Full Setup (All Icons)

For complete platform coverage, include all these files:

File structure
app/
├── favicon.ico          ← Browser tab (legacy)
├── icon.png             ← Browser tab (modern, 32x32)
├── icon.svg             ← SVG favicon (scalable)
├── apple-icon.png       ← Apple Touch Icon (180x180)
├── layout.tsx
└── page.tsx

public/
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── site.webmanifest
└── browserconfig.xml

Files in app/ are handled automatically. Files in public/ need manual <link> tags (covered in the metadata API section below).

2. App Router — Metadata API

For full control over favicon tags, use the metadata export in your root layout. This is useful when you need to reference files in public/, add a web manifest link, or set Microsoft tile meta tags.

app/layout.tsx
import type { Metadata } from "next";

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

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

This generates the following in your <head>:

Generated HTML
<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
<link rel="manifest" href="/site.webmanifest">

Important: If you use both file-based icons (app/favicon.ico) and the metadata API (metadata.icons), the metadata API takes precedence and the file-based icons are ignored. Choose one approach.

3. App Router — Dynamic Generation with icon.tsx

Next.js can generate favicons at build time using a special icon.tsx file. This uses the ImageResponse API (same as Open Graph image generation) to render a React component as an image.

app/icon.tsx
import { ImageResponse } from "next/og";

export const size = { width: 32, height: 32 };
export const contentType = "image/png";

export default function Icon() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 24,
          background: "#000",
          color: "#fff",
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          borderRadius: 6,
        }}
      >
        A
      </div>
    ),
    { ...size }
  );
}

This is particularly useful for:

  • Text-based favicons (initials, single letters) without needing an image editor
  • Environment indicators (e.g., a red dot for staging, green for production)
  • Dynamic favicons that change based on build-time data

Generate Apple Touch Icon too

Create a separate apple-icon.tsx for the Apple Touch Icon:

app/apple-icon.tsx
import { ImageResponse } from "next/og";

export const size = { width: 180, height: 180 };
export const contentType = "image/png";

export default function AppleIcon() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 120,
          background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
          color: "#fff",
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          borderRadius: 32,
        }}
      >
        A
      </div>
    ),
    { ...size }
  );
}

4. Pages Router Method

If you're using the Pages Router (pages/ directory), favicons go in the public/ directory and you add <link> tags manually using next/head.

Step 1: Add files to public/

File structure
public/
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png      ← 180x180
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── site.webmanifest
└── browserconfig.xml

Step 2: Add link tags in _document.tsx

The best place for favicon tags is _document.tsx because it runs once on the server and applies to every page:

pages/_document.tsx
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <link rel="icon" href="/favicon.ico" sizes="48x48" />
        <link rel="icon" href="/favicon-32x32.png" sizes="32x32" type="image/png" />
        <link rel="icon" href="/favicon-16x16.png" sizes="16x16" type="image/png" />
        <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
        <link rel="manifest" href="/site.webmanifest" />
        <meta name="msapplication-TileColor" content="#ffffff" />
        <meta name="theme-color" content="#ffffff" />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Don't use next/head in _app.tsx for favicons. While it works, using _document.tsx is more efficient because favicons don't change between pages. The Head component in _document.tsx renders once, while next/head in _app.tsx runs on every client-side navigation.

5. Adding All Favicon Sizes

For complete platform coverage, your Next.js project needs these favicon files. Use our favicon generator to create all of them from a single image.

FileSizeApp Router LocationPages Router Location
favicon.ico48x48app/public/
icon.png32x32app/public/ (as favicon-32x32.png)
apple-icon.png180x180app/public/ (as apple-touch-icon.png)
android-chrome-192x192.png192x192public/public/
android-chrome-512x512.png512x512public/public/
site.webmanifestpublic/public/

6. PWA Manifest Setup

For Progressive Web App support, you need a site.webmanifest file in your public/ directory (both App Router and Pages Router):

public/site.webmanifest
{
  "name": "Your App Name",
  "short_name": "App",
  "description": "Your app description",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable any"
    },
    {
      "src": "/android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable any"
    }
  ]
}

Then reference it in your layout:

App Router — app/layout.tsx
export const metadata: Metadata = {
  manifest: "/site.webmanifest",
};
Pages Router — pages/_document.tsx
<link rel="manifest" href="/site.webmanifest" />

7. Troubleshooting

Favicon not showing in development

Next.js caches static files aggressively during development. Try these steps in order:

  1. Hard refresh: Ctrl+Shift+R (Win) or Cmd+Shift+R (Mac)
  2. Stop the dev server, delete .next/, and restart: rm -rf .next && npm run dev
  3. Open in an incognito/private window
  4. Clear all browser data for localhost

Favicon works locally but not in production

Check that your favicon files are included in your deployment. On Vercel, files in public/ and app/ are deployed automatically. If using Docker or a custom build pipeline, make sure these files are copied to the output directory. Also check that your favicon paths use absolute URLs (starting with /) rather than relative paths.

Old favicon still showing after deploying a new one

Favicons are cached at multiple levels — browser cache, CDN cache, and DNS cache. Solutions: append a version query string to the favicon URL (e.g., /favicon.ico?v=2), purge your CDN cache, or wait 24-48 hours. Google takes even longer — it can take weeks to update a favicon in search results.

Favicon shows on desktop but not on mobile

Mobile browsers use different icon files. Make sure you have an apple-icon.png (180x180) in app/ for iOS, and a site.webmanifest with 192x192 and 512x512 icons for Android. These are separate from your favicon.ico.

Multiple favicons appearing (duplicate tags)

This happens when you use both file conventions (app/favicon.ico) and the metadata API (metadata.icons) simultaneously. Pick one approach. If using the metadata API, remove favicon.ico from app/ (keep it in public/ instead).

"Cannot find module 'next/og'" error with icon.tsx

The next/og module requires Next.js 14.0 or later. If you're on an older version, upgrade Next.js or use a static favicon file instead. Run npm install next@latest to update.

8. Frequently Asked Questions

Where do I put the favicon in Next.js App Router?

Place favicon.ico in your app/ directory. Next.js automatically detects it and adds the appropriate <link> tag. For PNG icons, use app/icon.png and for Apple Touch Icons use app/apple-icon.png. No manual link tags needed.

Why is my Next.js favicon not updating?

Favicons are aggressively cached. Try: hard refresh (Ctrl+Shift+R), clear browser cache, open incognito, restart the dev server, or delete the .next directory and rebuild.

Can I use SVG favicons in Next.js?

Yes. In the App Router, place an icon.svg in your app/ directory. In the Pages Router, place the SVG in public/ and add a <link rel="icon" type="image/svg+xml"> tag manually. SVG favicons can also support dark mode via CSS media queries inside the SVG.

How do I generate a favicon for Next.js dynamically?

Create an icon.tsx file in your app/ directory that exports a default function returning an ImageResponse from next/og. This renders a React component as an image at build time. See the dynamic generation section above for a full example.

Should I use App Router or Pages Router file conventions?

If you're starting a new project or already on Next.js 13+, use the App Router file conventions — they're simpler and require no manual configuration. If you have an existing Pages Router project, use the public/ + _document.tsx approach. You can also gradually migrate by using the App Router for new routes while keeping the Pages Router for existing ones.

Need favicon files for your Next.js project?

Generate all the sizes you need — favicon.ico, icon.png, apple-icon.png, Android Chrome icons, and site.webmanifest — in one click.

Open Free Favicon Generator