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 · square 512×512+ works best · non-square images are padded, never cropped · processed in your browser

Favicon Setup for Nuxt — Free, 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 seven files with fixed names. 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 @vite-pwa/nuxt or a hand-written manifest.
android-chrome-512x512.pngpublic/android-chrome-512x512.pngManifest only, PWA splash.

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.

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 seven 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. Check .output/public after build: The seven 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.

All Image Tools

Solutions by use case