favicon, icon, apple-icon
Set browser and home-screen icons from files in app, or generate them with ImageResponse.
export default function Icon(props: { params?: Promise<Record<string, string | string[]>>; id?: Promise<string> }): Blob | ArrayBuffer | TypedArray | DataView | ReadableStream | Response- Added in Next.js 13.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$paramsoptional | Promise<Record<string, string | string[]>> | Promise resolving to the dynamic route parameters from the root segment down to the segment this file sits in. Undefined when no segment above it is dynamic. |
$idoptional | Promise<string> | Only passed when the file also exports generateImageMetadata. Resolves to the id of the icon currently being generated. |
$sizeoptional | { width: number; height: number } | Named export. Written into the link tag's sizes attribute. Spread it into the ImageResponse options so the bitmap matches. |
$contentTypeoptional | string | Named export. Image MIME type written into the link tag's type attribute, for example 'image/png'. |
Return value
Blob | ArrayBuffer | TypedArray | DataView | ReadableStream | ResponseThe icon's bytes in any of the supported forms. An ImageResponse from next/og satisfies this type.
Errors
There is no code variant of favicon — only icon and apple-icon can be generated from .js/.ts/.tsx; a favicon must be a literal .ico file at the app root.
Changelog
- PHP 16.0.0params is now a promise that resolves to an object, so it must be awaited.
- PHP 13.3.0favicon, icon and apple-icon conventions introduced.
Three conventions, one job. favicon.ico lives only at the top level of app/ and produces <link rel="icon" href="/favicon.ico" sizes="any">. icon accepts .ico, .jpg, .jpeg, .png or .svg in any segment. apple-icon accepts .jpg, .jpeg or .png — no SVG, because iOS will not render one — and becomes rel="apple-touch-icon".
When to generate instead
- The mark is a letter or initial that should follow a theme colour.
- A white-label deployment needs a per-tenant badge.
- You would rather keep one
.tsxthan five exported PNGs.
What trips people up
You cannot write favicon.tsx; only icon and apple-icon have code variants, so the classic /favicon.ico request still needs a real .ico.
Multiple sizes come from numbered files — icon1.png, icon2.png — and those sort lexically, so icon10.png lands before icon2.png. Next.js reads real dimensions off the file, but an .svg or an undetectable size gets sizes="any". And a leftover public/favicon.ico competes for the same URL as the one in app/.
Example
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: 'black',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
}}
>
A
</div>
),
{
...size,
}
)
}When to use it
- Generate a letter badge on the brand colour instead of exporting a PNG
- Ship favicon.ico, icon.svg and apple-icon.png as the three files most browsers need
- Give a section of a dashboard its own tab icon via a nested icon.png
- Render a per-tenant icon from the awaited route params on a white-label app
Frequently asked questions
Where does favicon.ico go in the Next.js app router?
Directly in app/ — the top level only, not in a nested segment and not in public/. Nested segments can override the tab image with their own icon file instead, since favicon is restricted to the root.
Can I generate a favicon.ico with code in Next.js?
No. Only icon and apple-icon have .js/.ts/.tsx variants; favicon must be a literal .ico file. Generate an icon.tsx for modern browsers and keep a small static favicon.ico for the legacy /favicon.ico request.
Why is my Next.js icon not updating in the browser tab?
Usually browser caching of the previous icon — hard-reload or check in a private window first. Also confirm no stale public/favicon.ico is competing with the file in app/, since both claim the same URL.
More examples
The three static files most sites need
Next.js reads type and sizes from each file: the PNG reports its real 180x180 dimensions, while the SVG gets sizes="any" because a vector has no intrinsic pixel size. favicon.ico is the only one served at a stable, hashless path — the others get a generated query string for cache busting.
// File layout, no code required:
//
// app
// ├── favicon.ico (root only, .ico only)
// ├── icon.svg (scales to any size)
// ├── apple-icon.png (180 x 180, no SVG allowed)
// ├── layout.tsx
// └── page.tsx
//
// Emitted into <head>:
//
// <link rel="icon" href="/favicon.ico" sizes="any" />
// <link rel="icon" href="/icon?<generated>" type="image/svg+xml" sizes="any" />
// <link rel="apple-touch-icon" href="/apple-icon?<generated>"
// type="image/png" sizes="180x180" />
export {}Several raster sizes with numbered files
The suffixes only control ordering, not the declared size — that still comes from each file's real dimensions. Because the sort is lexical rather than numeric, a set that reaches icon10.png would emit icon10 before icon2, so pad the numbers or keep the count under ten.
// File layout, no code required:
//
// app
// ├── favicon.ico
// ├── icon1.png (32 x 32)
// ├── icon2.png (192 x 192)
// ├── icon3.png (512 x 512)
// └── layout.tsx
//
// Emitted into <head>, in lexical filename order:
//
// <link rel="icon" href="/icon1?<generated>" type="image/png" sizes="32x32" />
// <link rel="icon" href="/icon2?<generated>" type="image/png" sizes="192x192" />
// <link rel="icon" href="/icon3?<generated>" type="image/png" sizes="512x512" />
export {}Apple touch icon generated at 180x180
Rendering at 180x180 matches what iOS asks for, and the emitted link tag carries sizes="180x180" straight from the size export. There is no transparency here on purpose: iOS composites the icon onto a home screen tile, and a transparent background renders as black.
// app/apple-icon.tsx
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={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#2563eb',
color: 'white',
fontSize: 96,
fontWeight: 700,
}}
>
A
</div>
),
{ ...size }
)
}
Discussion