Next.js App Router handles favicons through file conventions in the app/ directory and the Metadata API. Drop favicon.ico, icon.png, or apple-icon.png into app/, or define icons in layout.tsx via export const metadata. Next.js generates the correct link tags at build time.
This guide covers every supported approach, common App Router pitfalls, and how to verify icons on your deployed Vercel or self-hosted URL before launch.
Quick answer: the three ways to add favicons
| Method | Best for |
|---|---|
File convention (app/icon.png) | Simple static icons, zero config |
app/favicon.ico | Legacy browser fallback at /favicon.ico |
metadata.icons in layout.tsx | Multiple sizes, SVG, manifest links |
You can combine file conventions with explicit metadata. Avoid duplicating the same icon twice in the HTML head.
Method 1: File conventions (recommended start)
Next.js 13+ recognizes special files in app/:
app/
favicon.ico
icon.png # or icon.jpg, icon.svg
apple-icon.png # Apple Touch Icon
Place your source image:
icon.png- Next.js generates appropriatelink rel="icon"tagsapple-icon.png- becomesapple-touch-iconfavicon.ico- served at/favicon.ico
Next.js optimizes and emits tags automatically during next build. No manual HTML required.
Supported icon file names
From the Next.js docs, these file names work:
favicon.ico-.icoonlyicon-.ico,.jpg,.jpeg,.png,.svgapple-icon-.jpg,.jpeg,.png
You can also use numbered variants like icon1.png for multiple icons, though explicit metadata is clearer for complex setups.
Method 2: Metadata API
For full control, define icons in your root layout:
// app/layout.tsximport type { Metadata } from 'next'export const metadata: Metadata = { icons: { icon: [ { 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' }, ], other: [ { rel: 'mask-icon', url: '/safari-pinned-tab.svg', color: '#5bbad5', }, ], },}Put static files in public/:
public/
favicon-32x32.png
favicon-16x16.png
apple-touch-icon.png
site.webmanifest
Link the manifest separately:
export const metadata: Metadata = { manifest: '/site.webmanifest', icons: { /* ... */ },}This pattern matches what you would hand-write in plain HTML. See How to Add a Favicon (HTML) for the underlying tag reference.
Method 3: Dynamic icons with code
Next.js supports generating icons programmatically:
app/
icon.tsx # or icon.js
apple-icon.tsx
Example 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', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', }} > A </div> ), { ...size } )}Dynamic icons help when favicons must match user-specific or environment-specific branding. For most marketing sites, static PNG files are simpler and easier to test.
PWA manifest icons in Next.js
Tab favicons and PWA install icons are separate. Add a manifest in public/site.webmanifest:
{ "name": "My App", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ]}Reference it in metadata:
export const metadata: Metadata = { manifest: '/site.webmanifest',}Required sizes: see Favicon Sizes Guide.
Pages Router vs App Router
Still on Pages Router? Use pages/_document.tsx or the older public/favicon.ico approach. App Router file conventions do not apply to pages/.
If you migrate:
- Move icons from
public/or_document.tsxlinks toapp/conventions - Remove duplicate
Headtags that conflict with metadata - Re-test production after migration
Common Next.js favicon mistakes
Icons only in public/ with no metadata
public/favicon.ico serves at /favicon.ico, but Next.js may not emit PNG link tags unless you use file conventions or metadata. Browsers fall back to ICO only. That misses 16×16 and 32×16 PNG declarations.
Duplicate tags from layout and page metadata
Child layouts can merge metadata. If both root and nested layouts define icons, you may get duplicates. Keep icon config in the root app/layout.tsx only.
Wrong image dimensions
Export apple-icon.png at 180×180. Export tab icons at 32×32 and 16×16. Upscaling a small logo produces blur. See Favicon Looks Blurry.
Forgetting build output on CI
Icons in app/ are processed at build time. If CI skips next build or caches stale .next output, deploys ship old icons. Bust cache or bump file names after rebrand.
SVG without PNG fallback
Safari and some contexts ignore SVG favicons. Ship PNG fallbacks alongside icon.svg.
Testing Next.js favicons before deploy
Localhost shows icons, but production paths differ behind CDNs and asset prefixes.
Step 1: Check build output
Run next build locally. Inspect .next/server/app/ or view page source on next start to confirm tags render in HTML without client JS.
Step 2: Scan staging or production URL
Deploy to preview. Open Favicon Check, enter the preview URL, and confirm:
- All declared icons return 200
/favicon.icoexists- Manifest icons resolve if you use a PWA
Step 3: Verify basePath and assetPrefix
If next.config.js sets basePath or assetPrefix, icon URLs must include the prefix. A scan catches 404s immediately.
Step 4: Cross-check Open Graph
Next.js metadata.openGraph is unrelated to favicons. Before launch, also run Open Graph Check on the same URL. If you set OG tags in the same layout.tsx, one deploy affects both.
Full example: production-ready setup
app/
layout.tsx
favicon.ico
icon.png
apple-icon.png
public/
favicon-16x16.png
favicon-32x32.png
android-chrome-192x192.png
android-chrome-512x512.png
site.webmanifest
// app/layout.tsximport type { Metadata } from 'next'export const metadata: Metadata = { title: 'My Site', manifest: '/site.webmanifest', icons: { icon: [ { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' }, ], },}export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> )}File conventions handle favicon.ico, icon.png, and apple-icon.png. Metadata adds explicit size declarations and manifest linkage.
After deploy, scan with Favicon Check and walk through How to Test Favicons Before Launch.
Deploying on Vercel, Netlify, and Docker
Hosting choice rarely changes favicon markup, but it affects cache and path behavior.
Vercel: App Router conventions work out of the box. Preview deployments get unique URLs. Scan each preview before merging to production. Production domains need a post-merge favicon scan because edge cache may hold old static files for minutes.
Netlify / static export: If you use output: 'export', confirm icons land in the exported out/ folder. Dynamic icon.tsx routes may not work the same as server builds. Prefer static PNG files in app/ or public/ for static export sites.
Docker / self-hosted: Ensure your reverse proxy does not strip link tags from HTML. Some security middleware removes unknown head elements. Compare container response with local next start.
Monorepo: If the Next app lives under /apps/web, favicon files still belong in that app's app/ directory, not the monorepo root.
Migrating favicons from Pages Router
Teams upgrading from Pages Router often leave duplicate icon paths behind.
Migration checklist:
- Audit
pages/_document.tsxfor manual faviconlinktags - Audit
public/favicon.icoand decide if it moves toapp/favicon.ico - Remove
<Head>favicon entries from individual pages - Run production scan after first App Router deploy
- Update CDN purge rules if icon URLs changed
During migration, both routers may serve different head output on different routes. Test marketing pages and app dashboard routes separately if they use different layouts.
FAQ
Where do I put favicon.ico in Next.js App Router?
Place favicon.ico directly in the app/ directory. Next.js serves it at /favicon.ico after build. You can also use public/favicon.ico, but the app/ convention integrates with the metadata pipeline.
Does Next.js generate favicon link tags automatically?
Yes, when you use file conventions (app/icon.png, app/favicon.ico) or define metadata.icons. Tags appear in the server-rendered HTML head.
Can I use SVG favicons in Next.js?
Yes. Add app/icon.svg or reference an SVG in metadata.icons. Always provide PNG fallbacks for Safari and older clients.
Why does my Next.js favicon work in dev but not production?
Check assetPrefix, CDN caching, and whether preview vs production domains differ. Scan the deployed URL with Favicon Check to list 404s.
How do I add multiple favicon sizes in Next.js?
Use metadata.icons.icon with an array of { url, sizes, type } objects pointing to files in public/, or export numbered icon1.png, icon2.png in app/.
App Router vs metadata in next/head?
Do not use next/head in App Router. Use the Metadata API or file conventions. next/head is for Pages Router.
Does Vercel cache favicons?
Vercel respects standard cache headers. After icon changes, redeploy and test in incognito. Rename files if users report stale icons.
Should Next.js favicons match Open Graph images?
No. Favicons are small square tab icons. OG images are large landscape share previews. Different files, different tags. See Favicon vs Open Graph Image.
Conclusion
Next.js App Router favicons come from app/favicon.ico, app/icon.png, app/apple-icon.png, or metadata.icons in your root layout. Combine file conventions with explicit PNG sizes and a web manifest for PWA support.
Build locally, deploy to preview, and scan with Favicon Check before go-live. Pair with Open Graph Check when you ship social metadata in the same release.