router: normalize base to always end with trailing slash#2
Merged
nothing-stops-this-train merged 1 commit intoMay 22, 2026
Merged
Conversation
Nuxt's getLocation helper (in .nuxt/utils.js) does
`base.slice(0, -1)` on the configured base, with a comment
explicitly noting "consideration is base is normalized with trailing
slash". When BITCART_ADMIN_ROOTPATH is set to "/admin" (no trailing
slash — the form deploy.sh pins, and the form Nuxt accepts
everywhere else), that slice corrupts the base to "/admi". The
initial path strip then misfires: window.location.pathname
"/admin/<route>" startsWith("/admi") matches, but slicing by 5
produces "n/<route>" — a non-route. router.resolve returns
matched=0, Nuxt's navigation logic sets a 404 on the page, and
the err state is sticky for nested URLs.
Single-segment URLs (`/admin/`, `/admin/stores`) happen to recover
because the corrupted-then-resolved path "/" still matches Nuxt's
auto-generated home route, which clears the err state on the
subsequent Vue Router navigation. But multi-segment URLs like
`/admin/plugins/<plugin>` resolve to paths that match no route at
all, so the err is never cleared — the user sees a "404 Not Found"
page on every reload, even though the actual route is correctly
registered and the page renders fine server-side.
This was reproducible 100% of the time on `/admin/plugins/liquidityhelper`
with a headless-Chromium test against an `/admin`-rooted deploy.
After this patch, fresh URL-bar navigation lands on the right page
without a 404 flash.
We normalize at the router-construction point (not the env-var
parser or the publicRuntimeConfig binding) so that any other code
reading config.ROOTPATH continues to see the unmodified value —
which keeps URL-construction code like `\${config.ROOTPATH}/api/...`
from producing accidental "/admin//api/..." double slashes.
This was referenced May 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hard-to-spot bug: when
BITCART_ADMIN_ROOTPATHis set to/admin(no trailing slash), every nested admin URL (/admin/plugins/<x>,/admin/stores/<id>/<x>, etc.) renders Nuxt's "404 Not Found" page on the first paint, even though the route is correctly registered with Vue Router and the page renders fine server-side.Single-segment URLs (
/admin/,/admin/stores,/admin/plugins) survive because their corrupted initial path coincidentally resolves to the home route, which clears the sticky err state on the next Vue Router navigation. Multi-segment URLs never recover.Root cause
.nuxt/utils.js'sgetLocationhelper doesbase.slice(0, -1), with the comment "consideration is base is normalized with trailing slash". When base is/admin(no trailing slash), the slice corrupts it to/admi. The subsequentpath.startsWith(slicedBase)matches the leading/admiof/admin/<x>but slices the path by 5 chars, producing strings liken/<x>— non-routes.router.resolvereturns matched=0, Nuxt's client.js navigation hook setsapp.context.error({statusCode: 404, message: "This page could not be found"}), and the err state never gets cleared.Headless-Chromium reproducer (before patch)
Polling
$nuxt.nuxt.errand$route.pathfrom page load (cookies seeded so we're logged in):After patch, the same probe shows:
Patch
Normalize
config.ROOTPATHto always end with/at the router-construction point. Three reasons to do it here (rather than at the env-var parse site or in publicRuntimeConfig):config.ROOTPATH— URL-construction code like${config.ROOTPATH}/api/foocontinues to see the unmodified value (e.g./admin), so it doesn't accidentally produce double slashes like/admin//api/foo.getLocationis ever fixed upstream.Normalization table
/admin/admin//admin//admin///""/undefined/Test plan
/admin/plugins/<any-plugin>no longer renders "404 Not Found"./admin/,/admin/stores, etc.) still load correctly.BITCART_ADMIN_ROOTPATHunset →"/"), behavior is unchanged — base stays/.Note re: PR #1
This supersedes the diagnostic in PR #1. That PR addressed a symptom (async chunk 404 from missing
/admin/prefix onpublicPath); this PR addresses the underlying cause of the visible "404 Not Found" page. The publicPath issue still happens but turns out to be cosmetic — it produces a single noisy console message for the workbox chunk, and doesn't break route resolution.