and others added 3 commits
June 25, 2026 11:27Add 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>
Closed
8 tasks
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
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
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.
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.
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`.
- 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).
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()`.
Flowm added a commit to Flowm/satvis that referenced this pull request
Jul 8, 2026Enable @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, 2026Closed
Closed
Flowm added a commit to Flowm/satvis that referenced this pull request
Aug 1, 2026The 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, 2026The 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, 2026The 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, 2026The 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, 2026The 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.