Next.js favicon, icon, and apple-icon
App icons look like the simplest possible piece of metadata to get right — drop an image somewhere, done — until you actually try to cover every surface that wants one: the browser tab, the bookmark bar, an iOS home screen icon with its own distinct visual conventions, search results. Next.js handles all of it through three closely related file conventions — favicon, icon, and apple-icon — each targeting a specific surface, each supporting both a plain static image and a fully code-generated variant.
Static Image Files
Placing a correctly-named image file directly in app is the simplest path — no code, no component, just a file:
| Convention | Supported file types | Valid locations |
|---|---|---|
favicon | .ico | app/ (top level only) |
icon | .ico, .jpg, .jpeg, .png, .svg | app/**/* |
apple-icon | .jpg, .jpeg, .png | app/**/* |
The location column matters more than it might first appear. favicon can only live at the top level of app — there's no per-route favicon override. icon and apple-icon, by contrast, can be placed anywhere in the route tree (app/**/*), meaning a specific section of your app can define its own distinct icon that overrides whatever the root defines, scoped to just that section and its children.
Next.js evaluates whichever file you've provided and generates the appropriate <head> tag automatically:
<link rel="icon" href="/favicon.ico" sizes="any" />
<link
rel="icon"
href="/icon?<generated>"
type="image/<generated>"
sizes="<generated>"
/>
<link
rel="apple-touch-icon"
href="/apple-icon?<generated>"
type="image/<generated>"
sizes="<generated>"
/>
Notice the type and sizes attributes are marked <generated> — Next.js determines these directly from the actual file's metadata rather than requiring you to specify them. A 32×32px PNG automatically produces type="image/png" and sizes="32x32"; you never manually declare either value.
Multiple Icons via Numbered Suffixes
You're not limited to a single icon file — adding a numeric suffix (icon1.png, icon2.png) lets you define several, sorted lexically by filename. This is useful for providing multiple resolutions or formats that a browser can choose between, without hand-writing multiple <link> tags yourself.
The sizes="any" Detail
Icons get sizes="any" specifically when the file is an .svg (which is inherently resolution-independent) or when the image's dimensions genuinely can't be determined from the file itself. If you want to understand the reasoning behind exactly when and why sizes="any" matters for real-world favicon compatibility across browsers, the linked favicon handbook in the official docs goes considerably deeper than this reference needs to — it's a genuinely subtle corner of browser behavior that this file convention abstracts away for you.
Generating Icons with Code
Instead of a static file, icon and apple-icon both also support .js, .ts, or .tsx variants that default-export a function generating the icon programmatically — the standard approach being Next.js's own ImageResponse API from next/og:
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 },
);
}
This produces the identical head-tag shape as a static file would — <link rel="icon" href="/icon?<generated>" type="image/png" sizes="32x32" /> — but the actual pixels come from rendered JSX rather than a pre-existing image asset. This is genuinely useful for icons that need to reflect dynamic state (a per-tenant logo, a notification-badge overlay) rather than a single fixed design.
Static Optimization Applies Here Too
Generated icons are statically optimized by default — computed once at build time and cached — unless the generating function itself reaches for Request-time APIs or uncached data. If your icon-generation logic doesn't touch anything request-dependent, you get build-time generation and caching for free, with zero extra configuration required to opt into it.
One Thing You Genuinely Cannot Do
You cannot generate a favicon programmatically — the code-generation path is only available for icon and apple-icon. If you need a dynamically-generated icon at the equivalent of the favicon's role, use icon instead; favicon is restricted to the static .ico file form specifically.
Props: params
The default-exported function receives an (optional) params prop — a promise resolving to the dynamic route parameters from the root segment down to wherever the icon or apple-icon file is colocated:
export default async function Icon({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// ...
}
| Route | URL | params |
|---|---|---|
app/shop/icon.js | /shop | undefined |
app/shop/[slug]/icon.js | /shop/1 | Promise<{ slug: '1' }> |
app/shop/[tag]/[item]/icon.js | /shop/1/2 | Promise<{ tag: '1', item: '2' }> |
If you're using generateImageMetadata to produce multiple icons from one file, the function also receives an id prop — itself a promise resolving to the id value from whichever entry generateImageMetadata returned that this particular invocation corresponds to.
Config Exports: size and contentType
Beyond the default export itself, you can optionally set size and contentType as separate exports to control the generated metadata directly:
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {}
These feed directly into the generated <link> tag's sizes and type attributes, respectively — the same values you'd get implicitly from a static image file's own metadata, but explicit here since a code-generated icon has no underlying file for Next.js to inspect.
Route Segment Config
Because icon and apple-icon are, mechanically, specialized Route Handlers, they support the same route segment configuration options (dynamic, revalidate, and the rest) that ordinary pages and layouts do — worth knowing if you need to force a generated icon to always run dynamically rather than being statically optimized, for instance.
Version History
| Version | Changes |
|---|---|
v16.0.0 | params became a promise resolving to an object |
v13.3.0 | favicon, icon, and apple-icon introduced |
Key Takeaways
| Convention | Static formats | Location | Code-generation supported |
|---|---|---|---|
favicon | .ico only | Top level of app only | No |
icon | .ico, .jpg, .jpeg, .png, .svg | Anywhere in app/**/* | Yes |
apple-icon | .jpg, .jpeg, .png | Anywhere in app/**/* | Yes |
| Behavior | Detail |
|---|---|
| Multiple icons | Use numbered suffixes (icon1.png, icon2.png) — sorted lexically |
| Generated icons | Statically optimized by default unless request-time data is accessed |
size/contentType exports | Control generated metadata directly for code-based icons |
Three file conventions, one shared mental model: name the file correctly, put it where it needs the right scope, and let Next.js handle deriving the correct <head> output — whether the source is a plain static image or a function rendering JSX into pixels at build time.


