How to Add a Favicon in React, Next.js, Vue & Angular
Published December 4, 2025 · Updated June 9, 2026
Add a favicon in React, Next.js, Vue, Nuxt, or Angular. The files go in public/ (or app/) and the same four link tags end up in the head — only the wiring differs.
The short version: in React, Next.js, Vue, Nuxt, and Angular, the favicon files still go in the framework’s static folder (public/, or app/ in Next.js), and the same four <link> tags end up in the rendered <head>. What changes between frameworks is only where the files live and how you get the head tags in — a raw HTML template, a <Head> component, a config object, or, in Next.js App Router, nothing at all.
In one line: same files, same <head>, different folder and wiring per framework.
So before the framework-specific steps, here’s the shared target every one of them is aiming at. Generate the modern set from one 512×512 source, then make sure these tags reach the page head:
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
That’s the clean, current set — no rel="shortcut icon" (never a valid relation), no long list of per-size PNG tags. The favicon.ico is multi-size (16/32/48 bundled), the SVG is the sharp primary icon, the Apple touch icon handles iOS, and the manifest carries the PWA sizes. See the favicon HTML reference for what each tag does and the favicon sizes guide for the files behind them.
React with Create React App (CRA)
CRA ships a default public/favicon.ico and a public/index.html template. Replace the files and edit the template:
- Drop your files in
public/—favicon.ico,favicon.svg,apple-touch-icon.png,icon-192.png,icon-512.png,icon-512-maskable.png, andsite.webmanifest. - Edit
public/index.html. CRA uses the%PUBLIC_URL%placeholder, which resolves to the public path at build time:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="32x32" />
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
- Restart the dev server. CRA names its manifest
manifest.jsonby default; if you keep that name, point the<link rel="manifest">at it instead.
React (and anything) with Vite
Vite keeps the entry index.html in the project root, not in public/. Files in public/ are served from /.
- Put the files in
public/(same set as above). - Edit the root
index.htmlwith plain root-relative paths — no placeholder needed:
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
This is identical for Vite + React, Vite + Vue, Vite + Svelte, or vanilla Vite — the entry HTML is the same file in every case.
Next.js: App Router (file-based, recommended)
This is the part people get wrong, so be precise. In the App Router, Next.js uses file-based metadata: drop icon files with specific reserved names directly inside the app/ directory and Next.js auto-detects them and generates the <head> tags for you. You write no <link> markup.
The reserved names are:
File in app/ | Generates |
|---|---|
favicon.ico | <link rel="icon" href="/favicon.ico" sizes="..."> |
icon.svg (or icon.png) | <link rel="icon" href="/icon.svg" type="image/svg+xml"> |
apple-icon.png | <link rel="apple-touch-icon" href="/apple-icon.png"> |
Note the names: it’s icon and apple-icon (not favicon.svg or apple-touch-icon.png) when they live in app/. So your layout looks like this:
app/
├── favicon.ico ← auto-detected
├── icon.svg ← auto-detected
├── apple-icon.png ← auto-detected
├── layout.tsx
└── page.tsx
The web manifest is the one piece that isn’t a single magic file: put site.webmanifest (or manifest.json) in public/ and declare it in the metadata export so Next.js emits the <link rel="manifest">:
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
manifest: '/site.webmanifest',
};
If you’d rather keep everything in public/ and declare it explicitly, you can — point metadata.icons at the files instead of relying on the app/ names. But the file-based convention is the idiomatic App Router approach and the one Next.js docs lead with.
Next.js: Pages Router
Older Pages Router projects (pages/) have no file-based metadata. Put the files in public/ and inject the tags with the next/head component, usually in pages/_app.js:
// pages/_app.js
import Head from 'next/head';
export default function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
<Component {...pageProps} />
</>
);
}
Vue 3 + Vite
A Vite-based Vue 3 project (the create-vue default) works exactly like the Vite section above: files in public/, tags in the root index.html.
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
If you’re on an older Vue CLI (Webpack) project instead, the template is public/index.html and paths use the <%= BASE_URL %> variable, e.g. href="<%= BASE_URL %>favicon.ico".
Nuxt 3
Nuxt 3 serves public/ from the root and manages the head through config or a composable. Put the files in public/, then either set them globally in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
link: [
{ rel: 'icon', href: '/favicon.ico', sizes: '32x32' },
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
{ rel: 'manifest', href: '/site.webmanifest' },
],
},
},
});
…or use useHead() in app.vue with the same link array if you prefer keeping head logic in the component.
Angular
Angular’s twist is the build config: static files only reach the output if they’re listed in angular.json.
- Put the files in
src/(or a subfolder likesrc/assets/). - List them under
assetsinangular.jsonso they’re copied to the build:
"assets": [
"src/favicon.ico",
"src/favicon.svg",
"src/apple-touch-icon.png",
"src/site.webmanifest"
]
- Reference them in
src/index.html. The template already has<base href="/">, so root-relative paths work:
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
Generate the whole set first
Every section above assumes you already have the modern file set: a multi-size favicon.ico, a scalable favicon.svg, an opaque 180×180 apple-touch-icon.png (transparent backgrounds turn black on iOS), and 192/512/512-maskable PNGs in the manifest. Exporting all of those by hand — at the right sizes, with the maskable icon’s own padded artwork — is fiddly. FaviconBuilder generates the complete set plus the copy-paste HTML and manifest from a single upload (SVG preferred, or a PNG/JPG 512×512 or larger) — free, no account, and your image never leaves your browser.
Why the favicon isn’t updating
Almost every framework favicon bug is caching, not code:
- Hard-refresh with
Cmd/Ctrl + Shift + Rto bypass the browser favicon cache. - Restart the dev server so the
public/(orapp/) folder is re-scanned and re-served. - Hit the file URL directly — open
/favicon.icoand/favicon.svgin the browser. A 404 means the file isn’t where the framework expects it (wrong folder, or missing fromangular.json). - Test the production build, not just dev — paths and static serving can differ. Run
npm run buildthen the framework’s preview/start command before deploying.
If it’s still blank after all that, walk through the favicon not showing checklist.
Summary
Across React, Next.js, Vue, Nuxt, and Angular the job is the same: files in the static folder, four <link> tags in the head. The only differences are location and wiring:
- CRA —
public/, editpublic/index.html,%PUBLIC_URL%paths. - Vite (React/Vue/etc.) —
public/, edit the rootindex.html, plain/paths. - Next.js App Router — files in
app/asfavicon.ico,icon.svg,apple-icon.png(auto-detected); manifest inpublic/via the metadata export. - Next.js Pages Router —
public/, tags vianext/headin_app.js. - Nuxt 3 —
public/, tags vianuxt.config.tsoruseHead(). - Angular —
src/, list inangular.jsonassets, tags insrc/index.html.
Continue reading:
Frequently asked questions
Where do I put the favicon in a React app?
Drop the files in the public/ folder, which is served at the site root. In Create React App, reference them in public/index.html with %PUBLIC_URL%; in a Vite project, reference them in the root index.html with plain / paths. The files are copied to the build output untouched.
How do I add a favicon in Next.js App Router?
Put the icon files directly in the app/ directory using Next.js's reserved names: favicon.ico, icon.svg, and apple-icon.png. Next.js auto-detects them and injects the correct <link> tags into every page's <head> — you write no HTML. The web manifest still lives in public/.
Why is my favicon not updating in my framework dev server?
Browsers cache favicons aggressively, and dev servers cache static assets too. Hard-refresh (Cmd/Ctrl + Shift + R), then restart the dev server so the public/ folder is re-served. If it still sticks, open the favicon URL directly (e.g. /favicon.ico) to confirm the new file is being served.
How do I add a favicon in Angular?
Put the files in src/ (or src/assets/), list them under the assets array in angular.json so they're copied to the build, and reference them with <link> tags in src/index.html. Use root-relative / paths or rely on the <base href='/'> already in the template.
Do single-page apps need a different favicon setup than static sites?
No. SPAs serve a single HTML shell, so the favicon <link> tags live in that one template and apply everywhere. The icon files and the rendered tags are identical to any static site — only the folder location and templating syntax change per framework.