How to share the non-scoped styles in “.NET MAUI Blazor Hybrid and Web App” multiproject?

Summary

The issue occurs because the DisableScopedCssBundling property effectively bypasses the automatic Static Web Asset bundling pipeline. When applied to a .NET MAUI Blazor Hybrid and Web App multiproject (specifically within the Shared project), the build system stops generating the virtual _content/{AssemblyName}.styles.css file that the host projects expect to load.

Furthermore, the user is utilizing a global app.css file accessed via the _content virtual path. Standard CSS isolation is required to resolve the _content path for Shared projects in MAUI Blazor. Disabling it breaks the asset resolution mechanism without replacing it with a manual resolution strategy.

Root Cause

The root cause is a misunderstanding of the Static Web Assets Manifest resolution chain in a multi-targeted MAUI solution.

  1. Static Web Assets (SWA) Manifest: The Shared project (Class Library) compiles CSS files into obj folders. The build process generates a staticwebassets.manifest.json that maps virtual paths (like _content/MyApp.Shared/app.css) to physical files.
  2. Scoped CSS Bundling: The DisableScopedCssBundling property tells the SDK: “Stop automatically generating the bundled .styles.css file and don’t automatically wire it up.” This is intended for custom build pipelines.
  3. The MAUI Web Context: In the Web project (Razor Components), the <link> tag requires the SWA runtime to resolve _content/Question260197-1.Shared/app.css. This resolution relies on the SWA manifest being active.
  4. The Failure:
    • When you add <DisableScopedCssBundling>true</DisableScopedCssBundling> to the Shared project, you break the generation of the manifest entries required for the host projects to find the files.
    • The build error “No file exists for the asset” happens because the build targets try to bundle the isolated CSS (even though you disabled the bundling wrapper) but fail to find the generated files in the specific platform obj folders (e.g., net10.0-maccatalyst).
    • The runtime error ERR_ADDRESS_UNREACHABLE (or 404) for /Question260197-1.styles.css happens because the Web project still attempts to load a scoped bundle that you have told the build system not to create.

Why This Happens in Real Systems

  • Abstraction Leaks: The MAUI Blazor Hybrid + Web template tries to share code via a Razor Class Library (RCL). RCLs rely heavily on the Static Web Assets system to serve CSS/JS to consuming apps. Developers often try to “turn off” features (like CSS Scoping) to simplify their CSS architecture, not realizing those features are actually the glue holding the virtual file system together.
  • Global Styles vs. Scoped Styles: Developers often want global styles (no BEM, no scoping) but use the RCL structure which defaults to scoped/consumable assets. The conflict arises when they disable the mechanism that serves those assets.

Real-World Impact

  • Build Failures: The build pipeline throws System.InvalidOperationException because it cannot reconcile the asset definitions with the physical files on disk.
  • Runtime 404s: The application starts, but styles fail to load, resulting in unstyled UI.
  • Development Deadlock: The “standard” fix (disabling isolation) breaks the project, and the default behavior (enabling isolation) forces unwanted CSS scoping syntax (b-{hash}) on global styles.

Example or Code

If you are trying to share a global app.css without scoping, you should not disable the bundling mechanism. Instead, configure the static web assets to treat the CSS as a raw resource, but keep the discovery mechanism active.

However, if you must disable scoping (e.g., for legacy CSS reasons), you cannot rely on the _content virtual path for Shared assets in MAUI Web projects without manual intervention.

Correct Project Configuration (Standard Approach):



  
    
    false
  

  
    
    
  

Code to Reference Shared CSS (in App.razor or index.html):

How Senior Engineers Fix It

Senior engineers address this by aligning the build configuration with the asset delivery requirement. Since the _content path relies on the Static Web Asset infrastructure, you cannot disable the infrastructure entirely.

  1. Re-enable Scoped CSS Bundling: Set DisableScopedCssBundling to false (or remove the property) in the Shared project.

  2. Target Global CSS: Instead of disabling the mechanism, target the specific file. Ensure the CSS file is in the wwwroot folder of the Shared project.

  3. Avoid the Build Error: If the build error regarding Question260197-1.styles.css persists (the MAUI host project trying to load a bundle), you must suppress the generation of the bundle only on the Shared project but ensure the assets are still published.

    • Actually, the correct fix is to ensure the Shared project does not try to bundle its own CSS as a “site” but rather as a library.

    The Senior Fix:
    Remove <DisableScopedCssBundling>true</DisableScopedCssBundling> from the Shared project. It is not needed there.
    If you want to avoid scoping classes (e.g., b-xyz), put your styles in a file outside the App.razor scope or use ::deep or simply write your CSS selectors to be specific enough. The “non-scoped” requirement is best met by not putting the CSS inside the <style> block of a component, but keeping it in wwwroot/app.css.

    If you truly want to bypass the _content routing, the “Senior” architectural move is to move the app.css into the Web Project wwwroot or the MAUI Project wwwroot and reference it relatively, abandoning the “Shared Project CSS via Virtual Path” pattern entirely, as it is brittle across different MAUI targets.

Why Juniors Miss It

  • Property Misplacement: Juniors often put project properties (like DisableScopedCssBundling) in the wrong .csproj file. It belongs in the consuming project (the Web or MAUI project) to control how that project bundles assets, not in the Shared library which supplies the assets.
  • Confusing “Scope” with “Location”: Juniors think “Disabling Scoped CSS” means “Stop putting b-xyz hashes in my class names.” It actually means “Stop managing the lifecycle of the CSS file entirely.” They don’t realize the virtual path _content/ is a service provided by that lifecycle management.
  • Ignoring Platform Paths: The error message mentions maccatalyst-x64 and iossimulator-x64. Juniors often miss that MAUI is multi-targeted. A setting in a Shared project might break the build for one specific target platform (iOS) while working on another (Web), leading to inconsistent build results.