Skip to content

resolve dev-mode Nuxt entry via Vite @fs absolute path#191

Open
marconett wants to merge 1 commit into
drunomics:1.xfrom
marconett:nuxt-dev
Open

resolve dev-mode Nuxt entry via Vite @fs absolute path#191
marconett wants to merge 1 commit into
drunomics:1.xfrom
marconett:nuxt-dev

Conversation

@marconett

Copy link
Copy Markdown

Problem

In dev mode (nuxt dev), the app-loader fails to load the Nuxt app entry in cross-origin previews (Nuxt frontend embedded in Drupal Canvas):

GET /nuxt-component-preview/app-loader.js   → 200
GET /_nuxt/node_modules/nuxt/dist/app/entry.js   → 404 (see screenshot)
Untitled

The Vite dev server actually serves the entry from:

/_nuxt/@fs/<project>/node_modules/nuxt/dist/app/entry.async.js

nuxt build / nuxt generate are unaffected (they read the real chunk from build:manifest).

Root cause

The if (nuxt.options.dev) branch in src/module.ts synthesizes a build-style entry URL instead of using the form Vite actually serves. Two things are wrong:

  1. Path. It strips appDir to a path relative to rootDir:

    const relativeAppDir = appDir.startsWith(rootDir) ? appDir.slice(rootDir.length + 1) : appDir
    resolvedEntryPath = `/_nuxt/${relativeAppDir}/entry.js` + ...

    The entry lives in node_modules/nuxt/dist/app, which Vite does not serve from a root-relative URL — it serves it via the @fs/<absolute-path> mechanism. For any normal single-project frontend (where appDir is
    under rootDir), this produces /_nuxt/node_modules/nuxt/dist/app/entry.js, which 404s.

    This is also why the playground masks the bug: there appDir (<repo>/node_modules/...) is not under rootDir (<repo>/playground), so the startsWith branch is skipped and the leftover absolute path happens
    to be served by Vite. Real consumer projects don't have that layout.

  2. Filename. Dev uses the async entry. @nuxt/vite-builder resolves useAsyncEntry = experimental.asyncEntry || nuxt.options.dev, so in dev the entry is always entry.async.js, not entry.js.

Fix

Build the dev entry URL from the entry's absolute filesystem path using Vite's canonical @fs/ prefix, and use the async entry filename.

Result: /_nuxt/@fs/<project>/node_modules/nuxt/dist/app/entry.async.js, which matches what the dev server serves.

Scope / safety

  • Change is confined to the if (nuxt.options.dev) branch. The !nuxt.options.dev path (build:manifest, used by both nuxt build and nuxt generate) is untouched.
  • baseURL / buildAssetsDir are now honored for non-default configs.

Verification

  • Playground nuxt dev: the app-loader now emits the @fs URL and it returns 200 (previously the synthesized path; the playground's monorepo layout hid the breakage).
  • Confirmed in a real cross-origin setup (separate-origin Drupal Canvas backend + nuxt dev frontend): preview now renders; entry request returns 200 instead of 404.
  • nuxt build + node .output/server/index.mjs: unchanged, still renders previews.

Acknowledgement

This PR was written with the help of AI, tho I manually verified and tested it within my nuxt (v4.4.6) project.

Copilot AI review requested due to automatic review settings June 9, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates how the module computes the Nuxt app entry URL in dev mode so it reliably resolves under Vite (including when the entry lives under node_modules) and uses the correct async entry file.

Changes:

  • Switches dev entry URL generation to Vite’s @fs/<absolute-path> mechanism.
  • Accounts for baseURL and buildAssetsDir when constructing the entry URL.
  • Selects entry.async.js in dev (matching Nuxt Vite behavior), with buildId cache-busting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/module.ts
Comment on lines 145 to +151
const appDir = nuxt.options.appDir.replace(/\/+$/, '')
const rootDir = nuxt.options.rootDir.replace(/\/+$/, '')
const relativeAppDir = appDir.startsWith(rootDir)
? appDir.slice(rootDir.length + 1)
: appDir
const baseURL = (nuxt.options.app.baseURL || '/').replace(/\/+$/, '')
const assetsDir = (nuxt.options.app.buildAssetsDir || '/_nuxt/').replace(/^\/+|\/+$/g, '')
const useAsyncEntry = nuxt.options.experimental?.asyncEntry || nuxt.options.dev
const entryName = useAsyncEntry ? 'entry.async' : 'entry'
const buildId = nuxt.options.appConfig?.nuxt?.buildId
resolvedEntryPath = `/_nuxt/${relativeAppDir}/entry.js` + (buildId ? `?v=${buildId}` : '')
resolvedEntryPath = `${baseURL}/${assetsDir}/@fs${appDir}/${entryName}.js` + (buildId ? `?v=${buildId}` : '')
Comment thread src/module.ts
Comment on lines +148 to +149
const useAsyncEntry = nuxt.options.experimental?.asyncEntry || nuxt.options.dev
const entryName = useAsyncEntry ? 'entry.async' : 'entry'
Comment thread src/module.ts
Comment on lines +146 to +147
const baseURL = (nuxt.options.app.baseURL || '/').replace(/\/+$/, '')
const assetsDir = (nuxt.options.app.buildAssetsDir || '/_nuxt/').replace(/^\/+|\/+$/g, '')
Comment thread src/module.ts
const entryName = useAsyncEntry ? 'entry.async' : 'entry'
const buildId = nuxt.options.appConfig?.nuxt?.buildId
resolvedEntryPath = `/_nuxt/${relativeAppDir}/entry.js` + (buildId ? `?v=${buildId}` : '')
resolvedEntryPath = `${baseURL}/${assetsDir}/@fs${appDir}/${entryName}.js` + (buildId ? `?v=${buildId}` : '')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants