Type something to search...
Next.js favicon, icon, and apple-icon

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:

ConventionSupported file typesValid locations
favicon.icoapp/ (top level only)
icon.ico, .jpg, .jpeg, .png, .svgapp/**/*
apple-icon.jpg, .jpeg, .pngapp/**/*

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;
  // ...
}
RouteURLparams
app/shop/icon.js/shopundefined
app/shop/[slug]/icon.js/shop/1Promise<{ slug: '1' }>
app/shop/[tag]/[item]/icon.js/shop/1/2Promise<{ 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

VersionChanges
v16.0.0params became a promise resolving to an object
v13.3.0favicon, icon, and apple-icon introduced

Key Takeaways

ConventionStatic formatsLocationCode-generation supported
favicon.ico onlyTop level of app onlyNo
icon.ico, .jpg, .jpeg, .png, .svgAnywhere in app/**/*Yes
apple-icon.jpg, .jpeg, .pngAnywhere in app/**/*Yes
BehaviorDetail
Multiple iconsUse numbered suffixes (icon1.png, icon2.png) — sorted lexically
Generated iconsStatically optimized by default unless request-time data is accessed
size/contentType exportsControl 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.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper