SlideShare a Scribd company logo
1
- Behind the scenes of "Awesome Blazor Browser" -
Pre-render
Blazor WebAssembly
on static web hosting at publishing time
2
2
Introduction
- What's "Awesome Blazor Browser"?
3
3
"Awesome Blazor Browser"
A dedicated web app for browsing the README of the
"Awesome Blazor" GitHub repository.
▪ It's a Blazor WebAssembly app (SPA).
▪ It's hosted on GitHub Pages.
▪ It's deployed by GitHub Actions.
▪ It provides us with easy navigation and
searching for the "Awesome Blazor"
collection.
• It fetches the README text from the
"Awesome Blazor" GitHub repository, parses,
restructures it, and shows the collection in a
searchable UI.
https://j.mp/awesome-blazor-browser 3
4
The "Awesome Blazor Browser" had a problem…
The Internet search result of the "Awesome Blazor Browser" was really bad.
- Currently, Google crawlers can index of a content that Blazor Wasm apps rendered.
- The server-side prerendering strategy is also effective when the app is hosted on an ASP.NET Core server.
See also: https://andrewlock.net/enabling-prerendering-for-blazor-webassembly-apps/
5
5
"Pre-rendering" at publishing time can resolve it.
• It's something like "Static Site Generation (SSG)".
• Make the published "index.html" to contains the same
content that the app renders into the browser after launching
the app.
• Additionally, if the app has some route URL, each URL should
be rendered and be saved into a corresponding static HTML.
• In this case, we have to do this in a GitHub Actions script
that publishes the site.
6
6
How to pre-render a Blazor
WebAssembly app at publishing time?
7
7
Create your own pre-render C# program
• Add an ASP.NET Core server project
for hosting the Blazor WebAssembly.
• And configure it to server-side render
with "static" render mode.
• Add an app that boots the host and
fetches contents from the host by
HttpClient and saves them to static
HTML files.
For more detail, see also "andrewlock.net".
Use the "react-snap"
• "react-snap" is a NodeJS tool.
• It traverses the published SPA contents
using a headless Chromium browser
and saves them to static HTML files.
• The Blazor WebAssembly side is not
required any code changes.
For more detail, see also "swimburger.net".
I could find some solutions on the Internet.
8
8
…But I could not be satisfied with those solutions.
Create your own pre-render C# program
• We have to write massive and
complicated C# codes.
• Those C# codes depend on individual
Blazor WebAssembly project strongly.
• That means the programs of this
approach can't reuse out-of-the-box for
the other Blazor WebAssembly project.
Use the "react-snap"
• It can't wait for an "OnInitializedAsync"
async method to be completed.
• Therefore, in some cases, it saved an
intermediate content result.
• It saves client-side process rendered
HTML, so the results are a bit different
from server-side rendered HTML.
• So, we must rewrite pre-rendered HTML
files after "react-snap" does.
9
9
…So, I started yet another project to
resolve it.
"Reinvent a wheel? Yes, I know."
10
10
My approach
and goal are:
Based on standard server-side
prerendering that is hosted on
an ASP.NET Core server.
Make the code changes of the
Blazor WebAssembly side to be
minimum.
Make it as a NuGet package.
11
11
…and finally, I did it !
"BlazorWasmPreRendering.Build"
https://www.nuget.org/packages/BlazorWasmPreRendering.Build/
12
12
Only you need to do is just adding that package!
> dotnet add package BlazorWasmPreRendering.Build --version 1.0.0-preview.4.1
13
13
An output of "dotnet publish" before adding this package
14
14
An output of "dotnet publish" after adding this package
15
15
Before After
In this case, I used the "Toolbelt.Blazor.HeadElement" NuGet package to change the document title.
See also: https://dev.to/j_sakamoto/yet-another-way-to-changing-the-page-title-in-blazor-and-more-43k
16
You don't need to change the GitHub action script.
In this case, I used the "PublishSPAforGitHubPages.Build"
NuGet package to simplify publishing the Blazor
WebAssembly app to GitHub Pages.
See also: https://dev.to/j_sakamoto/the-easier-way-to-
publish-your-blazor-webassembly-app-for-github-pages-319l
This package also works on the
GitHub Action script to deploy to
Azure Static Web apps.
17
17
What does the
"BlazorWasmPreRendering.Build" do?
18
18
"BlazorWasmPreRendering.Build" does…
• It starts its own ASP.NET Core web server instance.
• The server instance loads the published "index.html" file and
creates the SPA fallback Razor page on the fly from that
"index.html".
• The server instance also loads the Blazor components DLL files
from the publish folder and finds an "App" root component.
• It configures the SPA fallback Razor page to do server-side
rendering with "static" rendering mode.
• It sends HTTP requests to itself and saves the responses to
static HTML files in the publish folder.
19
19
Load
1st. Initializing step
📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/")
📄 "Index.html"
🎛 Web Server
🕹 Crawler
📂 "/_frameworks"
📄 "MyApp.dll"
...
<body>
<div id="app">
Loading...
</div>
...
</body>
📄 "_Host.cshtml"
</div>
...
</body>
...
<body>
<div id="app">
Loading...
@Html.RenderComponentAsync( ,…)
typeof(App) Load
20
20
2nd. Crawling step
📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/")
📄 "Index.html"
🎛 Web Server
🕹 Crawler
HTTP Request GET "/"
Response
<h1>Home</h1>
...
<a href="counter">
Counter</a>
... Save
<h1>Home</h1>
...
<a href="counter">
Counter</a>
...
Render
Detect Link
Response
<h1>Counter</h1>
... Save
<h1>Counter</h1>
...
Render
HTTP Request GET "/counter"
📂 "/counter"
📄 "index.html"
href="counter"
21
21
2nd. Crawling step
📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/")
📄 "Index.html"
🎛 WebHost
🕹 Crawler
HTTP Request GET "/"
Response
<h1>Home</h1>
...
<a href="counter">
Counter</a>
... Save
<h1>Home</h1>
...
<a href="counter">
Counter</a>
...
Render
Detect Link
Response
<h1>Counter</h1>
... Save
<h1>Counter</h1>
...
Render
HTTP Request GET "/counter"
📂 "/counter"
📄 "index.html"
href="counter"
These processes are executed just before the end of "dotnet publish".
22
22
But of course, it is not perfect.
"No Silver Bullet" ― Frederick Phillips Brooks, Jr.
23
23
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
var services = builder.Services;
var baseAddress = builder.HostEnvironment.BaseAddress;
await builder.Build().RunAsync();
}
"BlazorWasmPreRendering.Build" can't invoke the
Main method of the Blazor Wasm app side.
• "BlazorWasmPreRendering.Build"
runs your Blazor app on its own
server process.
• When your app depends on
custom services in a DI container,
but the "Main" method never be
invoked in the server process, the
app will fail because any services
are not registered to the DI
container in the server process.
services.AddTransient(sp => new HttpClient {
BaseAddress = new Uri(baseAddress) });
services.AddSingleton<MyService>();
Never invoked! 😱
24
24
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
var services = builder.Services;
var baseAddress = builder.HostEnvironment.BaseAddress;
await builder.Build().RunAsync();
}
So, you have to extract service registrations to
a method named "ConfigureServices()".
• "BlazorWasmPreRendering.Build"
will invoke this Blazor Wasm app's
"ConfigureServices()" method
within its startup process.
private static void ConfigureServices(
IServiceProvider services, string baseAddress)
{
}
ConfigureServices(services, baseAddress);
services.AddTransient(sp => new HttpClient {
BaseAddress = new Uri(baseAddress) });
services.AddSingleton<MyService>();
Will be invoked! 👍
25
25
And this is still an experimental project.
• There are few results of using the
"BlazorWasmPreRendering.Build" yet.
• I'm not sure currently that "BlazorWasmPreRendering.Build"
works well on complicated real-world applications or not.
• I am still interested in the "react-snap" approach if it can
avoid the problem that it can't wait for the async initialization
because developers may use this approach with an out-of-
the-box experience.
26
26
Conclusion
27
27
I hope "BlazorWasmPreRendering.Build" will save
your time
• This package will improve the Internet
search results of your static hosting
Blazor Wasm app with only minimal or
no code changes.
• But this is still an experimental project.
• I welcome anybody who forks and
improve this project or implement
other approaches.
28
28
Appendix
• "Yet another way to changing the page title in Blazor, and more." | DEV Community
https://dev.to/j_sakamoto/yet-another-way-to-changing-the-page-title-in-blazor-and-more-43k
• "The easier way to publish your Blazor WebAssembly app for GitHub Pages" | DEV Community
https://dev.to/j_sakamoto/the-easier-way-to-publish-your-blazor-webassembly-app-for-github-pages-319l
29
29
THANK YOU!
Lean, Practice, Share.

More Related Content

Pre-render Blazor WebAssembly on static web hosting at publishing time

  • 1. 1 - Behind the scenes of "Awesome Blazor Browser" - Pre-render Blazor WebAssembly on static web hosting at publishing time
  • 3. 3 3 "Awesome Blazor Browser" A dedicated web app for browsing the README of the "Awesome Blazor" GitHub repository. ▪ It's a Blazor WebAssembly app (SPA). ▪ It's hosted on GitHub Pages. ▪ It's deployed by GitHub Actions. ▪ It provides us with easy navigation and searching for the "Awesome Blazor" collection. • It fetches the README text from the "Awesome Blazor" GitHub repository, parses, restructures it, and shows the collection in a searchable UI. https://j.mp/awesome-blazor-browser 3
  • 4. 4 The "Awesome Blazor Browser" had a problem… The Internet search result of the "Awesome Blazor Browser" was really bad. - Currently, Google crawlers can index of a content that Blazor Wasm apps rendered. - The server-side prerendering strategy is also effective when the app is hosted on an ASP.NET Core server. See also: https://andrewlock.net/enabling-prerendering-for-blazor-webassembly-apps/
  • 5. 5 5 "Pre-rendering" at publishing time can resolve it. • It's something like "Static Site Generation (SSG)". • Make the published "index.html" to contains the same content that the app renders into the browser after launching the app. • Additionally, if the app has some route URL, each URL should be rendered and be saved into a corresponding static HTML. • In this case, we have to do this in a GitHub Actions script that publishes the site.
  • 6. 6 6 How to pre-render a Blazor WebAssembly app at publishing time?
  • 7. 7 7 Create your own pre-render C# program • Add an ASP.NET Core server project for hosting the Blazor WebAssembly. • And configure it to server-side render with "static" render mode. • Add an app that boots the host and fetches contents from the host by HttpClient and saves them to static HTML files. For more detail, see also "andrewlock.net". Use the "react-snap" • "react-snap" is a NodeJS tool. • It traverses the published SPA contents using a headless Chromium browser and saves them to static HTML files. • The Blazor WebAssembly side is not required any code changes. For more detail, see also "swimburger.net". I could find some solutions on the Internet.
  • 8. 8 8 …But I could not be satisfied with those solutions. Create your own pre-render C# program • We have to write massive and complicated C# codes. • Those C# codes depend on individual Blazor WebAssembly project strongly. • That means the programs of this approach can't reuse out-of-the-box for the other Blazor WebAssembly project. Use the "react-snap" • It can't wait for an "OnInitializedAsync" async method to be completed. • Therefore, in some cases, it saved an intermediate content result. • It saves client-side process rendered HTML, so the results are a bit different from server-side rendered HTML. • So, we must rewrite pre-rendered HTML files after "react-snap" does.
  • 9. 9 9 …So, I started yet another project to resolve it. "Reinvent a wheel? Yes, I know."
  • 10. 10 10 My approach and goal are: Based on standard server-side prerendering that is hosted on an ASP.NET Core server. Make the code changes of the Blazor WebAssembly side to be minimum. Make it as a NuGet package.
  • 11. 11 11 …and finally, I did it ! "BlazorWasmPreRendering.Build" https://www.nuget.org/packages/BlazorWasmPreRendering.Build/
  • 12. 12 12 Only you need to do is just adding that package! > dotnet add package BlazorWasmPreRendering.Build --version 1.0.0-preview.4.1
  • 13. 13 13 An output of "dotnet publish" before adding this package
  • 14. 14 14 An output of "dotnet publish" after adding this package
  • 15. 15 15 Before After In this case, I used the "Toolbelt.Blazor.HeadElement" NuGet package to change the document title. See also: https://dev.to/j_sakamoto/yet-another-way-to-changing-the-page-title-in-blazor-and-more-43k
  • 16. 16 You don't need to change the GitHub action script. In this case, I used the "PublishSPAforGitHubPages.Build" NuGet package to simplify publishing the Blazor WebAssembly app to GitHub Pages. See also: https://dev.to/j_sakamoto/the-easier-way-to- publish-your-blazor-webassembly-app-for-github-pages-319l This package also works on the GitHub Action script to deploy to Azure Static Web apps.
  • 18. 18 18 "BlazorWasmPreRendering.Build" does… • It starts its own ASP.NET Core web server instance. • The server instance loads the published "index.html" file and creates the SPA fallback Razor page on the fly from that "index.html". • The server instance also loads the Blazor components DLL files from the publish folder and finds an "App" root component. • It configures the SPA fallback Razor page to do server-side rendering with "static" rendering mode. • It sends HTTP requests to itself and saves the responses to static HTML files in the publish folder.
  • 19. 19 19 Load 1st. Initializing step 📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/") 📄 "Index.html" 🎛 Web Server 🕹 Crawler 📂 "/_frameworks" 📄 "MyApp.dll" ... <body> <div id="app"> Loading... </div> ... </body> 📄 "_Host.cshtml" </div> ... </body> ... <body> <div id="app"> Loading... @Html.RenderComponentAsync( ,…) typeof(App) Load
  • 20. 20 20 2nd. Crawling step 📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/") 📄 "Index.html" 🎛 Web Server 🕹 Crawler HTTP Request GET "/" Response <h1>Home</h1> ... <a href="counter"> Counter</a> ... Save <h1>Home</h1> ... <a href="counter"> Counter</a> ... Render Detect Link Response <h1>Counter</h1> ... Save <h1>Counter</h1> ... Render HTTP Request GET "/counter" 📂 "/counter" 📄 "index.html" href="counter"
  • 21. 21 21 2nd. Crawling step 📦 "BlazorWasmPreRendering.Build" 📂 Publish Folder ("/") 📄 "Index.html" 🎛 WebHost 🕹 Crawler HTTP Request GET "/" Response <h1>Home</h1> ... <a href="counter"> Counter</a> ... Save <h1>Home</h1> ... <a href="counter"> Counter</a> ... Render Detect Link Response <h1>Counter</h1> ... Save <h1>Counter</h1> ... Render HTTP Request GET "/counter" 📂 "/counter" 📄 "index.html" href="counter" These processes are executed just before the end of "dotnet publish".
  • 22. 22 22 But of course, it is not perfect. "No Silver Bullet" ― Frederick Phillips Brooks, Jr.
  • 23. 23 23 public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); var services = builder.Services; var baseAddress = builder.HostEnvironment.BaseAddress; await builder.Build().RunAsync(); } "BlazorWasmPreRendering.Build" can't invoke the Main method of the Blazor Wasm app side. • "BlazorWasmPreRendering.Build" runs your Blazor app on its own server process. • When your app depends on custom services in a DI container, but the "Main" method never be invoked in the server process, the app will fail because any services are not registered to the DI container in the server process. services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }); services.AddSingleton<MyService>(); Never invoked! 😱
  • 24. 24 24 public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); var services = builder.Services; var baseAddress = builder.HostEnvironment.BaseAddress; await builder.Build().RunAsync(); } So, you have to extract service registrations to a method named "ConfigureServices()". • "BlazorWasmPreRendering.Build" will invoke this Blazor Wasm app's "ConfigureServices()" method within its startup process. private static void ConfigureServices( IServiceProvider services, string baseAddress) { } ConfigureServices(services, baseAddress); services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }); services.AddSingleton<MyService>(); Will be invoked! 👍
  • 25. 25 25 And this is still an experimental project. • There are few results of using the "BlazorWasmPreRendering.Build" yet. • I'm not sure currently that "BlazorWasmPreRendering.Build" works well on complicated real-world applications or not. • I am still interested in the "react-snap" approach if it can avoid the problem that it can't wait for the async initialization because developers may use this approach with an out-of- the-box experience.
  • 27. 27 27 I hope "BlazorWasmPreRendering.Build" will save your time • This package will improve the Internet search results of your static hosting Blazor Wasm app with only minimal or no code changes. • But this is still an experimental project. • I welcome anybody who forks and improve this project or implement other approaches.
  • 28. 28 28 Appendix • "Yet another way to changing the page title in Blazor, and more." | DEV Community https://dev.to/j_sakamoto/yet-another-way-to-changing-the-page-title-in-blazor-and-more-43k • "The easier way to publish your Blazor WebAssembly app for GitHub Pages" | DEV Community https://dev.to/j_sakamoto/the-easier-way-to-publish-your-blazor-webassembly-app-for-github-pages-319l