Lesson 79 of 81
For a favicon that never changes, writing a generateMetadata function is solving a static problem with a dynamic tool. Next.js's file-convention metadata APIs exist specifically for the case where the answer to 'what should this be' never depends on anything.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Next.js resolves most metadata from exported objects or functions on layout.tsx/page.tsx, covered elsewhere in this track. But a whole separate category of metadata — icons, the web app manifest, a default social share image — has its own, simpler mechanism: drop a literally-named file in the right folder, write zero code, and Next.js generates the correct <head> tags itself.
favicon.ico — the root app/ folder only, .ico format.icon.(ico|jpg|jpeg|png|svg) — any route segment, for the general-purpose icon.apple-icon.(jpg|jpeg|png) — any route segment, for the iOS home-screen icon.opengraph-image.(jpg|jpeg|png|gif) and twitter-image.(jpg|jpeg|png|gif) — a static social share image, as opposed to the code-generated version covered in this track's dedicated lesson on generating OG images dynamically.manifest.json (or a generated manifest.ts) — the web app manifest for PWA metadata: name, theme color, icon set.Every one of these has a code-based sibling that does the same job dynamically — icon.tsx generating an icon with ImageResponse, opengraph-image.tsx generating a share image per request, generateMetadata computing a title from fetched data. The file-convention route and the code route solve overlapping jobs on completely different ends of a spectrum: static-file wins when the output never changes and there's nothing to compute; code wins the moment the output depends on params, on fetched data, or on anything else resolved per request. The mistake to avoid isn't picking the wrong one — it's defaulting to code everywhere out of habit, for values like a brand's favicon that were never going to vary in the first place.
app/
├── favicon.ico → <link rel="icon" href="/favicon.ico" sizes="any" />
├── icon.png → <link rel="icon" href="/icon?<generated>" ... />
├── apple-icon.png → <link rel="apple-touch-icon" href="/apple-icon?<generated>" ... />
├── opengraph-image.png → default og:image for every route that doesn't override it
├── manifest.json → PWA manifest, linked automatically
└── learn/[track]/[lesson]/
└── opengraph-image.tsx → per-lesson, DATA-DRIVEN share image — needs code, not a file
Nothing in the top five entries has any per-request decision to make — a brand's favicon is the same PNG for every single visitor, forever, until someone redesigns the logo. Writing a function that returns that same static result on every call would be correct but pointless ceremony around a value that was never going to change. The lesson-level opengraph-image.tsx, in contrast, genuinely needs code: the image has to contain that specific lesson's title, which isn't knowable until a request names which lesson.
<head> tags for you completely — there's no matching object to also set by hand. Adding icon.png is the entire integration; there's no metadata.icons field you're also supposed to populate pointing at the same file.icon.tsx, opengraph-image.tsx) and the static-file variant share a name but can't coexist for the same convention in the same segment. A folder with both icon.png and icon.tsx is a real conflict, not redundant fallback behavior.favicon specifically only works at the root app/ level — every other convention here works at any route segment. A per-section favicon.ico nested deeper in the tree is silently ignored; use icon instead if a sub-section genuinely needs its own distinct icon.icon.png is served as a plain asset; a generated icon.tsx still runs through the same rendering pipeline other route segments do — reach for static specifically when the value has no reason to ever change, not because it's inherently faster.opengraph-image.png at a parent segment is inherited by children that don't define their own — the same "define nothing, inherit the parent" rule that applies to the code-based generateMetadata fields applies here too."Why would you ever write an icon.tsx function instead of just dropping in a PNG?" is a good litmus test — the strong answer isn't about one approach being more modern, it's naming the one real reason to reach for code: the icon's content has to be computed per request (per-tenant branding, a dynamically generated badge), and for everything else — the overwhelming majority of a typical app's icons and share images — the static file is strictly simpler and does the identical job with no function to maintain.