Skip to content

fix: resolve dev server deadlock when starting embedded Storybook#1069

Open
srcdev wants to merge 3 commits into
nuxt-modules:mainfrom
srcdev:fix/993-embedded-startup-deadlock
Open

fix: resolve dev server deadlock when starting embedded Storybook#1069
srcdev wants to merge 3 commits into
nuxt-modules:mainfrom
srcdev:fix/993-embedded-startup-deadlock

Conversation

@srcdev

@srcdev srcdev commented Jun 12, 2026

Copy link
Copy Markdown

🔗 Linked issue

Refs #993 — fixes the dev-server deadlock that bricks the Nuxt app. (Deliberately not "Resolves": the residual /_nuxt EAGAIN proxy-target errors from that issue surface once the deadlock is gone; a follow-up PR adapting #994's devServer.url fix will address those.)

📚 Description

As offered in #993 (comment), this fixes the startup deadlock introduced in #981:

  • @nuxtjs/storybook starts Storybook inside Nuxt's listen hook and awaits it, blocking the boot pipeline (Vite middleware never attaches).
  • @storybook-vue/nuxt's loadNuxtViteConfig detects the already-running Nuxt via tryUseNuxt() and waits for its vite:configResolved event — which can only fire once boot proceeds.

Circular wait. With current nuxt/cli every nuxt dev with the module enabled hangs on all requests forever; on older versions it surfaced as the EAGAIN proxy errors reported in #993. Storybook's manager UI still binds its port (it starts before the preview builder hits the deadlocked promise), which disguised the boot hang as a proxy problem.

The fix:

  • module: capture the resolved client Vite config during module setup — the only moment registration is guaranteed to precede the event — and share it with the framework package via Symbol.for('@storybook-vue/nuxt:vite-config-promise') on the shared Nuxt instance
  • module: start Storybook from the listen hook without awaiting it (failures still surface via the logger)
  • addon: loadNuxtViteConfig consumes the captured promise instead of registering a hook that may already have missed the event; the previous hook registration remains as a fallback for embedding without the module
  • module: pass ci: true to buildDevStandalone so a busy port can never block the (quiet-mode) startup on an invisible interactive prompt

Tests:

  • New Playwright coverage for the embedded path (nuxt dev with the module starting Storybook), which was previously untested — the suite only exercised standalone storybook dev. Asserts the Nuxt app responds and a story renders, across Chromium/Firefox/WebKit (15/15 with the existing tests).
  • test/module-integration.spec.ts updated: it previously asserted the deadlocking await pattern as correct.
  • The playground now loads the module from the built package rather than raw TS source — embedded startup hangs under jiti's TS transform even with this fix (dev-rig-only; users always get dist). I'll file that separately, along with an embedded-mode docs-index gap found during testing.

Verified against the reproduction from #993 (braedenfoster/storybook-test-nuxt-4) with packed tarballs: app responds immediately, embedded Storybook fully boots (➜ Storybook: http://localhost:6006/), stories render.

(investigated and fixed with Claude Fable 5 assistance)

The module awaited setupStorybook() inside Nuxt's listen hook, while
@storybook-vue/nuxt's loadNuxtViteConfig() waited for the running Nuxt
instance's vite:configResolved event - which cannot fire until the boot
pipeline, blocked by that same listen hook, proceeds. Every `nuxt dev`
with the module enabled hung on all requests.

- module: capture the client Vite config during setup (before the event
  can fire) and share it with the framework via a well-known symbol
- module: start Storybook from the listen hook without awaiting it
- addon: consume the captured config instead of registering a late hook
- start Storybook with ci: true so a busy port cannot block on an
  interactive prompt
- e2e: cover the embedded (`nuxt dev`) path, previously untested

Refs nuxt-modules#993

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 12, 2026

Copy link
Copy Markdown

👷 Deploy request for nuxt-storybook pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 127301c

The playground now loads @nuxtjs/storybook from the built package, so
`nuxt prepare` during install needs at least a stub dist to exist.
@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nuxtjs/storybook@1069
npm i https://pkg.pr.new/@storybook-vue/nuxt@1069

commit: 127301c

vite's mergeConfig reuses nested objects by reference when a key exists
on only one side of the merge. In embedded mode the merge input is the
running app's resolved config, so writing noDiscovery and pushing
include entries poisoned the app's own dep optimizer. Clone the objects
this function writes to.

Verified against the nuxt-modules#993 reproduction with an instrumented module:
optimizeDeps.noDiscovery no longer flips on the live config after
Storybook starts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@srcdev

srcdev commented Jun 12, 2026

Copy link
Copy Markdown
Author

Update: pushed two follow-up commits — a CI fix (the playground now loads the module from the built package, so the root prepare script stub-builds packages first) and a fix for a config-mutation bug found while verifying against the #993 reproduction (mergeConfig shares nested references, so the addon was writing optimizeDeps settings into the running app's live config).

Full transparency on scope: during that verification I also found a deeper, pre-existing issue in how embedded mode shares the app's live Vite plugin instances with Storybook's server — filed as #1072 with full forensics. It affects plugin-heavy apps once this PR makes embedded mode bootable. This PR remains a strict improvement (embedded mode is 100% deadlocked without it), and the new issue documents the remaining work.

(investigated with Claude Fable 5 assistance)

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