17 min read

Next JS Favicon: A Guide for Perfect App Branding

Master your Next JS favicon setup. This guide covers the App Router, Pages Router, apple-touch-icons, and optimization tips for perfect branding on any device.

next js faviconnextjsfavicon guideweb developmentapp branding
Next JS Favicon: A Guide for Perfect App Branding

You deploy your app, open the production URL, and the tab still shows a generic icon. Sometimes it’s the default platform icon. Sometimes it’s your old favicon from three deployments ago. Sometimes Safari shows one thing, Chrome shows another, and iOS ignores both.

That’s the point where favicon setup stops being a basic checklist item and turns into a production issue.

A polished next js favicon setup affects how your product looks in browser tabs, bookmarks, install prompts, shared screens, and saved home screen shortcuts. If you’re shipping a SaaS app, those tiny assets carry more brand weight than is typically anticipated. They’re one of the few visual elements users keep seeing long after the landing page is gone.

Why Your Next JS Favicon Matters More Than You Think

A weak favicon setup makes a finished app look unfinished.

The common failure mode is simple. The UI is polished, auth works, billing works, onboarding works, and then the browser tab shows the wrong icon. That small mismatch makes the product feel less cared for. Users won’t say “the favicon broke trust,” but they will register that the app feels rough around the edges.

A young man looking at a laptop computer displaying a simple green square icon on the screen.

Tiny asset, real brand signal

Favicons matter most in the places developers often forget:

  • Tab-heavy workflows where users keep your app beside Stripe, Linear, Notion, and Slack
  • Bookmarks and reading lists where the icon becomes the primary visual identifier
  • Home screen installs on mobile where the app icon needs to look intentional
  • Shared demos and screen recordings where the tab strip is visible

If you’re working on launch polish, brand consistency matters just as much as copy or screenshots. That’s part of the same awareness problem discussed in this guide on building brand awareness.

Why Next.js made this easier

Next.js cleaned up a lot of old favicon pain with the App Router.

Next.js introduced automated favicon handling in its App Router with version 13, and for the 2.6 million active websites using the framework, developers can place a favicon.ico file in the /app root and let Next.js generate the needed tags automatically. The related static optimization can reduce bundle sizes by up to 90% via caching (Next.js app icons documentation).

That’s the happy path, and it works well.

Practical rule: If your favicon setup requires a lot of manual <head> maintenance in a modern App Router app, start by asking whether you’re fighting the framework instead of using it.

Where teams still get tripped up

The problem isn’t the first setup. It’s everything after that.

A favicon can be technically present and still wrong in production. The usual reasons are stale browser caches, CDN caching, duplicate icon declarations, route-level assumptions that don’t match how Next.js handles root app icons, and platform-specific files that were never generated in the first place.

The rest of this guide focuses on those real issues. Not just “how to add favicon.ico,” but how to make sure your next js favicon setup stays correct after deploys, device installs, and icon updates.

Generating and Organizing Your Favicon Files

Don’t start in layout.tsx. Start with the assets.

Most favicon problems come from using one file for every context and hoping browsers sort it out. They won’t. A modern app needs a small set of purpose-built files, each named and sized for a specific job.

A guide listing required and recommended favicon formats and sizes for modern web applications.

Start with a clean source asset

Design the icon from a square master file.

If you’re designing in Figma, build the favicon as a simple mark, not a full logo lockup. Tiny sizes punish detail. Thin strokes, text, and gradients often collapse badly. If your design team works across tools, the trade-offs in Figma vs Adobe XD matter less than exporting a clean source asset that survives downscaling.

Use a favicon generator if you want speed and consistency. RealFaviconGenerator is a practical choice because it outputs the common files teams usually forget.

The file set that actually matters

Here’s the set I’d prepare before touching code:

File Where it’s used Notes
favicon.ico Traditional browser tab icon Keep this even if you also use PNG or SVG
icon.png General modern browser icon Good fallback bitmap asset
icon.svg Modern scalable favicon Sharp in supported browsers
apple-icon.png or apple-touch-icon.png iOS home screen shortcut Use a clean square icon with padding awareness
manifest.json PWA metadata Connects your install experience to icon assets
web-app-manifest-192x192.png Android install surface Used by manifest-driven installs
web-app-manifest-512x512.png Large install and PWA contexts Useful for higher-resolution install assets
mask-icon.svg Safari pinned tab Monochrome use case, separate from your main favicon

Required versus nice-to-have

Not every file carries the same weight.

  • Must have: favicon.ico
  • Must have for mobile polish: Apple touch icon and manifest icons
  • Worth adding: SVG favicon for modern browsers
  • Situational: Safari pinned tab assets, extra desktop PNG sizes, larger social images

The icon that looks great at marketing-site size often looks muddy at tab size. Test the smallest version first, not last.

File naming discipline saves time

Name files exactly once and stick to the convention.

Avoid patterns like:

  • favicon-new.ico
  • favicon-final-final.ico
  • apple-touch-icon-v2.png

Those names create cache confusion and make your metadata harder to audit. Use stable names for stable assets, and only add versioning when you’re intentionally cache-busting.

A clean working set looks like this:

  • Core browser asset: favicon.ico
  • Vector version: icon.svg
  • Bitmap fallback: icon.png
  • Apple asset: apple-icon.png
  • Manifest: manifest.json
  • PWA icons: web-app-manifest-192x192.png, web-app-manifest-512x512.png

What usually doesn’t work well

Some choices are technically valid but operationally messy.

  • Over-detailed logos: Fine lines and text disappear.
  • Transparent edges without testing: They can look soft or awkward on some surfaces.
  • Single-format strategy: Relying only on SVG or only on ICO creates avoidable compatibility gaps.
  • Manual resizing in random tools: It often introduces blur, inconsistent padding, or export artifacts.

If you prepare the asset set carefully, implementation becomes straightforward. If the files are inconsistent, no amount of metadata cleanup will make the result look polished.

Implementing Favicons in the Next JS App Router

If you’re on the App Router, the best setup is usually the simplest one.

Put the right files in the right place and let Next.js do its job first. Add manual metadata only when you need more control.

A person coding in Next.js using the app router structure on a desktop computer monitor.

The default file-based setup

For a modern App Router app, place favicon files in the root of /app.

A clean structure looks like this:

app/
  layout.tsx
  page.tsx
  favicon.ico
  icon.png
  icon.svg
  apple-icon.png
  manifest.json

This is the setup many projects should start with.

Next.js recognizes file conventions and generates the relevant tags for you. That means less manual <head> code, less duplication, and fewer mistakes from stale legacy patterns.

What filenames Next.js expects

These are the practical filenames to know:

  • favicon.ico for the classic browser icon
  • icon.* for general icons, including formats like PNG or SVG
  • apple-icon.* for Apple-specific home screen usage
  • manifest.json for install metadata

A few implementation details matter:

  1. Put favicon.ico at the root of /app, not inside a nested route.
  2. Keep icon.* files in /app root if they represent the global app identity.
  3. Don’t duplicate the same icon in both file convention and manual metadata unless you mean to override behavior.
  4. Use root-level naming consistently, because favicons are a global concern in most apps.

The happy path in practice

If all you need is one global favicon, this may be enough:

app/
  favicon.ico
  apple-icon.png
  icon.png
  manifest.json
  layout.tsx

And your root layout can stay minimal:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

That’s it. No manual <link rel="icon"> tags required for the basic case.

If the App Router file convention already expresses your intent, prefer it over custom head markup. Simpler setups are easier to debug after deployment.

When to use the metadata object

Use the metadata export when you need explicit control.

Examples include:

  • separate light and dark mode icons
  • explicit type declarations
  • multiple icon entries
  • a manifest path you want to define centrally
  • a deliberate override of defaults

A practical root layout example looks like this:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Acme',
  icons: {
    icon: [
      { url: '/favicon.ico' },
      { url: '/icon.png', type: 'image/png' },
      { url: '/icon.svg', type: 'image/svg+xml' },
    ],
    apple: [{ url: '/apple-icon.png' }],
  },
  manifest: '/manifest.json',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

This is useful when you want the intent visible in code, not only in filesystem conventions.

Here’s a walkthrough if you want to see the broader App Router structure in action before layering on custom icon behavior:

Common mistakes in App Router projects

The problems I see most often are avoidable.

Duplicate icon declarations

Teams add favicon.ico in /app, then also hardcode <link rel="icon"> tags in a custom head component, then also define icons in metadata. Browsers may still pick one, but debugging becomes painful.

Choose one primary strategy.

Wrong file location

Putting favicon.ico in /public can still make sense in some manual setups, but if you’re relying on App Router file conventions, put it in /app root.

Expecting route-specific file conventions

Favicons are not like route-level Open Graph images. Many developers assume they can drop route-specific favicons into nested folders and get automatic per-route behavior. That’s where reality pushes you toward custom metadata or manual link management.

My production default

For most SaaS apps, I’d use this order of preference:

Need Best approach
One global app favicon /app file convention only
Global icon plus explicit metadata clarity /app files + metadata.icons
Runtime or conditional favicon logic Manual link management with custom control

That last case needs extra care. That’s where caching, deployment behavior, and browser quirks start to matter.

Legacy Support for the Next JS Pages Router

If your app still uses the Pages Router, you’re in the manual world.

That doesn’t mean the setup is bad. It just means you are responsible for wiring the favicon tags correctly. Many older production apps still run this way, and if the codebase is stable, there’s no need to force a migration just to fix icons.

Put files in public

For Pages Router projects, store favicon assets in the /public directory.

A typical structure looks like this:

public/
  favicon.ico
  apple-touch-icon.png
  icon.png
  icon.svg
  manifest.json
  web-app-manifest-192x192.png
  web-app-manifest-512x512.png

Files in /public are served from the root path, so public/favicon.ico becomes /favicon.ico.

Add the tags in _document.tsx

Use a custom _document.tsx when you need stable, global <head> tags across the app.

A practical example:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <link rel="icon" href="/favicon.ico" />
        <link rel="icon" href="/icon.png" type="image/png" />
        <link rel="icon" href="/icon.svg" type="image/svg+xml" />
        <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
        <link rel="manifest" href="/manifest.json" />
        <meta name="apple-mobile-web-app-title" content="Acme" />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

This setup is explicit, readable, and easy to audit.

When this approach is still the right one

Use the Pages Router pattern when:

  • You’re maintaining an older app that hasn’t migrated to /app
  • Your team wants explicit <head> control without mixing conventions
  • You need consistency in a codebase already built around _document.tsx

What to watch out for

Pages Router apps tend to accumulate old favicon tags.

Check for:

  • duplicate icon tags in _app.tsx, page-level Head, and _document.tsx
  • outdated filenames no longer present in /public
  • a mix of old Apple tag names and new asset names
  • stale manifest.json references after asset renames

If a Pages Router app shows the wrong favicon, inspect the final rendered HTML first. The bug is often a duplicate tag, not the file itself.

This method is more manual than the App Router path, but it’s predictable. That predictability is useful in mature codebases where a full migration isn’t on the roadmap.

Advanced Favicon Techniques and Troubleshooting

Most favicon guides stop right after “put favicon.ico in your app folder.” That’s fine for local development. It’s not enough for production.

The hard part starts when the icon won’t update after deploy, or when you need the favicon to change based on state, environment, or user context.

A person working on a desktop computer with a web browser displaying the Acme website homepage.

Why your favicon looks stuck

A favicon can get cached at multiple layers:

  • Browser cache
  • CDN cache
  • Hosting platform behavior
  • Preloaded metadata from earlier deploys

That’s why replacing the file alone often doesn’t seem to work.

Handling dynamic or conditional favicons is a significant challenge, with over 30% of “Next.js favicon not updating” queries stemming from caching issues on platforms like Vercel. Static metadata exports often cause default icons to override custom ones. An effective solution involves disabling default injection with unstable_noFaviconInjection: true and manually managing <link> tags, a pattern that has shown to resolve up to 20% of favicon mismatches in A/B deployment tests (favicon.im on adding a favicon to a Next.js project).

That tracks with real production behavior. The browser may still request an old file path, and the platform may still serve a cached result.

How to force-update a favicon

If you need an update to stick, use a deliberate cache-busting process.

Change the URL, not just the file

Browsers are much more willing to fetch a new favicon when the URL changes.

Instead of:

<link rel="icon" href="/favicon.ico" />

use:

<link rel="icon" href="/favicon.ico?v=2" />

For higher confidence after a rebrand or major update, rename the asset entirely:

<link rel="icon" href="/favicon-2026.ico" />

That’s blunt, but it works.

Clear duplicate declarations

If one part of your app points to /favicon.ico and another points to /icon.png?v=2, browsers can behave inconsistently. Audit the final <head> output and remove every stale declaration.

Rebuild the manifest relationship

If users install your app on mobile, update the icon file references inside manifest.json too. Teams often bust the browser tab icon and forget the install icons entirely.

A strong SEO and polish mindset applies here too. Little consistency issues tend to pile up, just like the broader technical details covered in this guide on how to improve search engine rankings.

Dynamic favicons that actually work

Changing the favicon based on app state sounds easy. In practice, Next.js metadata caching can get in the way.

Typical use cases:

  • unread notifications
  • environment badge for staging
  • tenant-specific branding
  • light and dark mode variants
  • status indicators for live jobs or incidents

If you need true runtime behavior, the safest pattern is usually manual control.

Disable default injection when needed

When Next.js auto-injects favicons and you also want dynamic behavior, conflicts can happen.

In cases where the default injection is the source of mismatch, use unstable_noFaviconInjection: true and manage the <link rel="icon"> tags yourself.

A manual layout approach might look like this:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" href="/favicon.ico?v=2" />
        <link rel="apple-touch-icon" href="/apple-icon.png?v=2" />
      </head>
      <body>{children}</body>
    </html>
  )
}

For client-driven state, you can update the favicon directly:

'use client'

import { useEffect } from 'react'

export function NotificationFavicon({ hasUnread }: { hasUnread: boolean }) {
  useEffect(() => {
    const link =
      document.querySelector("link[rel='icon']") ||
      document.createElement('link')

    link.setAttribute('rel', 'icon')
    link.setAttribute('href', hasUnread ? '/favicon-unread.ico' : '/favicon.ico')

    if (!link.parentNode) {
      document.head.appendChild(link)
    }
  }, [hasUnread])

  return null
}

This isn’t the prettiest solution, but it’s reliable for real runtime changes.

“If the favicon must react to client state, treat it like UI state. Don’t expect static metadata to behave like a live component.”

What usually fails

A few patterns sound elegant and then waste hours:

  • Relying on generateMetadata alone for per-request favicon changes when your deployment path still behaves statically
  • Keeping auto-generated and manual icons active together
  • Assuming Vercel deploy equals browser refresh
  • Testing only in one browser

Chrome, Safari, and mobile install surfaces all have their own habits. If the favicon matters to your brand, test all three.

Frequently Asked Next JS Favicon Questions

Can I use SVG for a next js favicon

Yes, and it’s a good option for modern browsers.

Still keep favicon.ico as a fallback. SVG is sharp and flexible, but a production setup shouldn’t depend on one format alone. For broad compatibility, use SVG as one layer in a multi-file favicon strategy, not the entire plan.

How do I support light mode and dark mode favicons

Use separate icon entries with a media condition when you’re managing metadata manually or explicitly.

A common pattern is:

export const metadata = {
  icons: {
    icon: [
      { url: '/icon-light.svg', media: '(prefers-color-scheme: light)' },
      { url: '/icon-dark.svg', media: '(prefers-color-scheme: dark)' },
    ],
  },
}

Keep both icons visually simple. Mode-specific swaps are useful, but only if each icon stays legible at small sizes.

Does manifest.json replace normal favicon tags

No.

The manifest helps with install surfaces and PWA behavior. It doesn’t replace the standard browser favicon relationship. You still need your regular icon declarations or App Router file-based setup for browser tabs and bookmarks.

Can I set different favicons for dynamic routes

Not with the basic root file convention alone.

If you want route-specific or tenant-specific icons in paths like app/blog/[slug]/page.tsx, move into explicit metadata logic or manual <link> control. That’s especially true if the icon depends on runtime state or branding tied to the request.

Why does the old favicon keep showing after deployment

Because browsers cache favicons aggressively, and your platform may cache them too.

The usual fixes are:

  • Change the icon URL
  • Remove duplicate declarations
  • Verify the rendered head
  • Update the manifest icons if installs are involved

When should I bring in outside React help

If your app already has metadata conflicts, mixed router patterns, white-label branding, and runtime icon requirements, this can turn into a broader front-end cleanup task. In that case, a specialized team can help untangle the app shell, metadata, and asset pipeline. If you need that kind of support, it’s reasonable to hire ReactJS developers who have handled production Next.js architecture rather than treating the favicon issue as an isolated bug.


If you’re preparing a SaaS launch, the little details matter. A clean favicon setup makes your product look finished everywhere people discover it. When you’re ready to put that product in front of more early users, list it on SubmitMySaas.

Want a review for your product?

Boost your product's visibility and credibility

Rank on Google for “[product] review”
Get a High-Quality Backlink
Build customer trust with professional reviews