ABP version: 10.2.0 / LeptonX 5.2.0
Affected template: MVC/Razor Pages + Angular (HttpApi.Host), tiered
Reproduction URL pattern: https://example.com/server/Account/Manage (any MVC page when App:SelfUrl includes a sub-path, e.g. https://example.com/server)
Problem
The project template generates global-styles.css (in wwwroot/) with root-absolute url() references for LeptonX CSS custom properties:
:root {
--lpx-theme-light-bg: url('/LeptonX/images/login-pages/login-bg-img-light.svg');
--lpx-theme-dim-bg: url('/LeptonX/images/login-pages/login-bg-img-dim.svg');
--lpx-theme-dark-bg: url('/LeptonX/images/login-pages/login-bg-img-dark.svg');
}
:root {
--lpx-logo: url('/images/logo/leptonx/icon.svg');
--lpx-logo-icon: url('/images/logo/leptonx/icon.svg');
}
When the application is hosted under a sub-application path (e.g. /server), these root-absolute URLs resolve against the host root instead of the application root. The browser requests:
GET /images/logo/leptonx/icon.svg → 404
instead of:
GET /server/images/logo/leptonx/icon.svg → 200
As a result, the application logo is not displayed and login page backgrounds are broken for any deployment that uses a path base.
Root cause
global-styles.css is a static file — it has no server-side context and cannot dynamically include the configured path base. Root-absolute paths (starting with /) always resolve from the host root, ignoring any UsePathBase middleware configuration.
Other parts of ABP handle this correctly by generating URLs server-side (e.g. LeptonX's accountLayoutBackgroundStyle uses a Razor-computed URL). The CSS variable definitions in global-styles.css have no equivalent mechanism.
Impact
- Application logo invisible on all server-side pages when hosted under a sub-path.
- Login page background images broken under a sub-path.
- Affects any production deployment that uses path-base hosting (e.g. IIS sub-application, reverse proxy with a path prefix).
Expected behaviour
Either:
- The template provides a server-rendered mechanism (e.g. a Layout Hook with
Url.Content("~/...")) for these CSS variable definitions so the path base is respected, or - The documentation explicitly covers how to handle
global-styles.csswhen hosting under a sub-application path.
Additional observation
We also briefly observed a similar 404 for bootstrap-dark.css (a LeptonX virtual file system asset) on the same page, which would suggest the issue may extend beyond global-styles.css to bundle-managed stylesheet URLs as well. However, we were unable to reproduce it consistently and it subsequently resolved itself, so we cannot confirm whether it is related or was an unrelated transient error.
5 Answer(s)
-
0
Fix (recommended): stop using root-absolute URLs in
global-styles.cssand switch to “application-root relative” URLs.
In a static CSS file, paths starting with/will always resolve from the host root (ignoringUsePathBase). To make the browser automatically include the path base (e.g./server), change them to relative paths (no leading/).Example (
HttpApi.Host/wwwroot/global-styles.css):
:root { /* was: url('/LeptonX/images/...') */ --lpx-theme-light-bg: url('LeptonX/images/login-pages/login-bg-img-light.svg'); --lpx-theme-dim-bg: url('LeptonX/images/login-pages/login-bg-img-dim.svg'); --lpx-theme-dark-bg: url('LeptonX/images/login-pages/login-bg-img-dark.svg'); } :root { /* was: url('/images/...') */ --lpx-logo: url('images/logo/leptonx/icon.svg'); --lpx-logo-icon: url('images/logo/leptonx/icon.svg'); }With this change, when the CSS file is served from
https://example.com/server/global-styles.css, the browser resolves:url('images/logo/leptonx/icon.svg')→https://example.com/server/images/logo/leptonx/icon.svg(works under a path base)If you prefer not to edit the generated file: move these CSS variable overrides into a custom global CSS file you control (still under
wwwroot) and add it to the global bundle after theme styles, then use the same “no leading/” relative URLs there. ABP supports adding a global CSS file to the global style bundle via bundling options (MVC/Razor Pages).(This is the official customization mechanism; the key is: don’t use
/...in static CSS when hosting under a sub-path.)
Links (verified):
- https://abp.io/docs/10.2/framework/ui/mvc-razor-pages/customization-user-interface
- https://abp.io/docs/10.2/framework/ui/mvc-razor-pages/basic-theme
- https://abp.io/docs/10.2/framework/ui/common/leptonx-css-variables
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
Thank you for the response. Unfortunately, switching to relative paths is something we already tried, and it broke after upgrading to LeptonX 5.2.0.
In LeptonX 5.2.0,
global-styles.cssis included in the LeptonX global CSS bundle, which is served from a URL such as/Themes/LeptonX/Global/side-menu/css/bootstrap-dark.css. Because CSSurl()references resolve relative to the stylesheet's own URL, a relative path likeurl('images/logo/leptonx/icon.svg')resolves to/Themes/LeptonX/Global/side-menu/css/images/logo/leptonx/icon.svginstead of/images/logo/leptonx/icon.svg— so the resource 404s regardless of the path base. This is what forced us back to absolute paths.So neither option works when the app is hosted under a path base:
| URL style | Breaks because | |---|---| |
url('images/...')relative | Resolves against the bundle URL, not the app root (regression introduced in LeptonX 5.2.0) | |url('/images/...')absolute | Resolves against the host root, ignoringUsePathBase|The only correct fix is to define these CSS variables server-side where
HttpContext.Request.PathBaseis available — for example via a LeptonX Layout Hook that renders an inline<style>block usingUrl.Content("~/images/..."). Could you confirm whether that approach is officially supported, or whether there is a built-in ABP mechanism we are missing?Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Yes — injecting an inline
<style>via ABP’s layout hook mechanism is an officially supported customization approach for MVC/Razor Pages, and it’s the correct way to generate path-base-aware URLs (because it runs in Razor withUrl.Content("~/...")/IUrlHelperand therefore respectsPathBase).Concrete approach (works with LeptonX 5.2.x bundling paths):
- Create a Razor partial (or ViewComponent) that outputs your CSS variables using server-generated URLs:
@* /Pages/Shared/Components/LpxCssVariables/Default.cshtml (or any partial you include via a hook) *@ <style> :root { --lpx-theme-light-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-light.svg")'); --lpx-theme-dim-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-dim.svg")'); --lpx-theme-dark-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-dark.svg")'); --lpx-logo: url('@Url.Content("~/images/logo/leptonx/icon.svg")'); --lpx-logo-icon: url('@Url.Content("~/images/logo/leptonx/icon.svg")'); } </style>- Register it using ABP’s UI customization extension points (layout hooks). This ensures it’s rendered on every page (or at least on the layouts you target) and therefore doesn’t depend on the CSS bundle URL at all.
Why this is the “missing mechanism”:
- Bundled theme CSS can be served from deep URLs like
/Themes/LeptonX/.../css/bootstrap-dark.css, so relativeurl(...)inside static CSS becomes relative to that deep path (as you observed). - Root-absolute
/images/...ignoresUsePathBase. - Server-rendered
Url.Content("~/images/...")produces/server/images/...whenPathBase=/server, so it works in all hosting modes.
- Bundled theme CSS can be served from deep URLs like
Alternative supported mechanism (also server-side): you can also add a dedicated CSS file as a bundle contributor, but that still won’t solve
PathBaseunless the file content is generated server-side. For this specific problem, layout hook + inline style is the correct pattern.
Links (verified):
- https://abp.io/docs/10.2/framework/ui/mvc-razor-pages/customization-user-interface
- https://abp.io/docs/10.2/framework/ui/mvc-razor-pages/bundling-minification
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
Thank you — this confirms our suspicion and the layout hook approach makes sense. We'll implement it.
For completeness, could you provide the full code example for the Razor partial? Specifically, the
@Url.Content("~/...")calls for the CSS variables, and theConfigure<AbpLayoutHookOptions>registration. The partial path and hook target (LayoutHooks.Head.Last) you mentioned are clear, but an explicit snippet showing the inline<style>block with the correct variable names would help us avoid guessing the right hook and layout scope.Also, one follow-up: should
global-styles.cssretain these CSS variable declarations (with absolute paths as a fallback) or should they be removed entirely once the layout hook is in place? Keeping both would cause a specificity race depending on load order, so we want to be sure which is the intended pattern.Finally, should this be considered a bug in the project template? The generated
global-styles.cssuses root-absolute paths that work only when the app is hosted at the root. Sub-path deployments are a common and supported ASP.NET Core scenario (UsePathBase), yet the template generates code that silently breaks in that configuration with no warning. If the layout hook is the correct pattern, we would expect the template to generate that instead of a static CSS file with hard-coded absolute paths.Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
This is a known issue — static CSS files like
global-styles.csscan't resolvePathBasebecause they have no server-side context. Neither root-absolute (/images/...) nor relative (images/...) paths work reliably across all bundling modes when hosted under a sub-path.The workaround is to move these CSS variable definitions from the static file to a server-rendered Layout Hook, where
Url.Content("~/...")properly respectsPathBase.Here's how:
1. Create a ViewComponent:
using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; public class PathBaseStyleViewComponent : AbpViewComponent { public IViewComponentResult Invoke() { return View("~/Views/Shared/Components/PathBaseStyle/Default.cshtml"); } }2. Create the Razor view (
Views/Shared/Components/PathBaseStyle/Default.cshtml):<style> :root { --lpx-logo: url('@Url.Content("~/images/logo/leptonx/icon.svg")'); --lpx-logo-icon: url('@Url.Content("~/images/logo/leptonx/icon.svg")'); --lpx-theme-light-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-light.svg")'); --lpx-theme-dim-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-dim.svg")'); --lpx-theme-dark-bg: url('@Url.Content("~/LeptonX/images/login-pages/login-bg-img-dark.svg")'); } </style>3. Register the hook in your module:
Configure<AbpLayoutHookOptions>(options => { options.Add(LayoutHooks.Head.Last, typeof(PathBaseStyleViewComponent)); });4. Remove the CSS variable definitions from
global-styles.css(the--lpx-logo,--lpx-logo-icon, and--lpx-theme-*-bgdeclarations). Keep the file if you have other custom styles in it.With this approach,
Url.Content("~/...")generates the correct path (e.g./server/images/logo/leptonx/icon.svg) regardless of yourPathBaseconfiguration.We're also looking into updating the LeptonX layouts to handle this internally so it works out of the box in future versions.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
