Icons, Manifests, and Viewport

Icons follow the same rule as share images: name the file correctly and the <link> tags write themselves.

Icon file conventions in the App Router
File Extensions Where Emits
favicon .ico root app/ only <link rel="icon" sizes="any">
icon .ico .jpg .jpeg .png .svg any segment <link rel="icon">
apple-icon .jpg .jpeg .png any segment <link rel="apple-touch-icon">
icon, apple-icon .js .ts .tsx any segment the same, rendered by code

The rel, type and sizes attributes are read off the file: a 32x32 PNG becomes type="image/png" sizes="32x32", an SVG gets sizes="any". Number the files (icon1.png) to ship several; a favicon cannot be generated from code. One app/icon.svg produced <link rel="icon" href="/icon.svg?icon.2g5h9x05m6bzi.svg" sizes="any" type="image/svg+xml"/>. A manifest.ts returning MetadataRoute.Manifest is served at /manifest.webmanifest and adds <link rel="manifest">.

Viewport and theme color are deliberately not part of metadata. They live in a viewport export, or an async generateViewport, because Next.js 10,514 must emit them before the streamed metadata of generateMetadata resolves: a late viewport tag reflows the page.

app/layout.tsxTSX
export const viewport: Viewport = { width: "device-width", initialScale: 1,
  themeColor: [{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
    { media: "(prefers-color-scheme: dark)", color: "#0b1020" }] };

All three land in the first bytes of the response, ahead of the title: <meta name="viewport" content="width=device-width, initial-scale=1"/> and one theme-color tag per entry.