Open Closed

global-styles.css CSS variable URLs break when application is hosted under a sub-application path #10590


User avatar
0
vts-leszek created

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:

  1. 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
  2. The documentation explicitly covers how to handle global-styles.css when 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.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

5 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • Fix (recommended): stop using root-absolute URLs in global-styles.css and switch to “application-root relative” URLs.
      In a static CSS file, paths starting with / will always resolve from the host root (ignoring UsePathBase). 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)
  • User Avatar
    0
    vts-leszek created

    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.css is 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 CSS url() references resolve relative to the stylesheet's own URL, a relative path like url('images/logo/leptonx/icon.svg') resolves to /Themes/LeptonX/Global/side-menu/css/images/logo/leptonx/icon.svg instead 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, ignoring UsePathBase |

    The only correct fix is to define these CSS variables server-side where HttpContext.Request.PathBase is available — for example via a LeptonX Layout Hook that renders an inline <style> block using Url.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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • 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 with Url.Content("~/...") / IUrlHelper and therefore respects PathBase).

    • Concrete approach (works with LeptonX 5.2.x bundling paths):

      1. 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>
    
    1. 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 relative url(...) inside static CSS becomes relative to that deep path (as you observed).
      • Root-absolute /images/... ignores UsePathBase.
      • Server-rendered Url.Content("~/images/...") produces /server/images/... when PathBase=/server, so it works in all hosting modes.
    • Alternative supported mechanism (also server-side): you can also add a dedicated CSS file as a bundle contributor, but that still won’t solve PathBase unless 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)
  • User Avatar
    0
    vts-leszek created

    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 the Configure<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.css retain 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.css uses 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)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    This is a known issue — static CSS files like global-styles.css can't resolve PathBase because 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 respects PathBase.

    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-*-bg declarations). 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 your PathBase configuration.

    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)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.