benjamincanac · GitHub

and others added 3 commits

June 25, 2026 11:27
Add the icons Nuxt UI uses (chevrons, loading spinner, close, etc.) to `@nuxt/icon`'s client bundle via the `icon:clientBundleIcons` hook so they're embedded at build time instead of fetched at runtime, removing the first-paint flash.
Icons are only added when their collection data is installed (resolved from `rootDir`/`workspaceDir` to match `@nuxt/icon`), so missing collections fall back to runtime loading instead of failing the build.
Resolves #6295
Temporary: relies on nuxt/icon#502 (client bundle resolves collections from rootDir/workspaceDir). Revert to a normal version once it's released.
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>

@vercel

Closed

8 tasks

@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

@benjamincanac

@vercel

Base automatically changed from feat/pre-bundle-icons to v4

June 30, 2026 12:39
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/module.ts
#	src/utils/icons.ts
#	test/utils/icons.spec.ts

@vercel

A virtual module can't resolve a bare specifier like `@iconify/vue` in a non-workspace install, which broke standalone Vue builds. Export only the icon data from `virtual:nuxt-ui-icons` and do the `addIcon` registration in a real runtime plugin that owns the `@iconify/vue` import.

@benjamincanac

coderabbitai[bot]

@vercel

@benjamincanac

@vercel

@benjamincanac

@vercel

Scans the project source for icon usages from installed collections and bundles them, mirroring `@nuxt/icon`'s `clientBundle.scan`, so icons used in your own components render offline too. Opt-in, off by default.

coderabbitai[bot]

@vercel

@benjamincanac

@vercel

…alone

@benjamincanac

@benjamincanac

@vercel

Instead of a hand-rolled scanner/loader, use the canonical primitives from @nuxt/icon 2.3.1 (nuxt/icon#506): `resolveBundleIcons` (with Nuxt UI's defaults on the soft `extraIcons` tier so a missing collection degrades to runtime instead of failing the build), `IconUsageScanner` for `scan`, and `generateClientBundleCode` (which emits `init(addIcon)` and enforces `sizeLimitKb`). The runtime plugin registers via `init(addIcon)` on Nuxt UI's own `@iconify/vue`.

@vercel

- Parse user `clientBundle.icons` against @nuxt/icon's known collection list so multi-word dash forms (e.g. `i-material-symbols-menu`) resolve instead of mis-splitting and hard-failing the build.
- Read scanned files in bounded batches to avoid EMFILE on large workspaces (@nuxt/icon's scanFiles reads all at once).
- Don't cache a rejected generation promise, so a fix is picked up without a dev-server restart.
- Drop the now-unused `@iconify/utils` dependency and root `@iconify-json/lucide` devDependency (leftovers from the pre-delegation implementation and removed SSR test).

@vercel

Match `@nuxt/icon`'s vite plugin: an unresolved `clientBundle.icons` entry throws on build but only warns in dev, so a typo doesn't crash the dev server (it falls back to runtime there). This removes the reason for the rejected-promise cache workaround, so `load()` goes back to a plain memoized `generate()`.

@vercel

@benjamincanac

@benjamincanac

@vercel

@benjamincanac

@vercel

@benjamincanac

@vercel

@benjamincanac

@vercel

@benjamincanac

@benjamincanac

Flowm added a commit to Flowm/satvis that referenced this pull request

Jul 8, 2026
Enable @nuxt/ui's client-side icon bundling (nuxt/ui#6635) so every
icon — Nuxt UI's own component icons and the app's fa6/lucide icons —
is embedded into the build instead of fetched from api.iconify.design
at runtime. This restores offline icon rendering and removes the
third-party runtime dependency. Verified against a production build:
the icon SVG data is inlined and the running app makes zero requests
to api.iconify.design.
@nuxt/ui is temporarily pinned to the ephemeral pkg.pr.new preview of
#6635, which is merged but not yet in a published release. Swap it
back to a normal semver once the next @nuxt/ui release ships; the
vite.config and @iconify-json/lucide changes stay identical.
Assisted-by: ClaudeCode:claude-opus-4-8

Closed

This was referenced

Jul 21, 2026

Closed

Closed

Flowm added a commit to Flowm/satvis that referenced this pull request

Aug 1, 2026
The local icon bundling added in  b5ab26d  needed nuxt/ui#6635, which was
merged but unreleased, so the dependency was pointed at an ephemeral
pkg.pr.new artifact. That PR shipped in @nuxt/ui 4.10.0, so the pin can
become an ordinary semver range again.
This removes a real fragility: pkg.pr.new artifacts are build outputs
rather than registry releases and can be collected at any time, which
would break a fresh install outright. A URL dependency is also invisible
to `pnpm update`, so the package was silently frozen at 4.9.0.
The vite.config icon.clientBundle option is unchanged -- it is the same
API the PR added, now released.
Verified by building both ways and comparing the emitted ui chunk: 4.10.0
and the 4.9.0 preview each inline the same 27 icon path bodies. Both also
contain a single literal "api.iconify.design", which is Iconify's default
endpoint constant compiled into the library and not a runtime fetch --
it was equally present before this change. oxlint, oxfmt --check, vue-tsc
and 336 tests pass.
Note the unmet @tiptap peers pnpm reports are unaffected: 4.10.0 has the
same internal mismatch (extensions at 3.29.2 resolving @tiptap/core and
@tiptap/pm 3.27.1), so it is upstream packaging, not the pin.
Assisted-by: ClaudeCode:claude-opus-5

Closed

mathieutu added a commit to mathieutu/ui that referenced this pull request

Aug 14, 2026
The Vue-only Icon component renders @iconify/vue's Icon without
forwarding the `ssr` prop. In @iconify/vue, icon data is only
resolved synchronously during `setup()` when `ssr` is truthy;
otherwise resolution is deferred to `onMounted`, which never runs
during `renderToString`.
As a result, icons bundled via `icon.clientBundle` (nuxt#6635) still
render as empty `<svg>` tags in server-rendered HTML, even though
their data is already available in memory at render time. Icons
only ever appeared after client-side hydration.
Passing `:ssr="true"` lets `getIconData` resolve synchronously in
`setup()`, so bundled icons render their actual content during SSR.

mathieutu added a commit to mathieutu/ui that referenced this pull request

Aug 15, 2026
The Vue-only Icon component renders @iconify/vue's Icon without
forwarding the `ssr` prop. In @iconify/vue, icon data is only
resolved synchronously during `setup()` when `ssr` is truthy;
otherwise resolution is deferred to `onMounted`, which never runs
during `renderToString`.
As a result, icons bundled via `icon.clientBundle` (nuxt#6635) still
render as empty `<svg>` tags in server-rendered HTML, even though
their data is already available in memory at render time. Icons
only ever appeared after client-side hydration.
Setting `ssr` unconditionally would start an unawaited API request
from `setup()` for any icon that isn't already bundled, so it's
only enabled once `iconLoaded()` confirms the data is already in
the in-memory store. Unbundled icons keep loading from the API on
mount, unchanged.

mathieutu added a commit to mathieutu/ui that referenced this pull request

Aug 15, 2026
The Vue-only Icon component renders @iconify/vue's Icon without
forwarding the `ssr` prop. In @iconify/vue, icon data is only
resolved synchronously during `setup()` when `ssr` is truthy;
otherwise resolution is deferred to `onMounted`, which never runs
during `renderToString`.
As a result, icons bundled via `icon.clientBundle` (nuxt#6635) still
render as empty `<svg>` tags in server-rendered HTML, even though
their data is already available in memory at render time. Icons
only ever appeared after client-side hydration.
Setting `ssr` unconditionally would start an unawaited API request
from `setup()` for any icon that isn't already bundled, so it's
only enabled once `iconLoaded()` confirms the data is already in
the in-memory store. Unbundled icons keep loading from the API on
mount, unchanged.

mathieutu added a commit to mathieutu/ui that referenced this pull request

Aug 15, 2026
The Vue-only Icon component renders @iconify/vue's Icon without
forwarding the `ssr` prop. In @iconify/vue, icon data is only
resolved synchronously during `setup()` when `ssr` is truthy;
otherwise resolution is deferred to `onMounted`, which never runs
during `renderToString`.
As a result, icons bundled via `icon.clientBundle` (nuxt#6635) still
render as empty `<svg>` tags in server-rendered HTML, even though
their data is already available in memory at render time. Icons
only ever appeared after client-side hydration.
Setting `ssr` unconditionally would start an unawaited API request
from `setup()` for any icon that isn't already bundled, so it's
only enabled once `iconLoaded()` confirms the data is already in
the in-memory store. Unbundled icons keep loading from the API on
mount, unchanged.

Read the original on github.com ↗