Skip to content

feat(unplugin): pre-bundle used icons into the Vue/Vite build#6635

Draft
benjamincanac wants to merge 1 commit into
feat/pre-bundle-iconsfrom
feat/icon-bundle-vue
Draft

feat(unplugin): pre-bundle used icons into the Vue/Vite build#6635
benjamincanac wants to merge 1 commit into
feat/pre-bundle-iconsfrom
feat/icon-bundle-vue

Conversation

@benjamincanac

@benjamincanac benjamincanac commented Jun 25, 2026

Copy link
Copy Markdown
Member

Resolves #5242 and supersedes #5894.

Context

On the Vue/Vite side, the icons Nuxt UI uses were never bundled, so @iconify/vue fetched them from the Iconify API at runtime. That broke offline use, caused the SSR "icon appears after hydration" flash, and added network round-trips. This is the Vue equivalent of what #6633 wires up for Nuxt through @nuxt/icon's client bundle.

What this does

A new @nuxt/ui/vite sub-plugin (src/plugins/icons.ts) loads the SVG data at build time with @iconify/utils and emits a virtual:nuxt-ui-icons module that registers the inlined data through @iconify/vue's addIcon. The vue-plugin install runs it on both server and client, so the icons render synchronously during SSR and fully offline, while keeping the i-lucide-* string syntax.

It reuses the src/utils/icons.ts helpers from #6633 and applies the nuxt/icon#502 lesson by resolving collections from the Vite config.root rather than process.cwd(), so workspace and monorepo builds work too.

Options

Bundling is on by default for Nuxt UI's own icons. You can extend or disable it:

import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    ui({
      icon: {
        clientBundle: {
          icons: ['lucide:heart', 'simple-icons:github']
        }
        // or set `clientBundle: false` to opt out entirely
      }
    })
  ]
})

Following the same decision as #6633, this does not add @iconify-json/lucide as a hard dependency. Icons are bundled only when their collection is installed, and otherwise fall back to runtime loading. The SSR docs have been updated to explain this.

How to test

  1. Install a collection in your Vue app, for example @iconify-json/lucide.
  2. Build or SSR-render a page that uses a Nuxt UI component with an icon. The icon shows up in the server-rendered HTML with no request to api.iconify.design.

In this repo, pnpm dev:vue:build bundles all 42 default lucide icons into the output with their SVG bodies inlined.

Notes

Embed the icons Nuxt UI uses at build time via a new `@nuxt/ui/vite` sub-plugin so they render during SSR and work fully offline with no runtime Iconify API fetch, mirroring what #6633 does on the Nuxt side. Adds an `icon.clientBundle` option to bundle your own icons on top of the defaults.

Co-authored-by: Typed SIGTERM <145281501+typed-sigterm@users.noreply.github.com>
@benjamincanac benjamincanac changed the title feat: pre-bundle used icons into the Vue/Vite build feat(unplugin): pre-bundle used icons into the Vue/Vite build Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant