From 9eadfa056900b3734737dcb7256070c229b2afaf Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 29 Sep 2026 13:49:44 +1000 Subject: [PATCH 1/2] fix(icon): register hyphenated collection prefixes With serverBundle off and no API fallback, @nuxt/icon knows no collection names, so i-vscode-icons-* and i-simple-icons-* split at the first hyphen and never resolve. Each miss logged a warning and stalled SSR up to the 1500ms fetch timeout. --- nuxt.config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nuxt.config.ts b/nuxt.config.ts index 508ab3c6..02404624 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,11 +1,20 @@ import { existsSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { unpublishedAgentPaths } from './layers/marketing/app/utils/agent-pages' +import pkg from './package.json' import { dependencyPluginCompat } from './scripts/lib/dependency-plugin-compat' import { withBuildAssetMissFallthrough } from './scripts/lib/static-asset-fallthrough' import { externalCheckin } from './shared/checkin-external' import { SENTRY_DSN, sentryRelease, sentryReportingEnabled } from './shared/sentry' +// With `serverBundle: false` and no API fallback, @nuxt/icon registers no +// collection names, so it splits `i-vscode-icons-foo` at the first hyphen and +// looks up collection `vscode`. The icon never resolves: SSR ships a bare class +// and logs a warning. Naming every installed collection fixes the split. +const iconCollections = Object.keys(pkg.dependencies) + .filter(name => name.startsWith('@iconify-json/')) + .map(name => name.slice('@iconify-json/'.length)) + const hasSentryAuthToken = Boolean(process.env.SENTRY_AUTH_TOKEN) || existsSync('.env.sentry-build-plugin') @@ -608,6 +617,7 @@ export default defineNuxtConfig({ // inlines just those. The Iconify API fallback is off, so a visitor's browser // never calls api.iconify.design. See docs/ops/bundle-baseline-2026-07-23.md. icon: { + collections: iconCollections, serverBundle: false, fallbackToApi: false, clientBundle: { From 16c9553258180d02ec6dbd8924b49cedbe470f75 Mon Sep 17 00:00:00 2001 From: Harlan GitHub Agent Date: Tue, 29 Sep 2026 14:43:43 +1000 Subject: [PATCH 2/2] refactor(icon): extract collection derivation to shared module --- nuxt.config.ts | 9 ++------ shared/icon-collections.ts | 26 +++++++++++++++++++++++ test/unit/icon-collections.test.ts | 34 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 shared/icon-collections.ts create mode 100644 test/unit/icon-collections.test.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index 02404624..3705788e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -5,15 +5,10 @@ import pkg from './package.json' import { dependencyPluginCompat } from './scripts/lib/dependency-plugin-compat' import { withBuildAssetMissFallthrough } from './scripts/lib/static-asset-fallthrough' import { externalCheckin } from './shared/checkin-external' +import { iconifyCollections } from './shared/icon-collections' import { SENTRY_DSN, sentryRelease, sentryReportingEnabled } from './shared/sentry' -// With `serverBundle: false` and no API fallback, @nuxt/icon registers no -// collection names, so it splits `i-vscode-icons-foo` at the first hyphen and -// looks up collection `vscode`. The icon never resolves: SSR ships a bare class -// and logs a warning. Naming every installed collection fixes the split. -const iconCollections = Object.keys(pkg.dependencies) - .filter(name => name.startsWith('@iconify-json/')) - .map(name => name.slice('@iconify-json/'.length)) +const iconCollections = iconifyCollections(pkg) const hasSentryAuthToken = Boolean(process.env.SENTRY_AUTH_TOKEN) || existsSync('.env.sentry-build-plugin') diff --git a/shared/icon-collections.ts b/shared/icon-collections.ts new file mode 100644 index 00000000..e3b9bb4b --- /dev/null +++ b/shared/icon-collections.ts @@ -0,0 +1,26 @@ +/** + * The collection names `icon.collections` in nuxt.config.ts must list. + * + * With `serverBundle: false` and no API fallback, @nuxt/icon registers no + * collection names, so it splits `i-vscode-icons-foo` at the first hyphen and + * looks up collection `vscode`. The icon never resolves: SSR ships a bare class + * and logs a warning. Naming every installed collection fixes the split. + * + * Only `dependencies` count. An `@iconify-json/*` package that slips into + * `devDependencies` silently drops its collection here, which is exactly the + * failure above, so every icon package this site renders with must stay a + * runtime dependency. + */ + +const ICONIFY_PREFIX = '@iconify-json/' + +export interface PackageManifest { + dependencies?: Record + devDependencies?: Record +} + +export function iconifyCollections(pkg: PackageManifest): string[] { + return Object.keys(pkg.dependencies ?? {}) + .filter(name => name.startsWith(ICONIFY_PREFIX)) + .map(name => name.slice(ICONIFY_PREFIX.length)) +} diff --git a/test/unit/icon-collections.test.ts b/test/unit/icon-collections.test.ts new file mode 100644 index 00000000..ce453f83 --- /dev/null +++ b/test/unit/icon-collections.test.ts @@ -0,0 +1,34 @@ +// @vitest-environment node +import { describe, expect, it } from 'vitest' +import { iconifyCollections } from '#shared/icon-collections' + +describe('iconifyCollections', () => { + it('names every installed collection in dependency order', () => { + expect(iconifyCollections({ + dependencies: { + '@iconify-json/vscode-icons': 'catalog:', + '@iconify-json/simple-icons': 'catalog:', + }, + })).toEqual(['vscode-icons', 'simple-icons']) + }) + + it('ignores dependencies that are not icon collections', () => { + expect(iconifyCollections({ + dependencies: { + 'nuxt': 'catalog:', + '@iconify-json/lucide': 'catalog:', + }, + })).toEqual(['lucide']) + }) + + it('excludes an icon package that only sits in devDependencies', () => { + expect(iconifyCollections({ + dependencies: { '@iconify-json/lucide': 'catalog:' }, + devDependencies: { '@iconify-json/vscode-icons': 'catalog:' }, + })).toEqual(['lucide']) + }) + + it('names nothing when no icon packages are installed', () => { + expect(iconifyCollections({ dependencies: { nuxt: 'catalog:' } })).toEqual([]) + }) +})