Project Structure
June 1, 2026 · View on GitHub
This guide explains how Abies projects are organized, covering both browser (WASM) and server deployments.
Browser (WASM) Project
A minimal browser project:
MyApp.Wasm/
├── MyApp.Wasm.csproj ← BlazorWebAssembly SDK, Picea.Abies.Browser ref
├── Program.cs ← Entry point: Runtime.Run<App, Model, Unit>()
└── wwwroot/
├── index.html ← HTML shell with loading placeholder
└── css/
└── app.css ← Application styles
Key Files
Program.cs — The entry point. A single line starts the runtime:
await Picea.Abies.Browser.Runtime.Run<App, Model, Unit>();
Or with an interpreter for side effects:
await Picea.Abies.Browser.Runtime.Run<App, Model, Unit>(
interpreter: MyInterpreter.Handle);
wwwroot/index.html — The HTML shell. Include a loading placeholder for fast first paint:
<body>
<div id="main">Loading...</div>
</body>
.csproj — Uses the BlazorWebAssembly SDK for WASM compilation:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Picea.Abies.Browser" Version="2.1.*" />
</ItemGroup>
</Project>
Server Project
A minimal server project:
MyApp.Server/
├── MyApp.Server.csproj ← Web SDK, Picea.Abies.Server.Kestrel ref
├── Program.cs ← Entry point: MapAbies<App, Model, Unit>(...)
└── Properties/
└── launchSettings.json ← Development URLs and profiles
Key Files
Program.cs — Configures Kestrel and maps the Abies endpoint:
using Picea.Abies.Server.Kestrel;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapAbies<App, Model, Unit>("/",
mode: new RenderMode.InteractiveServer("/ws"),
interpreter: MyInterpreter.Handle);
app.Run();
.csproj — Uses the standard Web SDK:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Picea.Abies.Server.Kestrel" Version="2.1.*" />
</ItemGroup>
</Project>
Shared Application Logic
For real applications, extract the application logic into a shared library:
MyApp/
├── MyApp.csproj ← Class library with app logic
├── App.cs ← Program<Model, Unit> implementation
├── Model.cs ← Application state
├── Messages.cs ← Message types
└── Views/
├── HomePage.cs ← View functions for home page
├── ArticlePage.cs ← View functions for article page
└── Components.cs ← Shared view functions
MyApp.Wasm/
├── MyApp.Wasm.csproj ← References MyApp
└── Program.cs ← Runtime.Run<App, Model, Unit>()
MyApp.Server/
├── MyApp.Server.csproj ← References MyApp
└── Program.cs ← MapAbies<App, Model, Unit>(...)
This is the recommended structure for any non-trivial application. The shared library contains:
- All application logic — Model, Messages, Transition, View, Subscriptions
- No platform dependencies — References only
Picea.Abies(the core package)
The platform projects (Wasm and Server) are thin shells that only wire up the runtime.
Real-World Example: Conduit
The Conduit application is a full-featured social blogging platform (Medium clone) built with Abies. Here's its structure:
For current local run commands and testing entry points, see the dedicated Conduit README.
Picea.Abies.Conduit/ ← Shared application logic
├── Domain/ ← Domain model
│ ├── Article.cs
│ ├── Comment.cs
│ ├── Profile.cs
│ └── User.cs
├── ReadModel/ ← Query-side models
│ ├── ArticleFeed.cs
│ └── TagList.cs
└── ...other application concerns
Picea.Abies.Conduit.App/ ← MVU application
├── App.cs ← Program<Model, Unit>
├── Model.cs ← Application state
├── Messages.cs ← Message types
├── Routing.cs ← URL → Page routing
└── Views/ ← View functions
Picea.Abies.Conduit.Api/ ← REST API backend
├── Endpoints/ ← Minimal API endpoints
├── Authentication/ ← JWT auth
└── Infrastructure/ ← Database, DI
Picea.Abies.Conduit.Wasm.Host/ ← Browser host
└── Program.cs ← One-liner entry point
Picea.Abies.Conduit.Server/ ← Server host
└── Program.cs ← MapAbies + API endpoints
Picea.Abies.Conduit.Tests/ ← Unit tests
Picea.Abies.Conduit.Wasm.Tests/ ← WASM-specific tests
Picea.Abies.Conduit.Api.Tests/ ← API integration tests
Picea.Abies.Conduit.Testing.E2E/ ← Playwright E2E tests
Testing Structure
Abies projects follow a predictable test structure:
MyApp.Tests/ ← Unit tests for pure functions
├── TransitionTests.cs ← Test Transition(model, message) => (model, command)
├── ViewTests.cs ← Test View(model) => Document
└── RoutingTests.cs ← Test URL parsing and route matching
MyApp.Testing.E2E/ ← End-to-end tests (Playwright)
├── CounterTests.cs ← Full browser interaction tests
└── NavigationTests.cs ← Client-side routing tests
Because Transition and View are pure functions, unit testing is trivial:
[Test]
public async Task Increment_IncreasesCount()
{
var model = new Model(Count: 5);
var (result, command) = Counter.Transition(model, new CounterMessage.Increment());
await Assert.That(result.Count).IsEqualTo(6);
await Assert.That(command).IsTypeOf<Command.None>();
}
Naming Conventions
| Convention | Example |
|---|---|
| App library | MyApp |
| Browser host | MyApp.Wasm |
| Server host | MyApp.Server |
| Unit tests | MyApp.Tests |
| E2E tests | MyApp.Testing.E2E |
| API backend | MyApp.Api |
| API tests | MyApp.Api.Tests |
For the Picea ecosystem, prefix with Picea.Abies.:
| Convention | Example |
|---|---|
| App library | Picea.Abies.Conduit |
| Browser host | Picea.Abies.Conduit.Wasm.Host |
| Server host | Picea.Abies.Conduit.Server |
See Also
- Your First App — Build a counter from scratch
- Render Modes — Static, Server, WASM, and Auto
- Installation — Package installation guide
- Conduit README — Conduit project layout, local run commands, and test strategy
- Conduit E2E Fixture Architecture — Render-mode fixture and seeding patterns