Favicons in Next.js App Router
Problem: Adding Icons with App Router Metadata
Since Next.js 13.4, the App Router introduced significant changes to how you manage site icons. The legacy head.js
file and custom <Head>
components are now replaced by the metadata API. Developers often face challenges when:
- Icon files don't appear in the final HTML output
- Cache issues prevent new icons from loading
- File location conflicts (
public/
vsapp/
) - Incorrect icon format usage (.png vs .ico)
- Missing support for multiple icon sizes
Recommended Solutions
1. Using File Conventions (Simplest Method)
Next.js automatically detects properly named files in your app/
directory:
app/
icon.svg # Main icon
favicon.ico # Legacy .ico favicon
apple-icon.png # iOS Home Screen icon
Supported file conventions:
File Type | Supported Formats | Valid Locations |
---|---|---|
favicon | .ico | app/ |
icon | .ico, .jpg, .jpeg, .png, .svg | app/**/* |
apple-icon | .jpg, .jpeg, .png | app/**/* |
Important
Delete the initial public/favicon.ico
file to avoid conflicts
2. Configuring icons Metadata
For more control, define icons in your layout's metadata:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
icons: {
icon: '/favicon/favicon.ico',
apple: '/favicon/apple-touch-icon.png',
shortcut: '/favicon/favicon.ico'
}
}
Place your icon files in the public/favicon/
directory.
3. Advanced Multi-Icon Configuration
Provide multiple icon sizes for different devices:
export const metadata: Metadata = {
icons: {
icon: [
{
url: '/favicon/favicon-32x32.png',
type: 'image/png',
sizes: '32x32'
},
{
url: '/favicon/favicon-16x16.png',
type: 'image/png',
sizes: '16x16'
}
],
apple: [
{
url: '/favicon/apple-touch-icon.png',
sizes: '180x180'
}
]
},
manifest: '/favicon/site.webmanifest'
}
Icon Preparation
Use tools like favicon.io to generate a complete icon set
Advanced Use Cases
Per-Path Custom Icons
Place icon.*
files in route segments to override global icons:
app/
dashboard/
icon.svg # Icon for /dashboard/*
Dynamically Generated Icons
Create programmatically generated icons using API routes:
import { ImageResponse } from 'next/og'
export const size = { width: 32, height: 32 }
export const contentType = 'image/png'
export default function Icon({ params }) {
return new ImageResponse(
<div style={{
background: 'black',
color: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
{params.slug[0].toUpperCase()}
</div>,
size
)
}
This generates icons with the first letter of the route slug.
Troubleshooting Guide
Cache Issues
rm -rf .next # Clear development cache
npm run build # Force fresh build
Common Errors
Conflict Error:
Error: A conflicting public file and page file was found for path /favicon.ico
Solution: Remove duplicate files from eitherapp/
orpublic/
Missing Icons:
Solution: Verify:- Correct location (
app/
orpublic/
) - Exact filename (
icon.svg
, notmy-icon.svg
) - Proper metadata type definitions
- Correct location (
iOS Icon Not Showing:
Solution: Ensure:- File placed in
app/
asapple-icon.png
- No
<meta name="apple-touch-icon">
in separate manual tags
- File placed in
Conflicting Approaches
Avoid adding manual <link>
tags directly in layout components as this bypasses Next.js optimization:
// app/layout.tsx - DON'T DO THIS
export default function Layout() {
return (
<html>
<head>
<link rel="icon" href="/path/to/icon" /> {/* Conflicts with metadata */}
</head>
...
</html>
)
}
Best Practices Summary
- Default Approach: Place
icon.svg
/favicon.ico
inapp/
- Multi-Resolution: Use
icons
metadata with array configuration - Organization: Store all icons in
public/favicon
folder - File Names: Never modify filenames generated by icon converters
- Cache Management: Regularly clear
.next
during development - Device Support: Always include both SVG and ICO versions
For complex implementations, refer to the official Next.js Metadata Documentation.