Vue Stable Diffusion

Vue Stable Diffusion Background
5 min read

Just as was created to showcase ServiceStack's Blazor Server and Blazor WASM project templates and components, we've also recreated a new Stable Diffusion UI in Vue to showcase the Razor SSG Project Template and Tailwind Vue Component Library that's now available at:

Blazor Diffusion with a Vue UI

Weighing close to 100 APIs, Blazor Diffusion is good representation of a medium-sized real-world App that can be used to compare the end user UX of different popular UI technologies used to develop Web Apps.

These Diffusion Apps are especially comparable as both Blazor WASM and Vue are both SSG Jamstack Apps deployed to a CDN which both access the same backend .NET APIs and both make use of the Tailwind Blazor Component Library and Vue Component Library rewritten in Vue, so any differences in UX are predominantly differences in what the UI technologies can deliver.

We'll look at covering the development workflow, productivity, startup and runtime performance of Blazor Server, Blazor WASM and Vue in a future post, for now you can compare their GitHub code-bases and Live Demos of each or download and run them locally to evaluate their code-base size, development workflow and performance to evaluate the different UI technologies:

Name Repo Live Demo
Vue NetCoreApps/VueDiffusion
Blazor WASM NetCoreApps/BlazorDiffusionWasm
Blazor Server NetCoreApps/BlazorDiffusion

It's best to evaluate Blazor Server by running it locally as it in particular has poor responsiveness when served over high internet latencies, but loads and runs exceptional well in low latency environments like Intranets which is the only environment where we'd recommend hosting it.

Razor SSG

Vue Diffusion is built differently from other Razor SSG Apps as instead of being pre-rendered from static content like Markdown documents, it's prerendered from APIs to render its dynamic Albums, Top and Latest pages at deployment which it does by configuring the App's Service Gateway to reference external Blazor Diffusion APIs:

    provider => new JsonApiClient(AppConfig.Instance.ApiBaseUrl!));

Resulting in all APIs invoked within Razor Pages being delegated to external Blazor Diffusion APIs as the data source to generate its prerendered Razor Pages.


For a preview of the development model of Razor SSG enhanced with Vue Components, checkout some of the different pages and their implementations:

Stable Diffusion Search Index.cshtml

Selected Image Artifacts.mjs

Top Images Top.cshtml

Most of these pages also utilize the reusable Vue 3 components defined in:

Stale-While-Revalidate APIs

We'll have a lot more to write up about our experiences with Vue Diffusion vs Blazor Diffusion in future Blog Posts, but we wanted to highlight the performance enhancing technique it uses to improve perceived performance between pages by utilizing @servicestack/vue new State-While-Revalidate (SWR) APIs.

Latency is the biggest performance killer when hosting Web Applications on the Internet, so much so that we'd historically look to start with a Single Page App template in order to provide the best UX up until the advent of native ES Modules support in modern browsers meant we could rid ourselves of SPA complexity and adopt a Simple, Modern JavaScript Multi Page App (MPA) approach combined with htmx's Boost feature to improve performance by avoiding full page reloads.

However we found that to be a fragile approach when navigating back/forward between pages as you'd need to be mindful of what scripts to place between <head> and <body> tags and which scripts need to be re-executed between navigations, reintroducing some of the stateful SPA complexity we want to avoid with a traditional MPA Web App.

We instead discovered we could get just as good UX with stateless full page reloads of pre-rendered HTML pages if we use SWR to fetch all the API data needed to render the page on first load:

This is easily achieved in reactive Vue.js UIs by invoking API requests with the new swr() client API where if the same API request had been run before it will execute the callback immediately with its "stale" cached results in localStorage first, before executing the callback again after receiving the API response with the latest data:

import { useClient } from "@servicestack/vue"
const client = useClient()

const results = ref([])
const topAlbums = ref([])

onMounted(async () => {
    await Promise.all([
        client.swr(request.value, api => {
            results.value = api.response?.results || []
        client.swr(new AnonData(), async api => {
            topAlbums.value = api.response?.topAlbums || []

This results in UIs being immediately rendered on load and if the API response has changed, the updated reactive collections will re-render the UI with the updated data.