Next.js Favicon: The Modern App Router Guide

So you’ve spun up a new Next.js project, dropped a favicon.ico in the public folder, and... nothing. Or worse, it works locally but breaks in production. What gives?

If this sounds familiar, you've likely stumbled upon the difference between the old pages router and the modern App Router. The good news is the new way is much simpler and more powerful once you know the convention.

The Official Way: File-Based Metadata

Forget manually adding <link> tags to your layout.tsx. The Next.js App Router uses a special file-based system. You place specifically named icon files in your app directory, and Next.js automatically generates the necessary HTML head tags for you.

This is my recommended approach. It’s cleaner, less error-prone, and leverages the framework's built-in optimizations.

Step-by-Step: Adding Your Favicon

Let's get this done in under five minutes. No prerequisites needed, other than a Next.js project running the App Router.

Step 1: Prepare Your Icon Files

You need a few key files for broad compatibility. While you can create these yourself, a generator ensures they're correctly sized and formatted. (Shameless plug: our free favicon generator handles this perfectly).

Step 2: Place Icons in the Root of the `app` Directory

This is the most important step. Move your generated icons directly into the /app folder. Do not put them in the /public folder.

Your project structure should look like this:

app/
├── layout.tsx
├── page.tsx
├── favicon.ico       <-- For legacy browsers
├── icon.svg          <-- For modern browsers (recommended)
└── apple-icon.png    <-- For iOS home screen
public/
└── ... (your other static assets)

Next.js will automatically detect these files and generate the corresponding <link> tags in your site's <head>.

Step 3: Remove Any Old or Manual Declarations

If you have an old favicon.ico in your public folder, delete it. The file in /app takes precedence, but it's best to avoid confusion.

Also, check your root layout.tsx and remove any manually added <link rel="icon" ...> tags. The file-based method makes them redundant.

Step 4: Verify the Result

Run your dev server (npm run dev) and inspect the source code of your page. You should see that Next.js has generated the tags for you, something like this:

<head>
  ...
  <link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="any">
  <link rel="icon" href="/icon.svg" type="image/svg+xml" sizes="any">
  <link rel="apple-touch-icon" href="/apple-icon.png" type="image/png" sizes="any">
  ...
</head>

Seeing these tags means it's working. The framework handled everything.

Real-World Example: How GitHub Does It

Curious how the pros handle this? Take a look at GitHub. They serve a favicon.ico, multiple PNG sizes for different devices, and a special monochrome SVG for the Safari Pinned Tab icon. The Next.js file-based approach is designed to build this kind of robust, multi-device support automatically, saving you from writing all those <link> tags by hand.

Common Pitfalls to Avoid

If your icon isn't showing up, it's usually one of these three things.

1. The `public` Folder Trap

Again, for metadata files like favicons, the app directory is the correct place in modern Next.js. While the public folder still works for general static assets, icons placed in /app have special meaning and take priority.

2. Aggressive Browser Caching

Your code is perfect, but you still see the old icon (or no icon). This is almost always the browser's fault. Browsers cache favicons very aggressively.

A simple page refresh often isn't enough. You'll need to do a hard refresh or clear your browser's cache. We wrote a detailed guide on how to force a favicon cache clear if you get stuck.

3. Incorrect File Names

The names favicon.ico, icon.png, icon.svg, and apple-icon.png are not arbitrary. They are conventions Next.js specifically looks for. A typo like my-icon.svg won't be picked up automatically.

And that’s it. By embracing the file-based convention, you're working with the framework, not against it. You get optimized, correct HTML for free, and your project stays clean. No more wrestling with <head> tags just to get your brand to show up in a browser tab.