"Three projects. Dozens of folders. Which file goes where — and why does it even matter?"

Quick Answer

Blazor .NET 8 introduced a unified rendering model with Auto render mode — components can start as Server-side rendered, switch to WebAssembly after the runtime downloads, and use Static SSR for SEO-critical pages. This eliminates the previous forced choice between Blazor Server (always connected) and Blazor WebAssembly (large download).

Render modesStatic SSR (no interactivity, best SEO), Interactive Server (SignalR, instant startup), Interactive WebAssembly (offline capable), Auto (hybrid)
.NET 8 key featurePer-component render mode — mix static and interactive components in the same app without choosing a global mode
Common mistakeUsing Interactive Server for the entire app when only a few components need interactivity — wastes SignalR connections
AZ-204 relevanceBlazor hosted apps deploy to Azure App Service — understand how WASM apps differ from Server apps in Azure deployment

When you create a Blazor Web App in Visual Studio 2022 with .NET 8, the template generates three separate projects. This surprises most developers who are used to a single project. This guide explains the architecture clearly — what each project does, what goes inside it, how they talk to each other, and the rules that keep everything clean.

🏗️ The Three Projects — One Sentence Each

Every Blazor Web App solution contains exactly three projects. Think of them as three rooms in a building, each with a specific purpose:

🖥️
Server — SkillverseApp
The back room. Runs on your machine or Azure. Owns the database connection, EF Core, API endpoints, and the startup configuration in Program.cs. This is the host — it starts everything.
🌐
Client — SkillverseApp.Client
The shop floor. Contains all .razor components and pages that the user sees. In Auto render mode, this eventually runs in the browser as WebAssembly. In Server mode, it streams from the server via SignalR.
📦
Shared — SkillverseApp.Shared
The shared vocabulary. A plain C# class library referenced by BOTH Server and Client. Contains models, DTOs, interfaces, and validation logic. No EF Core, no Blazor, no ASP.NET — just pure C#.
"The Shared project is like the contract both sides sign. If Server and Client both know what a TopicDto looks like, they can exchange it over HTTP without ever misunderstanding each other."

📁 Complete Folder Structure — Every File Explained

Here is what your solution looks like after creation, with a plain explanation of every file and folder:

Solution
SkillverseApp.sln
The top-level container Visual Studio opens. Lists all three projects and their relationships. Never edit this by hand.

🖥️ Server Project — SkillverseApp/

File / Folder
What Goes Here
Program.cs
Entry point. Register all services: DbContext, Repositories, Auth, CORS, Blazor pipeline. This is the single most important file in the project.
appsettings.json
Connection strings, API keys, logging levels. Never commit real secrets — use User Secrets locally and environment variables in production.
Data/AppDbContext.cs
Your EF Core DbContext. Registered in Program.cs via AddDbContext. Contains DbSet properties — one per entity table. Only the Server touches the database directly.
Repositories/
ITopicRepository, EmployeeRepository etc. Registered in Program.cs as Scoped. Injected into controllers or server components. Hides EF Core from the rest of the app.
Controllers/
Minimal API endpoints or Web API controllers. The Client calls these via HttpClient. Example: GET /api/topics returns List<TopicDto> (from Shared).
wwwroot/
Static files served by the server: app.css, favicon, images. The Client also has its own wwwroot for WASM-side assets.

🌐 Client Project — SkillverseApp.Client/

File / Folder
What Goes Here
Program.cs
Client-side entry point. Registers client-only services: HttpClient base address, client state management. Much lighter than the server Program.cs.
Pages/
Routable pages. Each .razor file with @page "/route" is a page mapped to a URL. Examples: Home.razor, TopicList.razor, TopicDetail.razor.
Components/
Reusable non-routable components: TopicCard.razor, NavMenu.razor, LoadingSpinner.razor. Used inside pages with <TopicCard Topic="@topic" />. No @page directive.
Layout/MainLayout.razor
The shell of the app. Contains the sidebar or top nav and the @Body placeholder where page content renders. Every page renders inside this unless overridden.
App.razor
The root of the component tree. Contains the Router that maps URLs to pages. You rarely edit this file.

📦 Shared Project — SkillverseApp.Shared/

File / Folder
What Goes Here
Models/ (DTOs)
TopicDto.cs, EmployeeDto.cs — plain C# classes that travel as JSON over HTTP. Server maps EF entities to these. Client deserialises them and binds to the UI.
Interfaces/
ITopicRepository, IEmailService etc. Defined here so both projects agree on the contract. Server implements them. Client can mock them in tests.
Validation/
DataAnnotations or FluentValidation rules on DTOs. Because these classes live in Shared, the same validation runs on both client (instant feedback) and server (authoritative check) automatically.
Enums & Constants
TopicLevel.cs, UserRole.cs, AppConstants.cs — shared across both projects so neither duplicates them.

🔄 The Request Lifecycle — What Happens When a User Opens a Page

When a user navigates to /topics in the browser, here is the exact sequence of events:

1
User navigates to /topics
Browser sends a request. The Blazor Router in App.razor matches the URL to TopicList.razor (because it has @page "/topics").
↓
2
OnInitializedAsync fires
The component's lifecycle method runs. It calls: topics = await Http.GetFromJsonAsync<List<TopicDto>>("/api/topics"); HttpClient is injected — it points to the Server project.
↓
3
API Controller on Server receives GET /api/topics
TopicsController calls the injected ITopicRepository.GetAllAsync(). The repository queries the database via EF Core and returns a List<Topic> (the full entity).
↓
4
Controller maps Entity → DTO
The controller converts the EF Core Topic entity to TopicDto (from the Shared project). Only the fields the UI needs are included. Returns JSON.
↓
5
Client deserialises JSON → TopicDto list
The Client project's HttpClient receives the JSON response and deserialises it using the same TopicDto class from the Shared project. Same class, both sides — no mismatch possible.
↓
6
StateHasChanged — component re-renders
The topics list is now populated. Blazor detects the state change and re-renders the component. The user sees the topic list. Total round trip: typically under 100ms on a local machine.

⚙️ Render Modes — The .NET 8 Superpower

In .NET 8, Blazor lets you choose the render mode per component or per page. This is the biggest change from previous Blazor versions. You are not locked into one mode for the whole application.

Mode
How It Works
Best For
How to Set
Static SSR
Rendered on server, sent as static HTML. No interactivity.
Read-only content pages, marketing pages
Default — no attribute needed
InteractiveServer
Runs on server, streams UI updates to browser via SignalR. Full .NET access.
Most pages — fastest to first render, full server access
@rendermode InteractiveServer
InteractiveWebAssembly
Compiled to WASM, runs in browser. No server round-trips after load.
Offline-capable tools, low-latency interactive UIs
@rendermode InteractiveWebAssembly
InteractiveAuto ✅
Starts as Server mode (instant first render), downloads WASM in background, switches to WebAssembly on next visit.
Best default for most apps
@rendermode InteractiveAuto
💡 Render Mode Placement You set render mode in two places: globally in App.razor (applies to all pages), or per-page/per-component using the @rendermode directive at the top of a .razor file. Per-component overrides the global setting. This means you can have a static marketing homepage and a fully interactive dashboard in the same app.

🗄️ EF Core Entity vs Shared DTO — The Most Important Distinction

This is the concept that trips up every developer new to Blazor Web App architecture. You have two different representations of "a topic" — and confusing them causes bugs.

EF Core Entity (Server only)
  • Lives in the Server project (or in Server/Data/)
  • Has navigation properties: public List<Enrollment> Enrollments { get; set; }
  • Has EF Core annotations: [Required], [MaxLength], [Column]
  • Never sent over the wire — EF Core would serialise circular references and crash
  • Maps directly to a database table
DTO (Shared project)
  • Lives in the Shared project
  • No navigation properties — flat, simple structure
  • Only the fields the UI actually needs — nothing extra
  • Travels safely as JSON over HTTP between Server and Client
  • Used for API responses AND for form binding in the Client
"The EF Core entity is the full internal representation — like an employee file with every detail. The DTO is what you hand to the employee at the front desk — only what they need to see."

⚖️ The Golden Rules — What Goes Where

Use this as a quick reference whenever you are not sure where a new file belongs:

🗄️
Does it touch the database? → Server
DbContext, EF Core migrations, Repositories, and connection strings all go in the Server project. The Client runs in the browser (WASM) — there is no SQL Server in a browser. The Client calls an HTTP API. The Server talks to the database.
🖱️
Is it something the user sees? → Client
.razor pages with @page, reusable components, layouts, App.razor, client-side state services — all go in the Client project. If it renders in the browser, it belongs here.
🤝
Does both Client and Server need it? → Shared
DTOs that travel as JSON, repository interfaces, validation rules, enums, and constants all go in Shared. If duplicating it in both projects would be a smell, it belongs here.
🚫
Never put these in Shared
No EF Core. No Microsoft.AspNetCore. No Blazor-specific code. Shared is a plain class library. If you add a package reference to EF Core in Shared, you have broken the architecture — the Client will try to compile EF Core into WebAssembly, which fails.

🔗 Project References — How They Wire Together

The reference graph is deliberately one-directional. Understanding why prevents circular dependency errors:

Server
References → Shared AND hosts → Client
Server references Shared to use DTOs and interfaces. Server also references Client because it is the host that serves the Client project's compiled output to the browser. This is configured automatically by the Blazor Web App template.
↓
Client
References → Shared ONLY
Client references Shared for DTOs, interfaces, and validation rules. Client does NOT reference Server. The Client has no knowledge of EF Core, the database, or any server-only code. The connection is only via HTTP.
↓
Shared
References → Nothing
Shared references no other project in the solution. It is a pure class library with no dependencies on Server or Client. This is what makes it safe to reference from both. If Shared references Server, you get a circular dependency and the build fails.
📌 In .csproj Terms In Server.csproj you see two ProjectReference entries: one to Shared and one to Client. In Client.csproj you see one ProjectReference: to Shared only. In Shared.csproj you see zero ProjectReference entries. That asymmetry is intentional and correct.

⚠️ The Five Most Common Architecture Mistakes

  • Putting DbContext in the Client project. The Client compiles to WebAssembly — there is no SQL Server in a browser. DbContext, EF Core, and anything database-related must live in the Server project only.
  • Sending EF Core entities directly over the API instead of DTOs. EF Core entities have navigation properties that create circular references. JSON serialisation will either throw a StackOverflowException or produce enormous, incorrect output. Always map to a DTO before returning from a controller.
  • Adding EF Core as a NuGet package to the Shared project. This forces the Client to include EF Core in its WebAssembly compilation — which either fails outright or produces a bloated WASM bundle. Shared must stay dependency-free.
  • Calling the database directly from a Blazor component. Even in InteractiveServer mode where you could technically inject AppDbContext into a component, this bypasses your repository layer, makes testing impossible, and mixes concerns. Always go: Component → Service/Repository → DbContext.
  • Forgetting to set the render mode when interactivity is needed. In .NET 8, pages default to Static SSR. A button's @onclick handler will silently do nothing unless you add @rendermode InteractiveServer or InteractiveAuto to the page or component. This is the number one "my button doesn't work" question in Blazor.

🧠 Hands-On — Create Your First Blazor Web App and Explore the Structure

Follow these steps to create a project, explore the structure, and verify your understanding before adding any features:

  1. Create the project in Visual Studio 2022
    File → New → Project → Search "Blazor Web App" → Select it (C#, .NET) → Name: SkillverseApp → Framework: .NET 8.0 → Authentication: None → Interactive render mode: Auto → Interactivity location: Per page/component → Click Create.
  2. Explore Solution Explorer
    Expand all three projects. Verify you see SkillverseApp (Server), SkillverseApp.Client, and SkillverseApp.Shared. Open each Program.cs and note how different they are — Server is long, Client is short.
  3. Verify project references
    Right-click each project → Properties → look at "Project references" (or open each .csproj). Confirm: Server references Client + Shared. Client references Shared only. Shared references nothing.
  4. Trace a page from URL to component
    Open SkillverseApp.Client → Pages → Weather.razor. Find the @page directive at the top. Run the app (F5). Navigate to /weather. Confirm the component renders. Add a Console.WriteLine in OnInitializedAsync and check whether it appears in the browser console or the VS Output window. Which tells you which render mode is active?
  5. Add your first DTO to Shared
    In SkillverseApp.Shared, add a Models folder. Create TopicDto.cs with Id (int), Title (string), Level (string), Price (decimal). Build the solution. Confirm both Server and Client can see the class without any additional reference.
What Comes Next The next article covers building your first full-stack Blazor feature end-to-end: EF Core entity → Repository → API Controller → HttpClient → Blazor page → bound UI. Every layer connected, every file in its right place.