Skip to content

@nuxt/hints does not work under a non-root app.baseURL (telemetry endpoint, client $fetch, and devtools panel all ignore the base) #361

Description

@OliverRC

Summary

When app.baseURL is not /, @nuxt/hints breaks in several independent ways, because it registers its server endpoints and serves its devtools client at bare paths while the browser requests them at base-prefixed paths.

PR #322 (issue #321), shipped in v1.1.0, only base-prefixed the devtools iframe src — it fixed the URL the iframe requests but not the middleware that serves it, and didn't touch the telemetry endpoint at all. So the module is still broken in 1.1.3:

  • the lazy-load / hydration / html-validate telemetry POSTs never reach their handler,
  • those requests fall through to the host app's SSR renderer, producing [Vue Router warn]: No match found … warnings,
  • and the devtools Hints panel renders its own 404 even once it loads, because its base is baked into the shipped client bundle.

A grep over dist/module.mjs + dist/runtime/ confirms the only app.baseURL reference in the whole module/runtime of 1.1.3 is the single line added by #322.

Reproduction

Minimal repro repo (vanilla Nuxt app; only non-default config is adding @nuxt/hints and a non-root app.baseURL):

https://github.com/OliverRC/nuxt-hints-base-url-repro

git clone https://github.com/OliverRC/nuxt-hints-base-url-repro
cd nuxt-hints-base-url-repro
npm install
npm run dev

Load the app (served under the base, e.g. http://localhost:3000/repro/) and open the Nuxt DevTools Hints tab.

Environment

  • @nuxt/hints: 1.1.3 (latest / dist-tags.latest)
  • nuxt: 4.4.8 (latest)
  • Reproducible in nuxt dev

Expected

The telemetry endpoint and the devtools UI resolve under app.baseURL; the Hints panel routes normally; no Vue Router warnings.

Actual

Warnings in the console / server log (requests miss the hints handlers and fall through to the host SSR renderer):

[Vue Router warn]: No match found for location with path "/__nuxt_hints/lazy-load"
[Vue Router warn]: No match found for location with path "/__nuxt_hints/hydration"
[Vue Router warn]: No match found for location with path "/__nuxt-hints"

And the Hints panel renders its own 404 on first load (recovers if you navigate to the panel's home).

Deterministic check (dev server on port 3000, base /repro/):

curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/__nuxt_hints/hydration        # 200  bare works
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/repro/__nuxt_hints/hydration  # 404  base-prefixed misses
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/__nuxt-hints                   # 200  bare works
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/repro/__nuxt-hints             # 404  base-prefixed misses

Set app.baseURL back to "/" and every symptom disappears.


Root causes

(1) Telemetry server handler is registered at a bare route

dist/module.mjs (setupDevToolsUI):

addDevServerHandler({ route: "/__nuxt_hints", handler: createHintsRouter().handler })

Registered at bare /__nuxt_hints, but the client sends to ${app.baseURL}/__nuxt_hints/... (see (2)). The base is never applied here.

(2) Client $fetch uses a relative path, which Nuxt prepends with app.baseURL

dist/runtime/lazy-load/plugin.client.js (same pattern for hydration / html-validate):

$fetch(LAZY_LOAD_ROUTE, { method: "POST", body: payload })
// LAZY_LOAD_ROUTE = "/__nuxt_hints/lazy-load"

Nuxt's $fetch prepends app.baseURL, so the real request is /repro/__nuxt_hints/lazy-load — which the bare handler in (1) never matches. The POST falls through to the host SSR renderer → the No match found for "/__nuxt_hints/lazy-load" warning.

(3) Devtools sirv mount is bare, contradicting the #322 iframe fix

dist/module.mjs (setupDevToolsUI):

// iframe src — base-prefixed by #322:
src: joinURL(nuxt.options.app?.baseURL || "/", DEVTOOLS_UI_ROUTE)   // → /repro/__nuxt-hints
// ...but the client is still served at the bare path:
server.middlewares.use(DEVTOOLS_UI_ROUTE, sirv(clientPath, { dev: true, single: true }))   // → /__nuxt-hints

After #322 the iframe requests /repro/__nuxt-hints, which nothing serves → the No match found for "/__nuxt-hints" warning. (Before #322 the iframe pointed at bare /__nuxt-hints, which sirv does serve — so #322 introduced this particular symptom by fixing only half of it.)

(4) The prebuilt devtools client hardcodes its own baseURL, so the panel is unusable under a non-root host base

This is the most fundamental one, and it remains even if (3) is fully fixed (iframe src and sirv mount both base-prefixed). The shipped client bundle bakes its base into the served HTML:

window.__NUXT__.config.app.baseURL = "/__nuxt-hints"

So when the panel is loaded at <host-base>/__nuxt-hints (e.g. /repro/__nuxt-hints), its internal Vue Router strips /__nuxt-hints from /repro/__nuxt-hints, fails to match, and renders the panel's own 404 on first load (navigating to the panel's home recovers it). The devtools client needs to be built/served with — or be agnostic to — the host's app.baseURL; otherwise the Hints tab cannot function under any non-root base.

Additional note (turns the mismatches into visible warnings)

The POST/DELETE handlers (e.g. dist/runtime/lazy-load/handlers.js) call setResponseStatus(...) and then return undefined:

export const postHandler = defineEventHandler(async (event) => {
  // ...validate, store...
  setResponseStatus(event, 201);   // no return value
});

In h3 an undefined return is treated as pass-through middleware, so even once routing is fixed the request continues down the stack to the host's SSR renderer (which is what surfaces the Vue Router warning) instead of terminating. Returning a value (e.g. null) would stop the chain.


Suggested fix

The module already imports joinURL from ufo and has nuxt.options.app.baseURL available:

  • (1) addDevServerHandler({ route: withBase("/__nuxt_hints", base), ... })
  • (2) have the client derive its endpoint from the runtime app.baseURL (or issue a base-agnostic request)
  • (3) server.middlewares.use(withBase(DEVTOOLS_UI_ROUTE, base), sirv(...)) to match the (already base-prefixed) iframe src
  • (4) build/serve the devtools client with the host's app.baseURL, or make its router/asset base relative to where it's mounted
  • have the POST/DELETE handlers return a terminating value instead of undefined

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions