MudBlazor Starter

Blazor MudBlazor Starter

Production-ready Blazor Server starter template with MudBlazor Material Design components. Built on .NET 9 with Docker multi-arch support, GHCR publishing, Azure App Service deployment, CodeQL, and GitHub Pages documentation.

Live demo · GitHub repository


Page Description
Getting Started Prerequisites, local run, Docker run
Project Structure Directory layout and file descriptions
Components Blazor components reference
Configuration App settings, build flags, environment variables
Deployment Docker, CI/CD, and Azure deployment
Documentation Site Astro Pages authoring, Sätteri, and validation workflow

Key Features

  • Pre-configured MudBlazor layout with purple app bar, navigation drawer, compact breadcrumbs, project links, and dark mode toggle
  • Productized demo pages: Overview, Counter demo, and DataGrid demo with Add/Edit/Remove dialogs
  • DataGrid showcase with 69,420 virtualized rows, shortened record IDs, row selection, paging, and right-click clipboard copy
  • Multi-architecture Docker image (AMD64 + ARM64) with ASP.NET Core health endpoint at /healthz
  • Production-optimized builds with optional AOT plus ReadyToRun, trimming, and extra optimization support
  • CI/CD pipeline: PR build checks with container health verification, main branch release to GHCR and Azure
  • Renovate dependency updates through the shared github>jonathanperis/.github preset
  • GitHub Pages documentation site with project overview, reference docs, and deployment notes

Components

Layout Components

MainLayout.razor

The root layout component that provides the MudBlazor application shell. Inherits from LayoutComponentBase and implements IBrowserViewportObserver for responsive design.

Features:

  • MudThemeProvider with bindable dark mode toggle, persisted to localStorage
  • Purple MudAppBar branded as MudBlazor Starter, with drawer toggle, dark mode control, and project overflow menu
  • MudDrawer with MudNavMenu links labeled Overview, Counter demo, and DataGrid demo
  • Project overflow links for GitHub, documentation, and health check
  • MudPopoverProvider, MudDialogProvider, and MudSnackbarProvider for MudBlazor services
  • Responsive breakpoint detection: displays a MudToggleIconButton on small screens and a MudSwitch on larger screens
  • Semantic <main> wrapper and accessible labels for shell controls
  • UI state (dark mode, drawer open, screen size) persisted to localStorage and restored on first render

Key behavior:

  • Subscribes to IBrowserViewportService for breakpoint change notifications
  • Implements IAsyncDisposable to unsubscribe from viewport events
  • Delays rendering until localStorage state is loaded to prevent flash of unstyled content

A reusable compact breadcrumb navigation component that wraps MudBreadcrumbs in a MudContainer instead of a heavy raised card.

Parameters:

Parameter Type Description
Items List<BreadcrumbItem> List of breadcrumb items to display

Uses a custom separator template with MudIcon (arrow forward icon) and an aria-label="Breadcrumb" navigation label. Each page defines its own breadcrumb items and passes them to this component.


Page Components

Home.razor

Route: /

Production-oriented starter overview page. It presents a concise hero, proof chips, GitHub/docs/DataGrid CTAs, a clone/run command block, and feature cards that explain the included deployment, documentation, and MudBlazor UI patterns.

Counter.razor

Route: /counter

Interactive Counter demo page. Shows a prominent current count value, a primary Increment count button, and a Reset button that is disabled while the count is zero. Demonstrates Blazor component state and event handling without reading like untouched scaffold filler.

Weather.razor

Route: /weather

Full-featured DataGrid demo page demonstrating CRUD operations, virtualization, row selection, paging, and clipboard integration.

Features:

  • MudDataGrid with 69,420 generated weather forecast entries
  • Header framed as MudDataGrid showcase with capability chips for virtualization, CRUD dialogs, and right-click copy
  • Action row with Add record, Remove selected, and a selected-count chip
  • Remove selected stays disabled until at least one row is selected
  • Shortened record IDs via ShortId(Guid) to avoid full GUID visual noise
  • Date, Temperature (C/F), and Summary columns without duplicated stress-test columns
  • Multi-selection support with SelectColumn
  • Quick filter search across displayed columns
  • Sortable and filterable columns with SortMode.Multiple
  • Virtualized rendering for performance with large datasets
  • Fixed header with configurable page sizes (10, 25, 50, 100, 500, 1000, 5000)
  • Loading state with simulated 2-second delay

CRUD Operations:

  • Add: opens AddWeather dialog via IDialogService, appends a new entry, and shows Weather record added.
  • Edit: opens EditWeather dialog with the selected item and replaces the entry in-place
  • Remove: opens RemoveWeather confirmation dialog and removes all selected items

Context Menu:

  • Right-click on a row to copy a single line or all selected lines to the clipboard
  • Clipboard data is formatted as semicolon-separated values
  • Empty-selection snackbar messages explicitly tell the user to select rows first

Data model (WeatherForecast): Defined as a nested class with Id (Guid), Date (DateTime), TemperatureC (int), Summary (string?), and computed TemperatureF.

Error.razor

Route: /Error

Error page that displays when an unhandled exception occurs. Shows the request ID from Activity.Current or HttpContext.TraceIdentifier when available. Includes guidance about the Development environment.


Weather Dialog Components

AddWeather.razor

A MudDialog wrapped in an EditForm with DataAnnotationsValidator. Provides text fields for Weather ID (Guid), Date, Temperature (C), and Summary. On valid submission, returns the new WeatherForecast via DialogResult.Ok and shows a success snackbar notification.

EditWeather.razor

A MudDialog wrapped in an EditForm for editing an existing weather entry.

Parameters:

Parameter Type Description
Item Weather.WeatherForecast The weather entry to edit

The Weather ID field is read-only. On valid submission, returns the edited item via DialogResult.Ok and shows a success snackbar notification.

RemoveWeather.razor

A simple MudDialog confirmation prompt. Displays a warning message asking the user to confirm deletion. On confirmation, returns DialogResult.Ok(true) and shows a success snackbar notification. Does not use EditForm since no data input is required.


MudBlazor Components Used

Component Usage
MudLayout, MudAppBar, MudDrawer, MudMainContent Application shell structure
MudThemeProvider Material Design theming with dark mode
MudNavMenu, MudNavLink Side navigation
MudBreadcrumbs Compact page navigation breadcrumbs
MudDataGrid, PropertyColumn, SelectColumn, TemplateColumn DataGrid demo table
MudDataGridPager Data grid pagination
MudDialog, MudDialogProvider Modal dialogs for CRUD operations
MudSnackbar, MudSnackbarProvider Toast notifications
MudButton, MudIconButton, MudToggleIconButton Action buttons
MudTextField Form inputs and search
MudCard, MudCardHeader, MudCardContent, MudCardActions Content cards
MudMenu, MudMenuItem Context menu and overflow menu
MudSwitch Dark mode toggle (large screens)
MudText, MudLink, MudSpacer, MudDivider, MudIcon, MudChip Typography and layout utilities
MudPopoverProvider Popover rendering

Configuration

Application Settings

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Key Default Description
Logging:LogLevel:Default Information Minimum log level for all categories
Logging:LogLevel:Microsoft.AspNetCore Warning Log level for ASP.NET Core framework logs
AllowedHosts * Allowed host headers (all hosts by default)

appsettings.Development.json

Overrides for local development. Currently mirrors the base logging configuration.

Environment Variables

Variable Default Description
ASPNETCORE_ENVIRONMENT Production Set to Development for local dev (auto-set by launch profiles)
ASPNETCORE_URLS http://+:5000 Listening URL (set in Dockerfile for container builds)
APPLICATIONINSIGHTS_CONNECTION_STRING unset locally; set by Azure Bicep Enables Application Insights telemetry when present
APPINSIGHTS_INSTRUMENTATIONKEY unset locally; set by Azure Bicep Legacy instrumentation key exposed for Azure App Service/Application Insights compatibility
ApplicationInsightsAgent_EXTENSION_VERSION unset locally; ~3 in Azure Enables the Azure App Service Application Insights site extension

Application Insights is inactive for local development unless you provide APPLICATIONINSIGHTS_CONNECTION_STRING. In Azure, the Bicep deployment wires the connection string and instrumentation key into Web App application settings.

.NET SDK Version

Pinned in global.json:

{
  "sdk": {
    "version": "9.0.202",
    "rollForward": "minor"
  }
}

The rollForward: minor policy allows using any 9.0.x SDK version at or above 9.0.202.

MudBlazor Service Registration

In Program.cs, MudBlazor is registered with:

builder.Services.AddMudServices();
builder.Services.AddMudTranslations();
MudGlobal.UnhandledExceptionHandler = Console.WriteLine;
  • AddMudServices() registers all MudBlazor services (dialogs, snackbar, scroll, resize, etc.)
  • AddMudTranslations() registers localization support (MudBlazor.Translations 3.3.0)
  • MudGlobal.UnhandledExceptionHandler routes unhandled exceptions to the console

Docker Build Arguments

The Dockerfile accepts four build arguments that control the compilation output:

Argument Default Values Description
AOT false true, false Enable Ahead-of-Time compilation for faster startup
TRIM false true, false Enable ReadyToRun, single-file publish, and self-contained deployment
EXTRA_OPTIMIZE false true, false Strip symbols, disable debugger support, invariant globalization
BUILD_CONFIGURATION Debug Debug, Release .NET build configuration

Build Flag Details

AOT (AOT=true): Enables PublishAot with OptimizationPreference set to Speed. Produces a native binary with faster cold-start performance.

TRIM (Trim=true): Enables PublishReadyToRun, PublishReadyToRunComposite, PublishSingleFile, and SelfContained. Produces an optimized single-file deployment.

EXTRA_OPTIMIZE (ExtraOptimize=true): Applies aggressive optimizations for minimal binary size:

  • TrimmerRemoveSymbols – removes debug symbols
  • DebuggerSupport – disabled
  • InvariantGlobalization – uses invariant culture
  • EventSourceSupport – disabled
  • HttpActivityPropagationSupport – disabled
  • MetadataUpdaterSupport – disabled
  • StackTraceSupport – disabled
  • UseSystemResourceKeys – uses system resource keys instead of embedded strings

Production Defaults

The main-release.yml pipeline uses these defaults:

AOT=false
TRIM=true
EXTRA_OPTIMIZE=true
BUILD_CONFIGURATION=Release

Getting Started

Prerequisites

Run Locally

git clone https://github.com/jonathanperis/blazor-mudblazor-starter.git
cd blazor-mudblazor-starter
dotnet restore
dotnet run --project src/WebClient

Open http://localhost:5000 in your browser.

The https launch profile is also available at https://localhost:5001.

Run with Docker

Build the image from the src/ context using the Dockerfile inside src/WebClient/:

docker build -t blazor-mudblazor -f src/WebClient/Dockerfile src/
docker run -p 5000:5000 blazor-mudblazor

Open http://localhost:5000 in your browser.

Docker Build Arguments

You can pass build arguments to control optimization:

docker build \
  --build-arg AOT=false \
  --build-arg TRIM=true \
  --build-arg EXTRA_OPTIMIZE=true \
  --build-arg BUILD_CONFIGURATION=Release \
  -t blazor-mudblazor -f src/WebClient/Dockerfile src/

See Configuration for details on each build argument.

Access URLs

Context URL
Local (HTTP) http://localhost:5000
Local (HTTPS) https://localhost:5001
Docker container http://localhost:5000
Live demo blazor-mudblazor-starter

Deployment

Docker

Build the Image

The Dockerfile uses a multi-stage build with the .NET 9 SDK and ASP.NET runtime images. The build context is the src/ directory.

docker build -t blazor-mudblazor -f src/WebClient/Dockerfile src/

With production optimizations:

docker build \
  --build-arg AOT=false \
  --build-arg TRIM=true \
  --build-arg EXTRA_OPTIMIZE=true \
  --build-arg BUILD_CONFIGURATION=Release \
  -t blazor-mudblazor -f src/WebClient/Dockerfile src/

Run the Container

docker run -p 5000:5000 blazor-mudblazor

The container listens on port 5000 (ASPNETCORE_URLS=http://+:5000). The entry point is the compiled ./WebClient binary.

Multi-Architecture Support

The release pipeline builds both linux/amd64 and linux/arm64/v8 images. It uses Docker Buildx for both builds, QEMU for the arm64 job, and then merges both digests into the multi-arch :latest manifest. The Dockerfile installs clang and zlib1g-dev in the SDK stage so optional AOT compilation has the native toolchain it needs.

Pre-built Image

The latest release image is available from GitHub Container Registry:

docker pull ghcr.io/jonathanperis/blazor-mudblazor-starter:latest
docker run -p 5000:5000 ghcr.io/jonathanperis/blazor-mudblazor-starter:latest

CI/CD Pipelines

build-check.yml (Pull Requests)

Triggered on pull requests to main. Runs two jobs:

  1. setup-build-test: Sets up the .NET SDK from global.json, restores dependencies, and builds the project with debug settings (AOT=false, TRIM=false, BUILD_CONFIGURATION=Debug).

  2. container-test: Builds a Docker image, runs the container on port 5030, and polls the /healthz endpoint up to 20 times (5-second intervals) to verify the application starts correctly. Fails the pipeline if the health check does not return HTTP 200.

main-release.yml (Main Branch)

Triggered on push to main or manual dispatch. The current release flow is split into six jobs:

  1. setup-build-test: Restores and builds with production settings (AOT=false, TRIM=true, EXTRA_OPTIMIZE=true, BUILD_CONFIGURATION=Release).

  2. build-push-amd64: Sets up Docker Buildx, authenticates to GitHub Container Registry, builds the linux/amd64 image, and pushes it as ghcr.io/jonathanperis/blazor-mudblazor-starter:latest.

  3. deploy-infra: Logs in to Azure with OIDC (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID) and deploys infra/main.bicep/infra/main.bicepparam with azure/arm-deploy.

  4. deploy-image: Deploys the GHCR :latest image to Azure App Service with azure/webapps-deploy and the AZURE_WEBAPP_PUBLISH_PROFILE secret.

  5. build-push-arm64: Sets up QEMU and Docker Buildx, builds linux/arm64/v8, and pushes it as :latest-arm64.

  6. merge-manifest: Combines the amd64 and arm64 digests into the final multi-arch :latest manifest.

codeql.yml

Runs CodeQL security analysis on the codebase.

deploy.yml (GitHub Pages)

Triggered on push to main or manual dispatch. Delegates to the reusable jonathanperis/.github/.github/workflows/pages-docs-deploy.yml@main workflow with inherited secrets; that shared workflow builds the Astro docs from docs/ and publishes the generated Pages artifact.


Azure Web App

The application is deployed to Azure App Service in the Brazil South region. The workflow first keeps the Azure resources current with Bicep over OIDC, then deploys the GHCR container image to the Web App with the Azure publish profile.

The Bicep entry point creates or updates the App Service Plan, Web App, Log Analytics Workspace, and Application Insights instance. The App Service Plan is provisioned from infra/modules/appServicePlan.bicep before the Web App module consumes its resource ID.

Live demo: blazor-mudblazor-starter

Azure Deployment Requirements

  • Resource group: github-jonathanperis
  • Region: brazilsouth
  • App Service Plan: github-jonathanperis (B1, Linux)
    • The plan is created or updated by infra/modules/appServicePlan.bicep.
    • infra/main.bicepparam controls the plan name and appServicePlanSku.
  • Web App name: blazor-mudblazor-starter
  • OIDC secrets for infrastructure deployment: AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID
  • The AZURE_WEBAPP_PUBLISH_PROFILE secret set in the GitHub repository settings (download from Azure Portal > Web App > Deployment Center > Manage publish profile)
  • GHCR image access configured on the Azure Web App (the image is public via GitHub Packages)

Observability

infra/main.bicep provisions Log Analytics and Application Insights, then passes telemetry settings into the Web App module. The app only registers AddApplicationInsightsTelemetry() when APPLICATIONINSIGHTS_CONNECTION_STRING is present, so local runs stay telemetry-free by default while Azure deployments emit telemetry automatically.

Azure app settings managed by Bicep:

Setting Purpose
APPLICATIONINSIGHTS_CONNECTION_STRING Enables Application Insights telemetry in Program.cs
APPINSIGHTS_INSTRUMENTATIONKEY Compatibility setting for App Service/Application Insights integration
ApplicationInsightsAgent_EXTENSION_VERSION Enables the App Service Application Insights extension (~3)

Documentation Site

The public documentation is an Astro static site in docs/ and is published to GitHub Pages by .github/workflows/deploy.yml.

Authoring Model

Area Purpose
docs/wiki/*.md Markdown source pages rendered under /docs/
docs/src/lib/sidebar.config.ts Navigation order and section grouping for Markdown pages
docs/src/components/home/ Custom landing page sections for the GitHub Pages root
docs/astro.config.mjs Astro configuration, static output, base path, sitemap, Tailwind, and Markdown processor
docs/out/ Generated static output from bun run build

Local Commands

Use Bun from the docs/ directory:

cd docs
bun install
bun run build
bun run check:rendered

Run the source-backed drift check from the repository root:

python3 scripts/check-docs-drift.py

Adding or Renaming a Page

  1. Add or rename the Markdown file in docs/wiki/.
  2. Add the page slug to SECTION_CATEGORIES in docs/src/lib/sidebar.config.ts.
  3. Link to the page with root-relative docs routes such as ../configuration/ from another docs page, or /blazor-mudblazor-starter/docs/configuration/ when authoring absolute public links.
  4. Run bun run build, bun run check:rendered, and python3 scripts/check-docs-drift.py.

Markdown Processor

The site uses Astro 6.4 with the Rust-based Sätteri Markdown processor:

import { satteri } from '@astrojs/markdown-satteri';

export default defineConfig({
  markdown: {
    processor: satteri(),
  },
});

Sätteri is used for faster Markdown builds. The rendered HTML smoke test protects the important Markdown features this site depends on: routes, headings and anchors, tables, fenced code blocks, and internal links.

Deployment

.github/workflows/deploy.yml delegates to the reusable jonathanperis/.github/.github/workflows/pages-docs-deploy.yml@main workflow with inherited secrets. The reusable workflow installs the docs dependencies, builds the Astro site, and publishes the generated Pages artifact.

Project Structure

blazor-mudblazor-starter/
├── .github/
│   └── workflows/
│       ├── build-check.yml             # PR validation: .NET build + Docker build + health check
│       ├── codeql.yml                  # CodeQL security analysis
│       ├── deploy.yml                  # GitHub Pages deployment
│       └── main-release.yml            # Release: optimized build + GHCR push + Azure deploy
├── src/
│   └── WebClient/
│       ├── Components/
│       │   ├── App.razor               # Root HTML document with MudBlazor CSS/JS imports
│       │   ├── Routes.razor            # Blazor router, defaults to MainLayout
│       │   ├── _Imports.razor          # Global using directives for all components
│       │   ├── Layout/
│       │   │   ├── MainLayout.razor    # App shell: app bar, drawer, dark mode, responsive breakpoints
│       │   │   └── Breadcrumb.razor    # Reusable breadcrumb navigation component
│       │   ├── Pages/
│       │   │   ├── Home.razor          # Production starter overview (route: /)
│       │   │   ├── Counter.razor       # Counter demo with increment/reset state (route: /counter)
│       │   │   ├── Weather.razor       # DataGrid demo with virtualization and CRUD (route: /weather)
│       │   │   └── Error.razor         # Error page with request ID display (route: /Error)
│       │   └── Weather/
│       │       ├── AddWeather.razor    # MudDialog for adding weather entries
│       │       ├── EditWeather.razor   # MudDialog for editing weather entries
│       │       └── RemoveWeather.razor # MudDialog for delete confirmation
│       ├── Properties/
│       │   └── launchSettings.json     # Local dev profiles (HTTP on 5000, HTTPS on 5001)
│       ├── wwwroot/                    # Static assets (favicon, CSS)
│       ├── appsettings.json            # Base configuration (logging, allowed hosts)
│       ├── appsettings.Development.json # Development logging overrides
│       ├── Dockerfile                  # Multi-stage .NET 9 build (AMD64 + ARM64)
│       ├── Program.cs                  # App entry point, MudBlazor service registration
│       └── WebClient.csproj            # Project file: .NET 9, MudBlazor 9.3.0, AOT/Trim flags
├── .editorconfig                       # Code style settings
├── .gitignore                          # Git ignore rules
├── global.json                         # .NET SDK version pin (9.0.202, roll-forward: minor)
├── renovate.json                       # Shared Renovate dependency update preset
├── LICENSE                             # MIT license
├── README.md                          # Project overview and quick start
└── WebClient.sln                       # Solution file

Key Directories

src/WebClient/Components/Layout/

Contains the application shell. MainLayout.razor provides the MudBlazor layout with app bar, navigation drawer, dark mode toggle (persisted via localStorage), and responsive breakpoint handling. Breadcrumb.razor is a reusable component that accepts a list of BreadcrumbItem parameters.

src/WebClient/Components/Pages/

Contains routable page components. Each page uses the compact Breadcrumb component for navigation context. Home presents the production-ready starter overview, Counter demonstrates component state, and the Weather route is framed as a DataGrid demo with MudDataGrid, dialog services, snackbar notifications, row selection, and clipboard integration.

src/WebClient/Components/Weather/

Contains MudDialog components used by the Weather page for Add, Edit, and Remove operations. Each dialog uses EditForm with DataAnnotationsValidator for form validation (except RemoveWeather, which is a simple confirmation).

.github/workflows/

Contains four GitHub Actions workflows: build-check.yml for PR validation (includes container health check against /healthz), main-release.yml for production releases to GHCR and Azure, codeql.yml for security analysis, and deploy.yml for GitHub Pages deployment. Dependency updates are configured separately in renovate.json, which inherits the shared github>jonathanperis/.github preset.