How can I host a shared Blazor Custom Elements app on a different Domain?

László Tóth 0 Reputation points
2023-03-27T18:06:00.6133333+00:00

I tried to publish a Blazor app with custom elements on a Static Web App and reference it from another deployed app. Just added the necessary script tags pointing to the static web app.
It does not work.
Looking into the blazor.webassembly.js script it uses relative paths - obviously. But the problem is those paths will be relative to the page which is loaded from the other app.

Seemed possible to hack it by rewriting the url to blazor.boot.json as absolute urls. Ugly it is, but it did the job. However in that json there is another relative url. I changed that one also to be an absolute url. Sadly in that case the js code explicitly prefixes it with the document.baseURI. So I get the https://... twice :)

Well, I could go on with these hacks, but does not look good.
Isn't there a way to make it possible to deploy a reusable Blazor app like this (to be usable in multiple other MVC or angular or any app as custom elements without the need to deploy the Blazor files in those apps)?

Here is the why:

Our company has multiple applications. We also have a 'portal' to combine some functionalities of some applications. Mostly it is not too sophisticated - just displays the apps in IFrames. I do not like that solution. My idea was to create the new UI in Blazor. Using components and at the same time exposing some of them as custom elements. So I could deploy it as a static we app in azure. The portal would need only one or two lines of script references and it could use the custom elements.
However I have no control over the portal's repository, the portal is deployed at another cloud provider. So I cannot push the published files to their repo or deployment.

Developer technologies | .NET | Blazor
Azure Static Web Apps
Azure Static Web Apps
An Azure service that provides streamlined full-stack web app development.
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. brtrach-MSFT 17,756 Reputation points Microsoft Employee Moderator
    2023-04-02T21:34:44.8366667+00:00

    To host a shared Blazor Custom Elements app on a different domain, you can deploy the Blazor app as a standalone web app and reference it from other applications using the script tag. However, as you mentioned, the relative paths in the blazor.webassembly.js script will be relative to the page which is loaded from the other app. One way to solve this issue is to use absolute URLs in the script tags that reference the Blazor app. Another way is to use a CDN to host the Blazor app and reference it from other applications using the CDN URL. This way, the relative paths in the blazor.webassembly.js script will be relative to the CDN URL, which will be the same for all applications that reference the Blazor app.

    Regarding your specific use case, you can deploy the Blazor app as a standalone web app in Azure and expose some of its components as custom elements. Then, you can reference these custom elements from other applications using the script tag. However, since you do not have control over the portal's repository and deployment, you cannot push the published files to their repo or deployment. In this case, you can use a CDN to host the Blazor app and reference it from the portal using the CDN URL. This way, you can update the Blazor app independently of the portal and all applications that reference it will get the latest version from the CDN.


  2. László Tóth 0 Reputation points
    2025-08-04T13:08:21.5366667+00:00

    Yes we did. It is a bit hacky because indeed we had to modify the blazor.webassembly.js.
    It could be (IMHO) the official implementation though...

    It took some steps to set it up but still then it works like a charm

    Modify the blazor.webassembly.js on publish

    For this we created a command line utility BlazorToAbsolutePath.exe and configured it to be executed on publish

    <Target Name="PostPublish" AfterTargets="AfterPublish">
    
    	<Exec Command="..\DeveloperTools\BlazorToAbsolutePath\bin\$(Configuration)\$(TargetFramework)\BlazorToAbsolutePath.exe bin\Release\net8.0\publish" />
    
    </Target>
    

    The utility itself replaces every usage of the root url with a variable. And of course first it defines that variable. In essence it just makes the baseUrl for Blazor configurable. After this change the app works exactly as worked before BUT you can define a variable with an absolute url on the document. If that is defined then will be picked up and used for blazor.

    Here is the main art of it (the whole code is 110 lines but it does other things as well, like tricks with our css files, recompressing the blazor.webassembly.js into .gz and .br, etc. NO secret in it i can share on request.)

    
    var rootPath = Path.GetFullPath(args[0]);
    var wwwroot = Path.Combine(rootPath, "wwwroot");
    
    Console.WriteLine($"root path: {rootPath}");
    #region hack the main stylesheet - until flow portal corrects the link
    var stylsheet = Path.Combine(wwwroot, "DocumentManager.UI.styles.css");
    var legacyStylsheet = Path.Combine(wwwroot, "DocumentManager.UI.styles.css");
    File.Copy(stylsheet, legacyStylsheet);
    #endregion
    
    #region hack the blazor.webassembly.js
    var framework = Path.Combine(wwwroot, "_framework");
    var filePath = Path.Combine(framework, "blazor.webassembly.js");
    var url = @"typeof(document.dmApiUrl) === ""undefined"" ? document.baseURI:document.dmApiUrl";
    
    
    var fileText = File.ReadAllText(filePath);
    fileText = fileText.Replace("var e,t,n;!", $"var e,t,n;var hostUrl = {url};!")
        .Replace("\"_framework/", "hostUrl+\"_framework/")
        .Replace("`_framework", "`${hostUrl}_framework")
        .Replace("\"string\"==typeof e&&e.startsWith(\"./\")&&(e=new URL(e.substr(2),document.baseURI).toString())", "\"string\"==typeof e&&e.startsWith(\"./\")&&(e=new URL(e.substr(2),hostUrl).toString())||\"string\"==typeof e&&e.startsWith(\"/\")&&(e=new URL(e.substr(1),hostUrl).toString())")
        .Replace("const t=document.baseURI;", "const t=hostUrl;")
        //.Replace("document.baseURI","hostUrl").Replace("document-baseURI", "document.baseURI") //because we host the UI in the webAPI
        ;
    
    File.WriteAllText(filePath, fileText);
    
    Compress(filePath);
    
    #endregion
    
    

    Register web components

    for this you ned the Microsoft.AspNetCore.Components.CustomElements nuget package in your Blazor app. then just add a line for each component you want to use/share liek for example:

    builder.RootComponents.RegisterCustomElement<MyTestComponent>("dm-testcomponent-three");
    

    Publish the Blazor app somewhere

    We have it as a WASM application and published it together with a webapi which backs up it with data. Like we would deploy a Blazor WASM anyway. So it is actually usable on it's own as well

    Configure CORS

    Just do not forget to setup CORS for the other application which is going to host your components

    Setup the hosting app

    On any page you want to use a blazor component (or on the 'master' page index.html whatever)

    <head>
      <link href=https://url-for-your-blazor-app.styles.css rel="stylesheet" />
      <script type="text/javascript">
        document.dmApiUrl = https://url-for-your-blazor-app-domain/;
      </script>
    ...
    

    and at the end of that page

    <script src=https://url-for-your-blazor-app-domain/_framework/blazor.webassembly.js></script>
    

    After all of these on the page you can just use the custom elements like.

    <dm-testcomponent-three />

    Gotchas

    Styling can be tricky. If you use component libraries which requires to include their css explicitly you have to do it on the hosting app as well - we only use libraries where they inject their css for themselves, so it is not a problem However the css isolation can be challenging. Because of the css isolation the only way to get global classes is in your app.css (or similar). That is not referenced and not automatically injected. But you don't even want that really, because that could mess up your hosting app's styles. Instead we came up with a practically empty HostingEnvironment templated component. If we need to share things we register it under that component and create a wrapper for our other components.

    Sadly the Blazor architecture makes it practically impossible to use Shadow DOM for your components (except if that component is not interactive) so it is better if you use prefixes for your css classes.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.