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
Summary
When
app.baseURLis not/,@nuxt/hintsbreaks 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:[Vue Router warn]: No match found …warnings,A
grepoverdist/module.mjs+dist/runtime/confirms the onlyapp.baseURLreference 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/hintsand a non-rootapp.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 devLoad 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)nuxt devExpected
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):
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/):Set
app.baseURLback to"/"and every symptom disappears.Root causes
(1) Telemetry server handler is registered at a bare route
dist/module.mjs(setupDevToolsUI):Registered at bare
/__nuxt_hints, but the client sends to${app.baseURL}/__nuxt_hints/...(see (2)). The base is never applied here.(2) Client
$fetchuses a relative path, which Nuxt prepends withapp.baseURLdist/runtime/lazy-load/plugin.client.js(same pattern for hydration / html-validate):Nuxt's
$fetchprependsapp.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 → theNo match found for "/__nuxt_hints/lazy-load"warning.(3) Devtools
sirvmount is bare, contradicting the #322 iframe fixdist/module.mjs(setupDevToolsUI):After #322 the iframe requests
/repro/__nuxt-hints, which nothing serves → theNo match found for "/__nuxt-hints"warning. (Before #322 the iframe pointed at bare/__nuxt-hints, whichsirvdoes 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 baseThis is the most fundamental one, and it remains even if (3) is fully fixed (iframe
srcandsirvmount both base-prefixed). The shipped client bundle bakes its base into the served HTML:So when the panel is loaded at
<host-base>/__nuxt-hints(e.g./repro/__nuxt-hints), its internal Vue Router strips/__nuxt-hintsfrom/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'sapp.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) callsetResponseStatus(...)and then returnundefined:In h3 an
undefinedreturn 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
joinURLfromufoand hasnuxt.options.app.baseURLavailable:addDevServerHandler({ route: withBase("/__nuxt_hints", base), ... })app.baseURL(or issue a base-agnostic request)server.middlewares.use(withBase(DEVTOOLS_UI_ROUTE, base), sirv(...))to match the (already base-prefixed) iframesrcapp.baseURL, or make its router/asset base relative to where it's mountedundefined