Favicon Setup by Framework — Free, Fast & Private
Every framework on this page consumes the same seven files, because the browsers and platforms asking for them do not care what built your site. What differs, and differs more than people expect, is the two-step install: which directory the build treats as untouched static output, and how a tag gets into the head of a document you may never write by hand.
Those two answers range from "drop it in public/ and you are done" to "list the file in a JSON array or the build silently omits it". Guessing costs an afternoon. Each guide below shows the real path and the real snippet for one framework, along with the specific way that framework tends to fail.
Static directory and head mechanism, side by side
| Framework | Static files go in | Head is written in |
|---|---|---|
| Next.js | app/ (file conventions) or public/ | app/layout.js — metadata export |
| React | public/ | index.html (Vite) or public/index.html (CRA) |
| Vue | public/ | index.html (create-vue) or public/index.html (Vue CLI) |
| Svelte | static/ | src/app.html |
| Angular | public/ (Angular 18+) or src/assets (earlier) | src/index.html |
| Astro | public/ | src/layouts/Layout.astro (or a shared BaseHead.astro) |
| Nuxt | public/ (project root, even in Nuxt 4) | nuxt.config.ts — app.head.link |
| Ruby on Rails | app/assets/images/ or public/ | app/views/layouts/application.html.erb |
| Laravel | public/ | resources/views/layouts/app.blade.php |
Four patterns, nine frameworks
Read that table for a minute and the shape of the problem appears. There are really only four ways a framework handles this, and knowing which camp yours is in tells you most of what you need before you open a single guide.
The first camp is copy-verbatim plus a handwritten HTML file. Vite-based React, Vue and Astro all work this way, along with SvelteKit once you accept that its folder is called static/ rather than public/. Files go in, files come out unchanged, and you edit an HTML document that really exists in the repository. This is the easiest camp to work in and the easiest to break with a hardcoded absolute path when the site is later deployed under a subdirectory.
The second camp has no HTML file at all. Nuxt builds its document from a config object, so the head is an array of link descriptors in nuxt.config rather than markup. Next.js goes further and will generate the tags from filenames alone, appending a content hash that solves favicon cache-staleness for free. Both are more pleasant than they sound, and both punish you for mixing the declarative route with the manual one — duplicated icon tags are the standard symptom.
The third camp routes assets through a server-side pipeline. Rails fingerprints anything under app/assets and expects you to call a helper that resolves the digested URL, which is why favicon.ico still has to sit in public/ undigested for the bare root request browsers make regardless of your markup. Laravel skips the pipeline for these files entirely, because public/ is already the web server document root — the shortest path of any framework here, and the reason its classic failure is the zero-byte placeholder favicon nobody thinks to check.
The fourth camp is Angular on its own, and it is the only one where putting a file in a directory is not sufficient. The CLI copies exactly what the assets array in angular.json lists and ignores everything else, no matter how sensibly it is named. A favicon that works in development and 404s in production is nearly always that array.
What every guide assumes
All nine start from the same seven files, which is the complete set current platforms actually request: favicon.ico bundling the 16, 32 and 48 pixel entries; loose favicon-16x16.png and favicon-32x32.png for browsers that prefer PNG; favicon-48x48.png for desktop shortcuts; apple-touch-icon.png at 180 pixels for iOS home screens; and android-chrome-192x192.png plus android-chrome-512x512.png for the web app manifest. The longer lists you will find elsewhere are mostly fossils from platforms that no longer exist.
Two of those seven never appear in your HTML in any framework. The android-chrome pair is referenced from the manifest, which is what supplies the home screen icon and the splash image when someone installs your app. That distinction is invisible on a desktop browser and immediately obvious on an Android phone, so if installs matter to you, test on a real device rather than trusting the tab.
One habit is worth carrying into whichever guide you open: after the build, look at the output directory rather than the source tree, and request /favicon.ico directly in the address bar of the deployed site. Between them those two checks catch the file that never got copied, the path prefix nobody accounted for, and the single-page-app fallback rule that answers every unknown URL with HTML — three failures that look identical from the browser tab and have nothing in common underneath.
