favicon, icon, apple-icon

Set browser and home-screen icons from files in app, or generate them with ImageResponse.

Signature
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

ParameterTypeDescription
$paramsoptionalPromise<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.
$idoptionalPromise<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.
$contentTypeoptionalstringNamed 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 .tsx than 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

Example · typescript
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.

Example · typescript
// 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.

Example · typescript
// 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.

Example · typescript
// 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

  • Be the first to comment on this lesson.