- ABP Framework version: v10.6.0 (
Volo.Abp.AdminConsole10.6.0) - UI Type: React
- Database System: EF Core (SQL Server)
- Tiered (for MVC) or Auth Server Separated (for Angular): no — single host (API + OpenIddict auth server)
- Exception message and full stack trace: none. There is no exception. The console returns HTTP 200 and renders its own "404 Page not found" screen on every route, including its root.
- Steps to reproduce the issue: below.
Our deployment, because it is the whole context
We deploy the ABP host as an IIS virtual application under a path prefix, not at the root of a
host name. So the application's Request.PathBase is a multi-segment path — four segments deep in
our case — and every URL the application composes has to carry it.
This is a constraint, not a preference. In our organisation, creating a new domain or subdomain requires an approval process that is not realistically available to a project at this stage. Sharing an existing host name under a path prefix is the only deployment shape open to us, and we expect that is true of a great many corporate ABP deployments.
To be precise about what we are not doing, since it changes what you would look for:
- We are not behind a reverse proxy that rewrites paths. IIS hosts the application directly, in process, as a child application. (Cloudflare fronts the site for users outside the company network, but it is pass-through and has no bearing on path composition or on anything below.)
- We are not asking ASP.NET Core to do anything unusual.
UsePathBaseis standard, IIS sets it automatically for a virtual application, and every other part of the application handles it correctly.
Everything below was measured against the shipped 10.6.0 package. Nothing in it depends on our
environment, and the examples use a generic /sub/backend prefix throughout.
Summary
Volo.Abp.AdminConsole 10.6.0 composes URLs for itself that ignore Request.PathBase, in six
distinct places. The console is served correctly under a prefix — every one of these URLs returns
200 when requested with the prefix — but the URLs the console builds for its own use omit it, so
under any non-root PathBase the console is unusable.
| Where | What is hardcoded or mis-derived | Consequence |
|---|---|---|
| main-*.js | basepath: "/admin-console" — the TanStack Router base | The router cannot strip its own base off the incoming pathname, matches no route, renders "404 Page not found", and rewrites the address bar to /admin-console/<the whole real path> — a URL that 404s for real if the user reloads |
| main-*.js | /admin-console/ + name — the lazy-chunk base | All code-split chunks resolve at the site root |
| main-*.css | url(/admin-console/assets/fa-*.woff2), nine of them | Every Font Awesome face 404s, so every icon in the console is missing |
| admin-console/api/config | apis.default.url, emitted as "" | Resolves to the root-absolute /api, so ABP's own calls (application-configuration, permissions, localization, current user) leave the application |
| admin-console/api/config | oAuthConfig.issuer, built from the request as scheme + host only | OIDC discovery is sought at the site root, where it 404s, so signing in to the console is impossible |
| admin-console/api/config | themeOverrideCssPath, injected into the DOM after the shell is served | Requested at the site root; 404 |
The address bar is the signature, and it is worth recognising on sight — the console's compiled base, followed by the entire real path:
https://example.org/admin-console/sub/backend/admin-console/account/manage
Relationship to our earlier report #10823
We filed question #10823 on 2026-07-28 against 10.5.0. It is now closed, so this is a new question rather than a follow-up. You should not need to read it — here is what it said and where it was wrong.
#10823 reported that the console's shell HTML emits <base href> and asset URLs that ignore
Request.PathBase, so the console loads blank. That much was accurate, and it is still unfixed in
10.6.0. But its central claim was:
routing works; only URL emission is broken
That is true of server-side routing only. It is not true of the shipped bundle, and the difference is the entire user-visible failure.
This matters practically: we worked around the shell exactly as #10823 described. The result was a console that loads and is still unusable — all assets arrive, the SPA boots, and then it renders its own 404 on every route. If #10823 is ever actioned, fixing only what it described will not fix the console.
To be straightforward about how it was found: this is a pre-release project with no end users, and it had been deployed this way in two environments simply because nobody had exercised the console there until we got round to testing that part of the application. We are not claiming any user impact — we are reporting it because the next consumer to try this will hit it, and because the fix on your side looks small.
A note on the code below: it is minified
We are reading your minified production bundle, and we want to be upfront about that — the identifiers below are the minifier's, not yours.
The package ships no source maps: 183 embedded resources, of which 165 are .js, and not one
.map file; the bundle contains no //# sourceMappingURL comment either, and main-*.js is
4.6 MB across 67 lines. So minified output is the only view of this code available to a consumer.
Two consequences worth your attention:
- Names like
Br,Yr,Krare build-assigned and will differ in your source and in any later build. We quote them because they are the only handles we have. Mapping them back to the real function names should be quick for you, and we would welcome the corrected names in your reply. - This is also why our workaround is fragile — see "What we are doing instead" below.
If un-minified sources for the Admin Console SPA are available to license holders through some channel we have missed, we would be glad to be pointed at it; it would make this report, and any future one, considerably more precise.
The fix is one line, and it is your own function
A function in the bundle already computes the correct deployed base, which is precisely why
api/config and api/modules resolve while routing does not. Quoted verbatim (minified names):
var Br = `/admin-console`;
function Yr(){
if (typeof window > `u`) return Br;
let e = window.location.pathname || ``,
t = e.toLowerCase().indexOf(Br);
return t < 0 ? Br : Jr(e.slice(0, t + 14)) ?? Br; // 14 === "/admin-console".length
}
Given /sub/backend/admin-console/account/manage, Yr() returns /sub/backend/admin-console —
exactly what basepath should be. But the router is created with the constant instead:
E({ routeTree: pw.addChildren(t), basepath: `/admin-console` }) // ← should be Yr()
So the primary fix is basepath: Yr(). The lazy-chunk base is the same one-liner:
var lC = function(e){ return `/admin-console/` + e } // ← should be `${Yr()}/`
A consumer cannot get at this by rewriting Br, and that is worth stating plainly, because it
is the obvious workaround and it is a trap: Yr() hardcodes 14 as the length of Br. Prefix Br
and indexOf returns 0, the slice yields the first fourteen characters of the deployment prefix,
and the API base becomes garbage — breaking api/config and api/modules, which currently work.
apis.default.url deserves its own note
api/config emits "apis": { "default": { "url": "" } }. The consuming chain is:
function Ur(){ return R?.apis?.default?.url ?? `/api` ?? Fr.apiUrl }
function Wr(e){ return e.endsWith(`/`) ? e.slice(0,-1) : e }
function Gr(e){ return (e.startsWith(`http://`) || e.startsWith(`https://`)) && !e.includes(`{{`)
? Wr(e) + `/api`
: e.startsWith(`/`) ? e : Wr(e) + `/api` }
function Kr(){ return Gr(Ur()) }
Evaluated directly (those functions, verbatim, with R stubbed):
| apis.default.url | Kr() — the API base actually used |
|---|---|
| "" (as shipped) | /api |
| key absent | /api |
| /sub/backend (a path) | /sub/backend — no /api appended |
| https://example.org/sub/backend | https://example.org/sub/backend/api |
Two things follow:
- The empty string is not really the bug — the
?? "/api"fallback produces the same broken value. The defect is that the default API base is the root-absolute literal/api, which isPathBase-unaware by construction. And you already have the correct derivation nearby:Xr()(Jr(qr(oAuthConfig.redirectUri)) ?? Yr()) andZr()(`${Xr()}/api`).Kr()falling back toZr()instead of/apiwould fix this surface with no new logic. Gr()returns a root-relative value unchanged, without appending/api. So a consumer who setsapis.default.urlto thePathBase— the natural thing to try — gets an API base with no/apisegment, and every request on the My account page 404s. Only an absolute URL works. If that asymmetry is intentional it is at least worth documenting; we found it by measurement.
Steps to reproduce
- Create an ABP 10.6.0 React solution whose host references
AbpAdminConsoleModule, withAdminConsole:IsEnabled=true. - In
Program.cs, addapp.UsePathBase("/sub/backend");immediately beforeawait app.InitializeApplicationAsync();— the same position IIS in-process hosting puts it. (Use a multi-segment prefix; a single segment can mask which component is at fault.) - Work around the shell defect from #10823 so the assets load at all. Without this you get the blank page of #10823 and never reach this bug.
- Sign in and open
GET /sub/backend/admin-consolein a browser. - Observe: HTTP 200, the bundle boots, the address bar changes to
/admin-console/sub/backend/admin-console, and the page reads "404 Page not found". Every console route behaves the same way, the root included. The network log additionally shows 404s for nine.woff2faces and for/api/abp/application-configurationat the site root.
The three code-side surfaces need no deployment at all — the SPA is embedded in the shipped assembly, so this can be confirmed straight from the package:
$dll = "$env:USERPROFILE\.nuget\packages\volo.abp.adminconsole\10.6.0\lib\net10.0\Volo.Abp.AdminConsole.dll"
$asm = [System.Reflection.Assembly]::LoadFrom($dll)
$out = "$env:TEMP\abp-admin-console"
New-Item -ItemType Directory -Force -Path $out | Out-Null
foreach ($r in 'main-DYvBIgeW.js', 'main-Bb5D3zyU.css') {
$s = $asm.GetManifestResourceStream("Volo.Abp.AdminConsole.wwwroot.admin_console.assets.$r")
$f = [System.IO.File]::Create("$out\$r"); $s.CopyTo($f); $f.Close(); $s.Close()
}
Select-String -Path "$out\main-DYvBIgeW.js" -Pattern '/admin-console' -AllMatches |
ForEach-Object { $_.Matches.Count } # 3: Br, the chunk base, basepath
Select-String -Path "$out\main-Bb5D3zyU.css" -Pattern 'url\(/admin-console/' -AllMatches |
ForEach-Object { $_.Matches.Count } # 9
Expected behaviour
The Admin Console works when the host is deployed under a path prefix: the router matches routes
beneath the deployed base, lazy chunks and fonts load from beneath it, and api/config advertises
an issuer and API base that carry it.
Actual behaviour
The console is unusable under any non-root PathBase. Fixing the shell alone converts a blank page
into a console that renders "404 Page not found" on every route.
Scope, so this is not mistaken for our misconfiguration
The console is served correctly under a PathBase — every one of these URLs returns 200 when
requested with the prefix. The failure is entirely in the URLs the console composes for itself.
There is no ASP.NET Core routing problem to fix on the consumer's side.
AbpAdminConsoleOptions declares twelve properties, read from the shipped assembly's metadata —
IsEnabled, RedirectRootToAdminConsole, Authority, AuthServerApplicationUrl, ClientId,
Scope, ThemeOverrideCssPath, ApplicationName, LogoUrl, InitialTheme,
CustomizationPermissionName, LocalizationLanguages. Of these, Authority and
ThemeOverrideCssPath let a consumer patch two of the six rows above. Nothing addresses the other
four, and none is a base path or route prefix.
The whole SPA — the shell, 165 JavaScript chunks, 9 stylesheets and 3 font files, 183 embedded
resources — ships inside Volo.Abp.AdminConsole.dll and is served through ABP's Virtual File
System. The NuGet package contains no loose files, so there is nothing on disk for a consumer to
patch.
A broader request: please test sub-path deployments
We would like to raise this beyond the individual bug.
Across this project and others, we have repeatedly hit defects that appear only when an ABP application is deployed under a path prefix rather than at a host root. They are not exotic: they are base hrefs, asset URLs, router bases, discovery documents and API roots — the same handful of things, in each new UI. Our experience is that fixes made in one UI stack do not carry across, and that issues resolved for Blazor have reappeared in React.
The pattern suggests sub-path deployment is not part of the regression suite for new UI work. We would like to suggest it becomes so. A single smoke test per UI — deploy under a two-segment prefix, sign in, load a page, follow a link — would have caught every defect in this report and in #10823, and would cost far less than the round trips these reports represent for both of us.
For organisations like ours this is not a niche configuration. It is the only shape available without a domain-approval process, and we would expect it to be common among corporate customers.
What we are doing instead, and why we would rather not
Response-rewriting middleware in our own host that buffers the console's HTML, JavaScript, CSS and
api/config, and rewrites the PathBase into them on the way out — carefully avoiding Br for the
reason given above. It works and it is covered by tests, but we are rewriting your minified bundle
in flight, matching on literals we found by reading it. It will break silently the first time the
minifier assigns different names or the shape changes, and 10.6.0 already restructured the shell
relative to 10.5.0 (assets renamed index-* → main-*, and six modulepreload links where there
were four). We would like to delete all of it.
Suggested fix, in priority order
basepath: Yr()when creating the router, and`${Yr()}/`as the lazy-chunk base. This is the whole user-visible failure and it uses a function you already ship.- Make
Kr()fall back toZr()rather than to the root-absolute/api, soapis.default.urlbeing empty or absent yields a base beneath the deployed path. - Derive
oAuthConfig.issuerandthemeOverrideCssPathfromRequest.PathBaserather than from scheme + host, soAuthorityandThemeOverrideCssPathdo not have to be configured per environment to compensate. - Build the bundle with a relative base and read the router base from
document.baseURI. With the shell's<base href>substituted at serve time — whichAdminConsoleSpaMiddlewareis already positioned to do — this would remove the whole class of defect rather than the six instances of it, and would make the console work under a sub-path with no configuration at all.
3 Answer(s)
-
0
What you’re seeing is consistent with the Admin Console React SPA being built for a fixed
/admin-consolebase path. UnderUsePathBase("/sub/backend")(or IIS virtual application), the server can still serve/sub/backend/admin-console/*, but the client-side router (TanStack Router) and several root-absolute asset/API URLs inside the compiled bundle still assume the app is mounted at the site root, so the SPA matches no routes and shows its own “404 Page not found”.In ABP 10.6, the Admin Console hosting model is documented as being served under a fixed path:
- UI:
/admin-console/* - API:
/admin-console/api/*This aligns with your observation that the compiled SPA hardcodes/admin-consoleand does not automatically incorporateRequest.PathBase.
- UI:
Practical resolution (supported approach today): run Admin Console at a host root OR provide it an origin that has no PathBase.
- Deploy the host application at the domain root (no
PathBase) or - If your organization constraint is “no new domain”, use an existing shared domain but map a separate IIS site/application (or reverse-proxy rule) so that the Admin Console is reachable at a root-level path on some origin (even if it’s internal), e.g.:
https://example.org/admin-console(root app) instead ofhttps://example.org/sub/backend/admin-console
- If you must keep the main host under
/sub/backend, then the only reliable workaround on 10.6.x is essentially what you already implemented: rewrite the served HTML/JS/CSS/config responses to inject the effective base path (because the SPA is shipped as embedded static resources and is not path-base aware).
- Deploy the host application at the domain root (no
There is no consumer-side configuration in
AbpAdminConsoleOptionsthat can fix the SPA router base/chunk base/font URLs when the bundle itself is compiled with root-absolute/admin-console...paths. Options likeAuthority/ThemeOverrideCssPathcan only patch a subset of the problems you listed.If you want this fixed upstream, the core requirement is: Admin Console SPA build needs to derive its base at runtime (e.g., from
document.baseURI/ injected<base href>/ a config endpoint) and avoid root-absolute URLs in emitted CSS and dynamic imports. As you noted, simply serving it under a PathBase is not enough if the bundle is compiled with a fixed base.
Links (validated):
- https://abp.io/docs/10.6/framework/ui/react/admin-console
- https://abp.io/docs/latest/framework/ui/react/admin-console
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
The AI answer seems to suggest that only deployments at the domain root are supported. However, that isn't the case for other UIs and the links provided don't say that it's the case for React, either. Running in a subdirectory is really the only practical solution here, and there really isn't a solid reason why a framework should make any assumptions about any parts of its URL that it doesn't directly control, including the meaning of a leading
/.Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
Confirmed, all six are real, and all six are fixed on our side. Sorry it took a second report to get here — the fix landed right after 10.6.0 was cut, and the patch release hasn't gone out yet, so there's nothing you can pick up from the feed today. We'll get it out as soon as we can and post the version here.
Until then, please keep the rewriting middleware you have. There's no configuration in 10.6.0 that can replace it, and it's the right shape for the problem.
One thing you can fix properly today, if your host project has the generated
wwwroot/global-styles.css: drop the leading slash from everyurl('/...')in it, otherwise the LeptonX logo and the login background 404 under a prefix.Un-minified sources aren't distributed and the package ships no source maps, so there's no channel you missed.
On sub-path deployment in the regression suite: fair ask, and I'll take it to the team.
Both tickets have been refunded — this one and https://abp.io/support/questions/10823.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)