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:
-
serveStaticutility: Located inpackages/next/src/server/serve-static.ts, this function handles incoming requests by checking if the pathname maps to a file within thepublicdirectory. When a browser requests/favicon.ico, the utility streams the file frompublic/favicon.icodirectly. -
Public folder constants: The
PUBLIC_DIR_MIDDLEWARE_CONFLICTconstant inpackages/next/src/lib/constants.tsprotects 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.icoin thepublic/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
serveStatichelper inpackages/next/src/server/serve-static.tsautomatically handles file streaming without custom routes - Verify your setup using
curlor 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:
curl -s https://instagit.com/install.md