You've shipped a Nuxt app and the default favicon is still showing in the tab. Worse, your marketing team wants different icons on the checkout page and the admin dashboard. Hard-coding link tags won't cut it — Nuxt gives you programmatic control through the app.head config and the useHead composable, and understanding both is the difference between fighting your framework and letting it work for you.

This guide focuses specifically on managing your favicon through Nuxt's head configuration system, not the file-based metadata approach covered in our Nuxt favicon guide. If you need per-page icons, dark mode toggling, or runtime swaps, the head API is what you want.

Prerequisites

Step 1: Set a Default Favicon via app.head

The app.head property in nuxt.config.ts is the framework-wide default. Anything you put here gets injected into every page's <head> unless a page overrides it.

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      link: [
        { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
        { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
        { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }
      ]
    }
  }
})

Place your icon files in the public/ directory so Nuxt serves them at the root. According to the Nuxt SEO and meta documentation, the link array follows the same shape that the underlying unhead engine accepts, which in turn matches the WHATWG rel="icon" spec at html.spec.whatwg.org.

Step 2: Override the Favicon Per Page with useHead

Sometimes you want a different icon on specific routes. The useHead composable lets you push entries that override the global config for that page only.

<script setup lang="ts">
useHead({
  link: [
    { rel: 'icon', type: 'image/svg+xml', href: '/icons/admin-favicon.svg' }
  ]
})
</script>

<template>
  <div>Admin Dashboard</div>
</template>

Call useHead() at the top of your <script setup> block. Nuxt handles SSR injection, so the override is present in the initial HTML — no flash of the wrong icon on hydration, no client-side patching needed.

Step 3: Swap Favicons Dynamically Based on State

Want the favicon to react to a notification count or theme change? Combine useHead with a reactive ref or computed.

<script setup lang="ts">
const cartCount = useCartCount()

useHead({
  link: [
    {
      rel: 'icon',
      type: 'image/svg+xml',
      href: computed(() =>
        cartCount.value > 0
          ? `/icons/cart-${cartCount.value}.svg`
          : '/favicon.svg'
      )
    }
  ]
})
</script>

The computed href updates whenever cartCount changes. Nuxt's useHead tracks the reactivity and rewrites the <link> tag in place — no full page reload, no manual DOM manipulation. If you want to push this further with canvas-based badges, the underlying pattern is covered in our dynamic favicon JavaScript guide.

Step 4: Support Dark Mode Favicons

Modern browsers honor media attributes on icon links. Pair this with useHead and you get theme-aware icons without any JavaScript at all.

useHead({
  link: [
    { rel: 'icon', type: 'image/svg+xml', href: '/favicon-light.svg', media: '(prefers-color-scheme: light)' },
    { rel: 'icon', type: 'image/svg+xml', href: '/favicon-dark.svg', media: '(prefers-color-scheme: dark)' }
  ]
})

The browser picks the right one based on the user's system preference. This is the same pattern described in our favicon dark mode guide, but driven through Nuxt's head API instead of raw HTML.

Common Pitfalls

Wrapping Up

The app.head config handles your defaults; useHead handles everything dynamic. Together they cover every favicon scenario Nuxt apps typically need — from a single static icon to per-page swaps and reactive badges — without reaching for third-party head plugins or fighting Nuxt's defaults.

If you haven't generated your icon files yet, Mzu favicondl produces the full multi-resolution package (SVG, PNG, ICO, Apple Touch) in one click. Drop the files into public/, wire them into app.head, and your tabs will look professional within minutes.