Prerender mit Blazor WebAssembly (WASM) in ASP.NET Core Teil 1 

Das Vorladen (Prerender) einer Blazor WebAssembly (WASM) Anwendung setzt eine Host ASP.NET Core Anwendung voraus, indem die Client-Anwendung eingebettet wird. Es kann dazu genutzt werden, um die Herausforderungen mit der Suchmaschinenoptimierung zu lösen. 

SEO und Performance  

Search Engine Optimization (SEO) respektive Suchmaschinenoptimierung bezeichnet die Leistungssteigerung von Webinhalten, um die Sichtbarkeit einer Webseite in Suchmaschinen wie Google oder Bing zu verbessern. 

Ein Aspekt von SEO ist die Performance einer Webseite zu erhöhen, sodass Webinhalte effizienter laden und die Inhalte, anstatt des Ladefortschritts, indexiert und in Suchresultaten angezeigt wird.

Warum Prerendern von Inhalten? 

Blazor WebAssembly (WASM) mit einem ASP.NET Core Host, liefert beim initialen Laden nicht direkt die angeforderte Seite bzw. Razor Page zurück. Die Index.html-Seite, welche normalerweise eine Ladeanimation zeigt, wird bis die WebAssembly vollständig geladen ist eingeblendet. Das hat zur Folge, dass die Performance und SEO stark abnimmt. Ausserdem werden Key-Wörter und Meta Tags in den Razor Pages nicht ausgelesen. Lighthouse von Chrome biete eine Möglichkeit dies zu testen.

Die Abbildung zeigt die Auswertung einer Analyse. Die Performance-Kennzahl liegt hier bei 33.

Wie zu sehen ist, wird der längste Inhalt erst nach 55.8 Sekunden geladen und der Speed Index ist 35 Sekunden. Optimal ist der längste Inhalt unter 2.5 Sekunden und der Speed Index unter 3.4 Sekunden. Dies hat einen grossen Einfluss auf die User Experience. Besucher verlieren bei solchen Ladezeiten sehr schnell die Geduld. 

Prerender umsetzen 

Ziel ist es, innerhalb des Zeitrahmens die Inhalte auszuliefern. Das erfordert ein paar Anpassungen der Standardvorlage, wie zum Beispiel die Index.html Seite durch eine Razor Page beim Initialen Server Request zu ersetzen. 

In der Program.cs Datei des Client Projekt muss die folgende Zeile aus dem Code entfernt werden: 

builder.RootComponents.Add<App>("#app"); 

Im Server Projekt wird stattdessen eine _Host.cshtml Seite im Verzeichnis /Pages erstellt. 

Die _Host.cshtml Datei muss als Razor Page registriert werden, damit der Server diese in der Request Pipeline zurückliefern kann: 

@page 
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 

In der _Host.cshtml Datei den Inhalt der Index.html im Client Projekt übernehmen. Das HTML-Fragment <div id=”app”> wird nicht mehr benötigt und durch folgendes Code-Fragement ersetzt:  

<component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> 

Jetzt dem Server nur noch die neue Fallback Page mitteilen. Dazu im Program.cs des Server Projekt die Middleware in die Request Pipeline nach MapRazorPages und MapControllers einfügen: 

app.MapRazorPages(); 
app.MapControllers();
app.MapFallbackToPage("/_Host"); 

Und nicht vergessen die Razor Pages als Service zu registrieren, falls dies noch nicht gemacht wurde: 

builder.Services.AddRazorPages(); 

Das wars! 

Die Index.html kann aus dem Client Projekt gelöscht werden. Falls Progressive Web App (PWA) eingebaut ist, funktioniert dies wie bisher.  

Beim initialen Request wird nun die _Host.cshtml geladen, welche die App Component der Blazor WebAssembly (WASM) Anwendung lädt. Sobald die WebAssembly vom Client vollständig geladen wurde, wird die App Component von Blazor ausgetauscht.  

Was hat sich verbessert?

Da die angeforderte Seite vorgeladen wird, erhöht sich die Performance der Webseite und damit das SEO-Ranking.

Texte und Key-Wörter auf einzelnen Seiten können nun von Suchmaschinen ausgelesen und berücksichtigt werden. 

Hier ist schon die erste Verbesserung sichtbar (Die Werte können gelegentlich schwanken, wie hier beim SEO zu sehen ist):

Die Abbildung zeigt die Auswertung einer Analyse nach den Anpassungen. Die Performance-Kennzahl liegt neu bei 67.

Prerendering allein reicht aber nicht aus. Es gibt noch weitere Aspekte zu beachten, um das Ranking der Webseite zu steigern:

  • Vor allem die fachlichen Texte und der Aufbau sind von grosser Bedeutung
  • JavaScripts und CSS können nachträglich geladen werden. 
  • Statische Files können mit HTTP-Cache Headers versehen werden. 
  • Response Caching und Komprimierung kann auf dem Server implementiert werden.

Zusammenfassung Prerender mit Blazor WebAssembly

Für das Vorladen von Inhalten mit Blazor WASM ist eine Host-Anwendung mit ASP.NET Core nötig, in der die Blazor WASM Anwendung eingebettet wird.

In der Client-Anwendung wird die index.html – Seite entfernt und Anpassungen in Program.cs vorgenommen. Das Resultat:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
// builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();

In der Host-Anwendung (Server) wird eine Razor-Page _Host.cshtml erstellt:

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using Prerender_PoC.Client
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>Prerender_PoC</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <link href="Prerender_PoC.Client.styles.css" rel="stylesheet" />
    <link href="manifest.json" rel="manifest" />
    <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
    <link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
</head>
<body>
<component type="typeof(App)" render-mode="WebAssemblyPrerendered"/>
<div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
</body>
</html>

In der Program.cs der Host-Anwendung müssen folgende Punkte hinzugefügt und aktiviert werden:

using Microsoft.AspNetCore.ResponseCompression;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToPage("/_Host");
app.Run();

Weitere Schritte 

Nun da das Vorladen von Blazor aktiviert ist, können verschiedene Probleme oder Fragen aufkommen wie:

  • Was passiert, wenn Razor Pages mit Dependency Injection vorgeladen werden? 
  • Welche Sicherheitsaspekte müssen beachtet werden? 
  • Kann ich bestimmte Razor Pages vom Vorladen ausschliessen? 
  • Razor Pages sind nicht interaktiv für den Benutzer bis WASM geladen ist, warum? 

Diese und weitere Fragen werde ich im 2. Teil erklären, wenn es um die fortgeschrittenen Funktionen von Prerendering geht.

Was sind deine Erfahrungen mit Prerendering?