.NET 8’s Greatest Blazor isn’t Blazor as we all know it

One of the simplest ways to seek out out what’s new in .NET 8 Blazor is to look at the wonderful
Full stack web UI with Blazor in .NET 8 presentation by Daniel Roth and Steve Sanderson,
which covers how Blazor has turn into a Full Stack UI Internet Expertise for creating any type of .NET Internet App.
Your first .NET 8 Blazor App
You aren’t getting to understand what this implies till you create your first .NET 8 Blazor App the place you may be pleasantly
shocked that Blazor Apps render quick, clear HTML without having to load massive Internet Meeting belongings wanted for
Blazor WebAssembly Apps or beginning a stateful Internet Socket connection required for Blazor Server Interactive Apps.
It’s because the default rendering mode for Blazor makes use of neither of those applied sciences, as an alternative it returns to
conventional Internet App growth the place Blazor Pages now return clear, superb HTML – courtesy of Blazor’s
Static render mode.
Select your compromise
Beforehand we have been compelled to decide on upfront whether or not we needed to construct a Blazor Internet Meeting App or a Blazor Server App and
the compromises that got here with them, which for public Web Internet Apps wasn’t even a alternative as Blazor Server Apps carry out poorly
over excessive latency Web connections.
This meant selecting Blazor Internet Meeting Apps which required downloading a big Internet Meeting runtime
with customers experiencing a protracted delay earlier than the App was practical. To reduce this impression our Blazor WebAssembly Tailwind template
included built-in prerendering the place as a part of deployment would
generate static .html pages that have been deployed with the Blazor Internet Meeting front-end UI that may be hosted on CDN
edge networks to additional enhance load instances.
While this meant the App’s UI could be rendered instantly, it nonetheless would not be practical till the Internet Meeting runtime was
downloaded and initialized, which might flicker because the static UI was changed with Blazor’s WASM rendered UI, then later
Authenticated customers would expertise additional delay and UI jank while the App indicators within the Authenticated Consumer.
While prerendering is an enchancment over Blazor WASM’s default clean loading display screen, it is nonetheless not best for public going through Internet Apps.
.NET 8 Blazor is a Recreation Changer
The state of affairs has significantly improved in .NET 8 the place your whole App now not must be certain to a single Interactivity mode.
Even higher, Blazor’s default static rendering mode leads to one of the best UX the place the Web site Format and essential
touchdown pages could be rendered immediately.
Interactive solely while you want it
Solely pages that want Blazor’s interactivity options can opt-in to whichever Blazor interactive rendering mode makes
essentially the most sense, both on a page-by-page or element foundation, or by selecting RenderMode.InteractiveAuto
which makes use of
InteractiveWebAssembly if the WASM runtime is loaded or InteractiveServer if it is not.
Enhanced Navigation FTW
In the end I anticipate Blazor’s new Enhanced Navigation is probably going the characteristic that may ship the largest UX enchancment
customers will expertise given it is enabled by default and offers conventional statically rendered Internet Apps prompt SPA-like
navigation responsiveness the place new pages are swapped in without having to carry out costly full web page reloads.
It is magnificence lies in having the ability to do that as a principally clear element with out the standard SPA complexity of needing
to handle advanced state or client-side routing. It is a good implementation that is capable of carry out fine-grained
DOM updates to solely components of pages which have modified, offering the final word UX of preserving web page state,
like populated kind fields and scroll place, to ship a quick and responsive UX that beforehand wasn’t attainable
from the simplicity of a Server Rendered App.
Its implementation does pose some challenges in implementing sure options, however we’ll cowl some approaches
under we have used to beat them under.
Full Stack Internet UI
Blazor’s static rendering with enhanced navigation and its opt-in flexibility makes .NET 8 Blazor a recreation changer,
increasing it from a really area of interest set of use-cases that weren’t too adversely affected by its Interactivity mode downsides,
to turning into a viable resolution for creating any type of .NET Internet App, particularly because it can be utilized inside
current ASP.NET MVC and Razor Pages Apps.
Advantages over MVC and Razor Pages
As well as, Blazor’s superior element mannequin permits constructing higher encapsulated, extra reusable and easier-to-use UI parts
which has enabled Blazor’s wealthy third Occasion library ecosystem to flourish, that we ourselves make the most of to develop
the excessive productiveness Tailwind Elements within the ServiceStack.Blazor element library.
Thus far there’s solely upsides for .NET Internet App growth, the compromises solely kick in while you want Blazor’s interactivity options,
fortunately these can now be scoped to only the Pages and Elements that want them. However how usually do we’d like them?
When do you want Blazor’s Interactivity options?
It in the end relies on what App your constructing, however loads of Web sites can fortunately show dynamic content material, navigate shortly
with enhanced navigation, fill out and submit kinds – all in Blazor’s default static rendering mode.
Not even superior options like Streaming Rendering utilized in Blazor Template’s
Weather.razor
web page require interactivity, as its progressive rendered UI updates are achieved in a single request with out interactivity.
Actually the one time @rendermode InteractiveServer
is required within the default Blazor template is within the
Counter.razor
web page whose C# Occasion Dealing with require it.
In the end some type of Interactivity goes to be required with the intention to add habits or client-side performance that
runs after pages have been rendered, however you continue to have some choices left earlier than being compelled to choose into an Interactive Blazor resolution.
Interactive Characteristic Choices
We are able to see a few of these choices utilized within the Blazor Template
NavMenu.razor
element which makes use of JavaScript onclick
occasion handlers so as to add client-side habits to simulate mouse clicks to toggle UI components:
<div class="nav-scrollable" onclick="doc.querySelector('.navbar-toggler').click on()">
and submitting kinds to Logout customers:
<LogoutForm id="logout-form" />
<NavLink class="nav-link" onclick="doc.getElementById('logout-form').submit(); return false;">
<span class="bi bi-arrow-bar-left" aria-hidden="true"></span> Logout
</NavLink>
Successfully including interactivity to Blazor static rendered pages with client-side JavaScript to keep away from paying Blazor’s Interactivity tax.
Keep away from utilizing Interactivity in Layouts
That is particularly essential for any options you need to add to the Web sites Format or Chrome UI which you may at all times need to be
statically rendered so touchdown pages can load quick and render Website positioning-friendly server rendered content material.
This meant we could not use ServiceStack.Blazor’s current DarkModeToggle.razor
element for toggling on/off DarkMode since its statically rendered contained in the Web sites Format and requires Interactivity to work.
Vanilla JS Blazor Elements
Thankfully using easy factor JavaScript callbacks was sufficient to have the ability to re-implement its performance with Vanilla JS
within the new DarkModeToggleLite.razor
element which works in all Blazor rendering modes, in each full-page or enhanced navigation hundreds:
<button sort="button" onclick="toggleDarkMode()" class=@ClassNames(DarkModeToggle.ButtonClasses, Class) function="change" aria-checked="false" @attributes="AdditionalAttributes">
<span class="@DarkModeToggle.InnerClasses" data-class-light="translate-x-5" data-class-dark="translate-x-0">
<span class="@DarkModeToggle.IconClasses" data-class-light="opacity-0 ease-out duration-100" data-class-dark="opacity-100 ease-in duration-200" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path fill="currentColor" d="M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3Z" /></svg>
</span>
<span class="@DarkModeToggle.IconClasses" data-class-light="opacity-100 ease-in duration-200" data-class-dark="opacity-0 ease-out duration-100" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-indigo-600" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path fill="currentColor" d="M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6ZM5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z" /></svg>
</span>
</span>
</button>
<script>
window.toggleDarkMode = (perform() {
let isDark = localStorage.getItem('color-scheme') === 'darkish'
const html = doc.documentElement
perform renderDarkMode() {
html.model.setProperty('color-scheme', isDark ? 'darkish' : null)
html.classList.toggle('darkish', isDark)
doc.querySelectorAll('[data-class-light]').forEach(el => {
const removeClasses = isDark
? el.dataset.classLight
: el.dataset.classDark
const addClasses = isDark
? el.dataset.classDark
: el.dataset.classLight
removeClasses.break up(' ').forEach(c => el.classList.take away(c))
addClasses.break up(' ').forEach(c => el.classList.add(c))
})
}
renderDarkMode()
doc.addEventListener('DOMContentLoaded', () =>
Blazor.addEventListener('enhancedload', () => {
isDark = localStorage.getItem('color-scheme') === 'darkish'
html.classList.toggle('darkish', isDark)
renderDarkMode()
}))
return perform() {
isDark = !isDark
localStorage.setItem('color-scheme', isDark ? 'darkish' : 'gentle')
renderDarkMode()
}
})()
</script>
To assist enhanced navigation you may should be conscious that <script>
tags are solely executed as soon as on preliminary web page load.
You may as an alternative must register a callback with Blazor’s enhancedload
occasion for any startup logic that wants re-executing,
which is fired after Blazor merges the brand new web page’s DOM with the prevailing DOM, and is the place the <DarkModeToggleLite>
element re-renders itself with the proper state.
When utilizing callbacks to invoke world features like this it is really helpful to wrap them in an IIFE
for higher encapsulation of inside element state and performance to keep away from polluting the worldwide namespace.
Strive it out!
With that it is prepared for motion, attempt it out in a brand new blazor Mission
or from its Stay Demo by toggling on/off Darkish Mode Part within the prime proper nook:
Declarative JavaScript Modules
Sadly loads of different approaches will not work with Blazor’s Enhanced Navigation, for instance while the built-in
ASP.NET Identification Pages all work with out Blazor’s Interactivity, the EnableAuthenticator.razor
web page does not really embody an answer for offering a visible QR Code barcode which cellphones can simply scan to
setup 2FA Authentication.
While the placeholders are there, that implementation element is left to us to exercise how we greatest need to implement it
inside our Apps, maybe as a result of they do not need to power an Interactivity rendering mode within the default template.
To keep away from a degraded UX with Blazor Interactivity you may naturally need to implement this with JavaScript utilizing the favored
qrcodejs library by following its directions and including a easy inline script to the web page:
<div data-permanent id="qrCode"></div>
<div id="qrCodeData" data-url="@_authenticatorUri"></div>
<script src="lib/js/qrcode.min.js"></script>
<script>
new QRCode(doc.getElementById('qrCode'),
doc.getElementById('qrCodeData').dataset.url)
</script>
While this works as anticipated in full web page reloads, it does not work in Blazor’s Enhanced Navigation because the <script>
tag
is just executed as soon as on preliminary web page load and never re-executed when the web page is loaded with enhanced navigation.
Your choices are to vary all hyperlinks to that web page with data-enhance-nav="false"
to show off enhanced navigation
to that web page, or we have to discover one other manner.
The answer that labored greatest for us is to make use of declarative directions to specify which JavaScript modules must be loaded
for any web page, which we will do by including a data-module
attribute to any factor, e.g:
<div data-module="pages/Account/Handle/EnableAuthenticator.mjs">
These directions are then dealt with by app.mjs
on every navigation which hundreds the required JavaScript module and calls its load()
perform if it exists:
export async perform remount() {
doc.querySelectorAll('[data-module]').forEach(async el => {
let modulePath = el.dataset.module
if (!modulePath) return
if (!modulePath.startsWith("https://servicestack.internet/") && !modulePath.startsWith('.')) {
modulePath = `../${modulePath}`
}
attempt {
const module = await import(modulePath)
if (typeof module.default?.load == 'perform') {
module.default.load()
}
} catch (e) {
console.error(`Could not load module ${el.dataset.module}`, e)
}
})
}
doc.addEventListener('DOMContentLoaded', () =>
Blazor.addEventListener('enhancedload', remount))
Which for EnableAuthenticator.razor
web page hundreds the
EnableAuthenticator.mjs
JavaScript Module which dynamically hundreds the qrcode.min.js
library and initializes the QR Code on its exported load()
perform:
import { addScript, $1 } from "@servicestack/shopper"
const loadJs = addScript('lib/js/qrcode.min.js')
export default {
async load() {
await loadJs
new QRCode($1("#qrCode"), $1('#qrCodeData').dataset.url)
}
}
Which now works as anticipated in each full web page reloads and Blazor’s Enhanced Navigation:
Blazor with out Blazor Interactivity
So proper now we now have a Blazor App that is predominantly statically rendered, with quick and Website positioning-friendly with none downsides
of the Blazor’s Interactivity choices, however how a lot of our App’s performance can we implement with out Blazor Interactivity?
What does not work with Enhanced Navigation
As of now we have managed to implement many of the required performance with Vanilla JS, nonetheless for any reasonably advanced
UI you may possible need to use one of many common JavaScript UI libraries, of which we consider Vue.js
is one of the best library for progressively enhancing statically rendered content material which gives one of the best steadiness of options,
efficiency and dimension.
The issue being that the pure manner to make use of Vue.js to progressively improve HTML content material does not work with Blazor’s
Enhanced Navigation.
E.g the pure method to implement Blazor’s Counter.razor
web page in Vue is to implement its UI in HTML and use JavaScript
to mount the element with the factor:
<div id="content material">
<p class="my-4">Present depend: {{currentCount}}</p>
<primary-button v-on:click on="incrementCount">Click on me</primary-button>
</div>
<script sort="module">
import { ref } from 'vue'
import { mount } from 'app.mjs'
const App = {
setup() {
const currentCount = ref(0)
const incrementCount = () => currentCount.worth++
return { currentCount, incrementCount }
}
}
mount('#content material', App)
</script>
Which as you’d anticipate works in full web page reloads, however not with Enhanced Navigation, the place no JavaScript
is re-executed upon navigation, leaving it as inert HTML.
Declarative Vue Elements
Fortunately we will use the identical method we used for loading JavaScript modules to load Vue.js parts, through the use of the
data-component
attribute to specify which world Vue element to mount with any properties optionally
specified within thedata-props
attribute, e.g:
<div data-component="GettingStarted" data-props="{template:'blazor'}"></div>
This does require guaranteeing all parts loaded this fashion are registered as a worldwide element, as finished in:
import GettingStarted from "./parts/GettingStarted.mjs"
/** Shared World Elements */
const Elements = {
GettingStarted,
}
export perform mount(sel, element, props) {
const app = createApp(element, props)
Object.keys(Elements).forEach(title => {
app.element(title, Elements[name])
})
app.mount(doc.querySelector(sel))
}
Nevertheless this additionally signifies that all world parts would should be downloaded earlier than any Vue Elements could be rendered
the primary time an internet site is accessed. Which wont be a difficulty after the primary web page is loaded after the browser caches all
its JS Module dependencies, however we will do higher.
Lazy Loading Vue Elements
To keep away from this we will as an alternative use the data-component
attribute to specify the trail to the Vue element to load,
guaranteeing that solely the Vue parts required for the present web page is loaded, e.g:
<div data-component="pages/Counter.mjs"></div>
Which is how we will implement Vue Elements that work in each statically rendered and enhanced navigation pages:
import { ref } from 'vue'
export default {
template: `
<p class="my-4">Present depend: {{currentCount}}</p>
<PrimaryButton @click on="incrementCount">Click on me</PrimaryButton>
`,
setup() {
const currentCount = ref(0)
const incrementCount = () => currentCount.worth++
return { currentCount, incrementCount }
}
}
The brand new Blazor Vue Template
This finally ends up being how the Interactive options within the new blazor-vue template
are carried out – best for constructing quick, Website positioning-friendly statically rendered Blazor Internet Apps the place all its dynamic functionally
makes use of Vue.js to progressively improve its static rendered content material – eliminating Blazor’s present limitations of having the ability to
use Blazor static SSR to implement a complete App with:
Blazor Vue Tailwind Template
The brand new blazor-vue template implements all of the options of the
blazor template however reimplements all its interactive options with
Vue.js to and the Vue Components library.
Create a brand new Blazor Vue Tailwind mission together with your most popular mission title:
Quicker iterative growth
Different advantages of utilizing Vue for Interactivity is the quick iterative suggestions loop throughout growth that even applies
to its Markdown-powered Blog which itself can embed wealthy interactive Vue Elements and wealthy JavaScript UIs
like Chart.js in its Markdown Blog Posts because of its unapologetic, complexity-free
#NoBuild resolution.
Blazor App Tailwind Template
Alternatively the Blazor Project Template is for C# Builders preferring
to make use of Blazor end-to-end for all App performance, which makes use of Blazor Server and
ServiceStack.Blazor Components on its Pages requiring Interactivity:
While Blazor Interactivity might stay the predominant resolution amongst .NET builders we consider .NET 8 Blazor opens the doorways
for progressively enhanced statically-rendered Blazor Apps which has now turn into our most popular resolution for creating
most .NET Internet Apps.
It overcomes our greatest gripe with Blazor Internet Meeting, that we have been unsuccessful in
prerendering away its poor startup efficiency and UI jank
in Web Apps.
Blazor Vue Diffusion
So once we realized about .NET 8’s static default rendering mode and enhanced navigation we jumped on the alternative to
create the Blazor Vue template which was used to re-implement Blazor Diffusion with Blazor SSR and Vue.js – a statically
rendered Blazor App that makes use of Vue.js for all its performance.
https://blazordiffusion.com
Blazor Diffusion is our Blazor Demo App we used to showcase the way you
may use Universal API Components to construct Blazor Elements and whole Blazor Apps
whose supply code runs in each Blazor Server and Blazor Internet Meeting Interactive modes, which was first
developed with Blazor Server then used a
sync.bat script to export its supply code into
a Blazor Web Assembly mission that was deployed as an alternative.
The Blazor Vue model begins from a clear slate, using statically rendered Blazor for sooner web page hundreds and producing
Website positioning-friendly content material:
We’re more than happy with the outcomes, a lot sooner loading instances, enhanced navigation, no UI jankiness, higher Website positioning – primarily
a greater UX total, regardless of not needing any prerendering resolution – all while having fun with a sooner iterative growth expertise
the place all Vue element modifications have been instantly seen after save.
You possibly can examine the variations of every Blazor Answer from the Stay Demos under:
All Stay Demos are hosted on a shared Hetzner Cloud VM utilizing SQLite that is replicated to Cloudflare R2 with Litestream