How to Add a Next.js Favicon to a Static Site: Complete Source Code Guide

Place your favicon.ico file in the public/ directory and reference it via <link rel="icon" href="/favicon.ico" /> in your root layout or custom _document component.

Adding a nextjs favicon to a statically exported site leverages the framework's built-in static file serving capabilities. The Next.js source code in vercel/next.js implements this through the serveStatic helper, which automatically maps files in the public/ folder to root URL paths without requiring additional routing configuration.

How Static File Serving Works in Next.js

When you build a static-exported Next.js site, the framework serves files placed in the public/ directory directly without extra routing logic. The internal serveStatic helper, located in [packages/next/src/server/serve-static.ts](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/serve-static.ts), resolves incoming request paths against the public folder and streams the corresponding file back to the client.

This behavior applies automatically to favicon.ico and any other static assets you place in public/. The framework also includes conflict protection via PUBLIC_DIR_MIDDLEWARE_CONFLICT in [packages/next/src/lib/constants.ts](https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/constants.ts), which prevents naming collisions with internal Next.js routes.

Step-by-Step: Adding Your Next.js Favicon

Follow these steps to implement a favicon in your static Next.js site.

1. Create the Public Directory

Ensure you have a public/ folder at the root of your project. This directory sits alongside your pages/ or app/ folder:

your-project/
├─ app/ or pages/
├─ public/
└─ package.json

2. Add the Favicon File

Place your favicon.ico file inside public/:

public/favicon.ico

You can also include alternative formats like favicon-16x16.png or favicon-32x32.png. These become accessible at /favicon-16x16.png and /favicon-32x32.png respectively.

3. Reference the Favicon in Your Layout

For App Router projects, add the link tag in your root layout:

// app/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
  icons: {
    icon: '/favicon.ico',
  },
};

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

Alternatively, use the Head component for direct HTML control:

// app/layout.tsx
import Head from 'next/head';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Head>
          <link rel="icon" href="/favicon.ico" />
        </Head>
      </head>
      <body>{children}</body>
    </html>
  );
}

For Pages Router projects, use a custom _document.tsx:

// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';

export default class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

4. Build and Export

Generate your static site with the following commands:


# Development mode for testing

npm run dev

# Production static export

npm run build

If using static export, ensure your next.config.js includes:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
};

module.exports = nextConfig;

5. Verify the Favicon is Served

Test that your favicon is accessible at the root path:

curl -I https://your-site.com/favicon.ico

The response should return HTTP 200 with Content-Type: image/x-icon. The Next.js test suite in [test/e2e/favicon-shortcircuit/favicon-shortcircuit.test.ts](https://github.com/vercel/next.js/blob/canary/test/e2e/favicon-shortcircuit/favicon-shortcircuit.test.ts) and [test/production/app-dir/metadata-static/app/manifest.ts](https://github.com/vercel/next.js/blob/canary/test/production/app-dir/metadata-static/app/manifest.ts) verifies that /favicon.ico returns a 200 status in both development and production modes.

Why This Approach Works

The nextjs favicon implementation relies on three key architectural decisions in the Next.js codebase:

  • serveStatic utility: Located in packages/next/src/server/serve-static.ts, this function handles incoming requests by checking if the pathname maps to a file within the public directory. When a browser requests /favicon.ico, the utility streams the file from public/favicon.ico directly.

  • Public folder constants: The PUBLIC_DIR_MIDDLEWARE_CONFLICT constant in packages/next/src/lib/constants.ts protects against route conflicts, ensuring your favicon path doesn't clash with internal Next.js internals like /_next/.

  • Development optimization: In development mode, Next.js short-circuits favicon requests to improve hot-reload performance, as tested in test/e2e/favicon-shortcircuit/favicon-shortcircuit.test.ts. This optimization doesn't affect production builds, where the actual static file is served.

Summary

  • Place favicon.ico in the public/ directory at your project root to make it accessible at /favicon.ico
  • Reference the favicon using <link rel="icon" href="/favicon.ico" /> in your App Router layout metadata or Pages Router _document.tsx
  • The serveStatic helper in packages/next/src/server/serve-static.ts automatically handles file streaming without custom routes
  • Verify your setup using curl or browser DevTools to confirm HTTP 200 responses
  • Development mode includes favicon request optimizations that don't affect static exports

Frequently Asked Questions

Can I use PNG or SVG files instead of ICO for my Next.js favicon?

Yes, you can use any image format supported by browsers. Place favicon.png or favicon.svg in your public/ folder and update the link tag's href attribute to match the filename and extension. The serveStatic utility in packages/next/src/server/serve-static.ts serves all file types equally, setting the appropriate Content-Type header based on the file extension.

Why isn't my favicon showing in development mode?

Next.js implements a favicon short-circuit in development to speed up hot reloading, as documented in test/e2e/favicon-shortcircuit/favicon-shortcircuit.test.ts. This optimization may cause the browser to receive an empty response for /favicon.ico requests during development. However, your actual favicon file in public/ will still be served correctly in production builds and static exports.

Do I need to configure next.config.js to serve favicons?

No additional configuration is required. The static file serving behavior is built into Next.js core. As long as you place files in the public/ directory, the framework automatically serves them at the root path. The PUBLIC_DIR_MIDDLEWARE_CONFLICT check in packages/next/src/lib/constants.ts ensures your favicon path won't conflict with internal Next.js routes.

How do I add multiple favicon sizes for different devices?

Create multiple favicon files (e.g., favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png) in your public/ folder. Then reference each size with specific rel and sizes attributes in your layout's <head> or metadata configuration. Each file will be served statically according to the same serveStatic logic used for the standard favicon.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client