Skip to main content

Favicon in Next.js App Router (2026 Guide)

8 min read

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

MethodBest for
File convention (app/icon.png)Simple static icons, zero config
app/favicon.icoLegacy browser fallback at /favicon.ico
metadata.icons in layout.tsxMultiple 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 appropriate link rel="icon" tags
  • apple-icon.png - becomes apple-touch-icon
  • favicon.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 - .ico only
  • icon - .ico, .jpg, .jpeg, .png, .svg
  • apple-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.tsx
import 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:

  1. Move icons from public/ or _document.tsx links to app/ conventions
  2. Remove duplicate Head tags that conflict with metadata
  3. 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.ico exists
  • 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.tsx
import 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:

  1. Audit pages/_document.tsx for manual favicon link tags
  2. Audit public/favicon.ico and decide if it moves to app/favicon.ico
  3. Remove <Head> favicon entries from individual pages
  4. Run production scan after first App Router deploy
  5. 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.