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 iconSupported 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 buildCommon 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.icoinapp/ - Multi-Resolution: Use
iconsmetadata with array configuration - Organization: Store all icons in
public/faviconfolder - File Names: Never modify filenames generated by icon converters
- Cache Management: Regularly clear
.nextduring development - Device Support: Always include both SVG and ICO versions
For complex implementations, refer to the official Next.js Metadata Documentation.