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
| File | Format | Generated Tag |
|---|---|---|
| app/favicon.ico | ICO | <link rel="icon" href="/favicon.ico"> |
| app/icon.png | PNG, JPG, SVG | <link rel="icon" type="image/png" href="/icon.png"> |
| app/icon.svg | SVG | <link rel="icon" type="image/svg+xml" href="/icon.svg"> |
| app/apple-icon.png | PNG | <link rel="apple-touch-icon" href="/apple-icon.png"> |
Minimal Setup
For the simplest setup, you only need one file:
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:
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.
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>:
<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.
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:
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/
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:
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.
| File | Size | App Router Location | Pages Router Location |
|---|---|---|---|
| favicon.ico | 48x48 | app/ | public/ |
| icon.png | 32x32 | app/ | public/ (as favicon-32x32.png) |
| apple-icon.png | 180x180 | app/ | public/ (as apple-touch-icon.png) |
| android-chrome-192x192.png | 192x192 | public/ | public/ |
| android-chrome-512x512.png | 512x512 | public/ | public/ |
| site.webmanifest | — | public/ | 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):
{
"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:
export const metadata: Metadata = {
manifest: "/site.webmanifest",
};<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:
- Hard refresh: Ctrl+Shift+R (Win) or Cmd+Shift+R (Mac)
- Stop the dev server, delete
.next/, and restart:rm -rf .next && npm run dev - Open in an incognito/private window
- 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