Two problems reported for SPA mode (ssr: false) with @bitrix24/b24ui-nuxt@2.9.0. They look independent of each other:
- App initialization crashes inside the b24ui
colors plugin after upgrading Nuxt to 4.5.1.
@nuxt/content / MDC renders nothing in SPA — also reproduces on nuxt@4.4.8, and still happens on 4.5.1 after the crashing line is commented out.
1. colors plugin crashes in SPA on Nuxt >= 4.5.1
Error
[NUXT_E1005] Error caught during app initialization.
╰▶ fix: Check your plugins, app:created, and app:beforeMount hooks for unhandled errors.
injectHead(...).hooks.hookOnce is not a function
/node_modules/.pnpm/@bitrix24+b24ui-nuxt@2.9.0_.../node_modules/@bitrix24/b24ui-nuxt/dist/runtime/plugins/colors.js
The offending call is in the published @bitrix24/b24ui-nuxt@2.9.0, dist/runtime/plugins/colors.js:34
(the dev-server stack frame pointed at 36/38 — Vite's transform shifts the line numbers):
injectHead().hooks.hookOnce("dom:rendered", removeTemporaryColorsStyle);
Root cause — Nuxt 4.5.1 bumps unhead v2 -> v3
| Nuxt |
unhead / @unhead/vue |
4.4.8 |
^2.1.15 |
4.5.1 |
^3.2.3 |
unhead v2 built head.hooks with hookable's createHooks(), producing a full Hookable, which implements hookOnce():
// unhead@2.1.16 — dist/shared/unhead.CaI5ZD4O.mjs
import { createHooks } from 'hookable'
const hooks = createHooks()
unhead v3 builds them from HookableCore instead:
// unhead@3.2.3 — dist/shared/unhead.Bm4Y6XQI.mjs
import { HookableCore } from 'hookable'
function createHooks(hooks) {
const instance = new HookableCore()
for (const key in hooks || {}) {
instance.hook(key, hooks[key])
}
return instance
}
And HookableCore (hookable@6) exposes only three methods — no hookOnce():
declare class HookableCore<...> {
hook<NameT>(name: NameT, fn: InferCallback<HooksT, NameT>): () => void
removeHook<NameT>(name: NameT, function_: InferCallback<HooksT, NameT>): void
callHook<NameT>(name: NameT, ...args): Promise<any> | void
}
So head.hooks.hookOnce is undefined on unhead v3 -> TypeError.
Why SPA only
The call sits in the SPA-only branch of the plugin:
if (import.meta.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
// ...
injectHead().hooks.hookOnce("dom:rendered", removeTemporaryColorsStyle);
}
With SSR enabled, payload.serverRendered is true, the branch never runs, and nothing breaks. That is why this only shows up with ssr: false.
Status — fixed on main, but not released
main already carries a version-agnostic fix in src/runtime/plugins/colors.ts:
// `hookOnce` is only available on unhead v2's `Hookable`. In v3 `hooks` is a `HookableCore`
// that exposes `hook` only, so self-unhook to keep the once semantics across both versions.
const head = injectHead()
const unhook = head.hooks?.hook('dom:rendered', () => {
removeTemporaryColorsStyle()
unhook?.()
})
This is correct on both majors, since HookableCore.hook() returns an unhook function (() => void).
However, the latest version published to npm is 2.9.0, and it still ships hookOnce — so every user on the current release hits this. A release is needed to ship the fix.
Workaround until a release is cut
Pin Nuxt to 4.4.8 (keeps unhead on v2, where hookOnce exists).
Reproduction
- Nuxt app with
ssr: false
@bitrix24/b24ui-nuxt@2.9.0
- Upgrade
nuxt to 4.5.1
- Load the app ->
NUXT_E1005 on init
2. @nuxt/content / MDC renders nothing in SPA
Reported separately, needs reproduction — it does not look related to the colors plugin:
- With
@nuxt/content installed on nuxt@4.4.8 — where problem 1 does not occur, because unhead v2 still has hookOnce — MDC does not render at all.
- On
nuxt@4.5.1, after commenting out the crashing line in the plugin, MDC still renders nothing in SPA.
Since it reproduces on both Nuxt versions and survives neutralizing the plugin, the two problems should be treated as distinct.
To check during analysis
@nuxt/content v3 is SQLite-backed. With ssr: false the content database has to be served to the client (WASM SQLite connector). Worth confirming the app's content config first, before digging into b24ui — this may be an app-side setup issue rather than a b24ui bug. (Hypothesis, not verified.)
- b24ui registers Prose / content components only when the module is detected — see
src/module.ts:231-246:
if (options.prose || options.mdc || options.content || hasNuxtModule('@nuxtjs/mdc') || hasNuxtModule('@nuxt/content')) {
// Prose* components
}
if (options.content || hasNuxtModule('@nuxt/content')) {
// B24* content components
}
Confirm the components are actually registered in the failing app; if module detection fails, forcing b24ui: { mdc: true, content: true } in nuxt.config is a quick way to test.
- Narrow down where it breaks: does the content query return no documents, or does the query succeed and
ContentRenderer output nothing?
Environment
@bitrix24/b24ui-nuxt 2.9.0
nuxt 4.5.1 (crash) / 4.4.8 (MDC)
- SPA mode,
ssr: false
- pnpm
Two problems reported for SPA mode (
ssr: false) with@bitrix24/b24ui-nuxt@2.9.0. They look independent of each other:colorsplugin after upgrading Nuxt to4.5.1.@nuxt/content/ MDC renders nothing in SPA — also reproduces onnuxt@4.4.8, and still happens on4.5.1after the crashing line is commented out.1.
colorsplugin crashes in SPA on Nuxt >= 4.5.1Error
The offending call is in the published
@bitrix24/b24ui-nuxt@2.9.0,dist/runtime/plugins/colors.js:34(the dev-server stack frame pointed at 36/38 — Vite's transform shifts the line numbers):
Root cause — Nuxt 4.5.1 bumps unhead v2 -> v3
unhead/@unhead/vue4.4.8^2.1.154.5.1^3.2.3unhead v2 built
head.hookswith hookable'screateHooks(), producing a fullHookable, which implementshookOnce():unhead v3 builds them from
HookableCoreinstead:And
HookableCore(hookable@6) exposes only three methods — nohookOnce():So
head.hooks.hookOnceisundefinedon unhead v3 ->TypeError.Why SPA only
The call sits in the SPA-only branch of the plugin:
With SSR enabled,
payload.serverRenderedistrue, the branch never runs, and nothing breaks. That is why this only shows up withssr: false.Status — fixed on
main, but not releasedmainalready carries a version-agnostic fix insrc/runtime/plugins/colors.ts:This is correct on both majors, since
HookableCore.hook()returns an unhook function (() => void).However, the latest version published to npm is
2.9.0, and it still shipshookOnce— so every user on the current release hits this. A release is needed to ship the fix.Workaround until a release is cut
Pin Nuxt to
4.4.8(keeps unhead on v2, wherehookOnceexists).Reproduction
ssr: false@bitrix24/b24ui-nuxt@2.9.0nuxtto4.5.1NUXT_E1005on init2.
@nuxt/content/ MDC renders nothing in SPAReported separately, needs reproduction — it does not look related to the
colorsplugin:@nuxt/contentinstalled onnuxt@4.4.8— where problem 1 does not occur, because unhead v2 still hashookOnce— MDC does not render at all.nuxt@4.5.1, after commenting out the crashing line in the plugin, MDC still renders nothing in SPA.Since it reproduces on both Nuxt versions and survives neutralizing the plugin, the two problems should be treated as distinct.
To check during analysis
@nuxt/contentv3 is SQLite-backed. Withssr: falsethe content database has to be served to the client (WASM SQLite connector). Worth confirming the app'scontentconfig first, before digging into b24ui — this may be an app-side setup issue rather than a b24ui bug. (Hypothesis, not verified.)src/module.ts:231-246:b24ui: { mdc: true, content: true }innuxt.configis a quick way to test.ContentRendereroutput nothing?Environment
@bitrix24/b24ui-nuxt2.9.0nuxt4.5.1(crash) /4.4.8(MDC)ssr: false