Your images are processed in your browser — Cloud HD (Pro, opt-in) is the only exception
public/ + app.head in nuxt.config

Nuxt Favicon Generator

Nuxt has no HTML file for you to edit. The head is data — an array of link objects in nuxt.config, merged with anything useHead adds at runtime. Files still go in public/ at the project root.

Drop an image
Drop a logo or image
PNG, JPG, WebP, GIF · square 512×512+ works best · non-square images are padded, never cropped · processed in your browser

Favicon Setup for NuxtFree, Fast & Private

Nuxt 3 renamed the Nuxt 2 static/ directory to public/, and Nuxt 4 moved application source into an app/ directory while deliberately leaving public/, server/ and the config file at the project root. So public/ is correct across every currently supported version, and it is one of the few paths a Nuxt 3 to 4 migration does not touch.

Where each generated file goes in Nuxt

The generator above emits eight files with fixed names, site.webmanifest included. Static assets in Nuxt live in public/ (project root, even in Nuxt 4), and the markup is written in nuxt.config.ts — app.head.link. This is the mapping between the two.

Generated fileGoes toNotes
favicon.icopublic/favicon.icoOverwrites the Nuxt logo the scaffold ships.
favicon-16x16.pngpublic/favicon-16x16.pngDeclared in app.head.link.
favicon-32x32.pngpublic/favicon-32x32.pngDeclared in app.head.link.
favicon-48x48.pngpublic/favicon-48x48.pngOptional — already inside the .ico.
apple-touch-icon.pngpublic/apple-touch-icon.pngiOS home screen, 180×180.
android-chrome-192x192.pngpublic/android-chrome-192x192.pngManifest only, read by the downloaded manifest or by @vite-pwa/nuxt.
android-chrome-512x512.pngpublic/android-chrome-512x512.pngManifest only, PWA splash.
site.webmanifestpublic/site.webmanifestSkip this one file if @vite-pwa/nuxt generates a manifest — two manifests is the duplication trap.

The head is configuration, not markup

There is no index.html in a Nuxt project. The document is assembled at request time — or at generate time — from an object, and the canonical place to put site-wide head entries is the app.head block in nuxt.config. Each link is a plain object whose keys become attributes, which reads oddly the first time and pays off immediately: it is data you can compose, spread from a shared constant, or generate from the same list that drives your manifest.

Everything declared there applies to every route with no per-page work. Because it is evaluated at build time it also lands in the server-rendered HTML, so crawlers and link preview services that never run JavaScript still see the icons. That is a real advantage over the runtime-only approach of setting them from a component.

The useHead composable exists for the tags that genuinely vary — a page title, a canonical URL, an Open Graph image tied to a specific article. You can override the icon from a page with it, and Nuxt will reconcile the duplicate link entries rather than emitting both, but that is a niche need. Favicons belong in the config.

nuxt.config.ts

export default defineNuxtConfig({
  app: {
    head: {
      link: [
        { rel: 'icon', href: '/favicon.ico', sizes: '16x16 32x32 48x48' },
        { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
        { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
        { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' },
        { rel: 'manifest', href: '/site.webmanifest' },
      ],
    },
  },
});

public/ stays at the project root, including in Nuxt 4

Nuxt 4 restructured the project: components, pages, layouts and composables moved under an app/ directory so that editor tooling and file watchers stop tripping over server code and node_modules. What did not move is public/. It remains a sibling of nuxt.config.ts at the project root, alongside server/ and any modules directory.

This catches people mid-migration who dutifully move everything into app/ and then find their icons 404ing. It also catches anyone following a Nuxt 2 tutorial, where the folder was called static/ — that name has been dead since Nuxt 3 and a static/ directory in a modern project is simply not served.

Files in public/ are copied verbatim into the build output with no hashing, which is why an absolute path in the config is stable. That also means no automatic cache busting, so a rebrand needs either a filename change or a version query string on the href.

Project layout — Nuxt 4

my-app/
  app/                     ← pages, components, layouts, composables
    app.vue
    pages/
  public/                  ← still at the root, NOT inside app/
    favicon.ico
    apple-touch-icon.png
    android-chrome-192x192.png
    android-chrome-512x512.png
    site.webmanifest
  server/
  nuxt.config.ts

Deploying under a base URL

Nuxt serves the app from app.baseURL, which defaults to "/" and is commonly overridden through the NUXT_APP_BASE_URL environment variable when a site is published under a path. Absolute hrefs written into the config are not rewritten by that setting, so a hardcoded /favicon.ico misses on a prefixed deployment.

The composable useRuntimeConfig().app.baseURL gives you the value at runtime, and joinURL from the ufo package that ships with Nuxt joins it cleanly without doubled slashes. If your deployment is always at the root, plain absolute paths are fine and simpler — just make the choice knowingly rather than discovering it in staging.

Composing hrefs against a base URL

// app.vue — when the site may be served under a path prefix
import { joinURL } from 'ufo';

const base = useRuntimeConfig().app.baseURL;
useHead({
  link: [
    { rel: 'icon', href: joinURL(base, 'favicon.ico'), sizes: '16x16 32x32 48x48' },
    { rel: 'apple-touch-icon', sizes: '180x180', href: joinURL(base, 'apple-touch-icon.png') },
  ],
});

Build outputs, and where the files actually land

nuxt build produces a Nitro server bundle in .output/, with the static assets under .output/public/ — that directory is what a CDN or static edge layer serves, and your icons will be at its top level. nuxt generate prerenders every route and produces a fully static tree under .output/public/ as well, which you can upload anywhere. Either way, checking that directory after a build is the fastest way to confirm the files were picked up.

If you are using @vite-pwa/nuxt or the Nuxt PWA module, be aware that it generates its own manifest and may inject its own icon link tags, which can duplicate the ones in your config. Configure the module with your android-chrome PNGs rather than declaring the manifest twice, and check the rendered head afterwards.

The manifest, unless a PWA module is writing it for you

The link array above declares a manifest at /site.webmanifest. On a plain Nuxt project nothing creates that file — public/ is copied verbatim and Nuxt adds nothing to it — so the tag points at a path that returns a 404 on every route in the app. Decide which of the two owns it and do only that one. If you are not running a PWA module, the generator above downloads the file: drop it into public/ beside the icons and change name and short_name.

If you are running @vite-pwa/nuxt, do the opposite: skip the downloaded file entirely, delete the manifest entry from app.head.link, and configure the module with the two android-chrome PNGs instead. The module generates a manifest and injects its own link tag, so shipping ours as well produces two competing manifests in the rendered head and the browser picks one. That is the same duplication trap described above, and it is the one case in this whole cluster where the right move is not to use the file the tool gives you.

One thing the downloaded file does buy you on the plain route: its icon paths are relative, so they follow the manifest under app.baseURL. Absolute hrefs in nuxt.config are not rewritten by that setting — the section above says so about the icon links — and a hand-written manifest is worse, because no build value reaches inside a static JSON file at all. Relative src values are resolved against the manifest URL, so a NUXT_APP_BASE_URL of "/app/" carries them along with it, as long as the manifest stays in public/ with the PNGs.

public/site.webmanifest — as downloaded (skip it entirely if a PWA module generates one)

{
  "name": "Your App",
  "short_name": "App",
  "start_url": ".",
  "display": "standalone",
  "icons": [
    {
      "src": "android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "android-chrome-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Whatever you paste, the artwork itself never leaves your machine. The set above is rendered with the Canvas API and the .ico container is assembled in JavaScript on your device, so an unreleased logo stays unreleased. If the 16px preview looks like mush, that is the honest signal to simplify the mark before it ships to a Nuxt tab near you.

How it works

  1. Generate the set: Drop a square 512px logo into the tool above and download the eight files.
  2. Copy into public/: At the project root, beside nuxt.config.ts — not inside app/, even on Nuxt 4. Overwrite the Nuxt logo favicon.
  3. Declare them in nuxt.config: Add link objects to app.head.link. Each object key becomes an HTML attribute, and the entries apply to every route.
  4. Add public/site.webmanifest: Nuxt creates none, so the manifest entry in app.head.link 404s until the downloaded file is in public/ beside the icons. Edit name and short_name; its relative icon paths follow app.baseURL on their own. Running @vite-pwa/nuxt instead? Skip the file, drop that link entry, and let the module own the manifest.
  5. Check .output/public after build: The eight files should be at the top level of that directory, and the rendered head should have no duplicate icon tags from a PWA module.

Frequently asked questions

Where do favicons go in Nuxt 3 and Nuxt 4?
public/ at the project root. Nuxt 3 renamed the old Nuxt 2 static/ folder to public/, and Nuxt 4 left public/ at the root even though pages and components moved into app/. A static/ folder in a modern Nuxt project is not served at all.
How do I add favicon link tags without an index.html?
Nuxt has no HTML file to edit. Declare them as objects in the app.head.link array in nuxt.config, where each key becomes an attribute. They apply to every route and land in the server-rendered HTML, so crawlers see them.
Should I use useHead instead?
Only for tags that vary per page. Favicons are identical everywhere, so the config is the right home — it is evaluated at build time and needs no component to run. useHead can override an icon on a specific route if you really need it; Nuxt reconciles rather than duplicating.
Did public/ move into app/ in Nuxt 4?
No. The Nuxt 4 restructure moved application source into app/ but deliberately left public/, server/ and nuxt.config at the project root. Moving public/ during a migration is a common cause of newly 404ing icons.
My favicon 404s when the site is served under a path.
Absolute hrefs in the config are not rewritten by app.baseURL. Read useRuntimeConfig().app.baseURL and join it with joinURL from ufo, or keep the deployment at the domain root where plain absolute paths are correct.
Where do the built files end up?
Under .output/public/ for both nuxt build and nuxt generate. Your icons sit at the top level of that directory. Checking there is the quickest way to confirm the files were copied before debugging any markup.
The PWA module added its own icon tags.
@vite-pwa/nuxt generates a manifest and can inject icon links of its own, which duplicates what you declared. Configure the module with the android-chrome PNGs instead of declaring the manifest in both places, then inspect the rendered head. This is the one case where you should not ship the downloaded site.webmanifest at all.
Do I still write site.webmanifest by hand?
Not on a plain Nuxt project — the generator downloads it, and it goes in public/ with the icons. Its relative icon paths also follow app.baseURL, which absolute hrefs in nuxt.config do not and a hand-written manifest cannot, since no build value is substituted inside a static JSON file. If @vite-pwa/nuxt is in the project, skip the file and let the module own the manifest.

All Image Tools

AI Tools

Solutions by use case