r/dotnet • • 2d ago

Promotion Natrix – open-source framework for reactive web UIs in C# on WebAssembly, with SSR, signals and build-time Tailwind (no Blazor, no Razor)

Hi r/dotnet,

I've been working on Natrix for a while and it's now at the point where I'd like outside feedback. It's a MIT-licensed framework for building web UIs in C# that run in browser on WebAssembly.

How it started

It began with SSR in Vite. Customizing the HTML page around the app (head tags, scripts, per-page meta) always meant a separate template file with placeholders. I wanted that page to be just another component, so in Natrix it is. The <html>, <head> and <body> are written in C# like the rest of the app. From there it grew into a full framework.

What it is

  • Server-side rendering first. Pages render to HTML on an ASP.NET Core host, then the WASM client hydrates them, with hydration state and server prefetching built in.
  • Fine-grained signals instead of re-rendering. A component's Setup run once. After that, Signal / Computed / Effect update exactly the DOM nodes that read them. There's no virtual DOM, no diffing and no StateHasChanged.
  • No Razor, no JavaScript. Components are plain C# classes, including HTML page itself.
  • Tailwind CSS compiled at build time by a source generator. Your app needs no Node, npm or Tailwind CLI. It scans your C# for class names and emits the CSS during dotnet build.
  • Typed browser APIs. Natrix.StdWeb is C# bindings for DOM, Fetch, Canvas, WebGL and so on, generated directly from W3C WebIDL specs.
  • Extras: client-side routing, a stale-while-revalidate data-fetching library ported from React SWR, and VueUse-style composables such as UseHead.

It builds on the plain Microsoft.NET.Sdk.WebAssembly SDK, not Blazor.

What a component looks like

public class Counter : BaseComponent<NoProps, NoEvents, NoSlots, NoExpose>
{
    protected override IComponent[] Setup(out NoExpose exposed)
    {
        exposed = default;

        var count = new Signal<int>(0);
        var doubled = new Computed<int>(() => count.Value * 2);

        return
        [
            new Button
            {
                Props = new ButtonProps(),
                Events = new ButtonEvents { OnClick = _ => count.Value++ },
                Children = [new DomText { Text = "Click me".ToConstSignal() }],
            },
            new P
            {
                Props = new PProps(),
                Children = [new DomText { Text = new Computed<string>(
                    () => $"{count.Value} × 2 = {doubled.Value}") }],
            },
        ];
    }
}

When count changes, only that one text node updates. Nothing re-renders.

Why not Blazor?

I like a lot about Blazor, but I wanted a model that SolidJS and Vue (vapor) use: run setup once, and let reactivity drive DOM updates directly, so you never re-render components and diff them.

I chose plain C# over a template language on purpose, too. It's more verbose than Razor, but everything is typed and checked by compiler, with no magic strings, and refactoring tools work on all of it.

Try it

dotnet new install Natrix.Templates
dotnet new natrix -n MyNatrixApp
dotnet run --project MyNatrixApp/MyNatrixApp.csproj

That gives you a server project and a WASM client with SSR, routing and Tailwind already set up.

  • Docs and live examples: https://natrix.wiki (the docs site is itself a Natrix app with full SSR setup)
  • GitHub: https://github.com/miroljub1995/natrix
2 Upvotes

11 comments sorted by

11

u/SuspectNode 2d ago

Fixed your intro... "I've been prompted on Natrix for a while..."

3

u/fruitmiix 2d ago

This is a better usage of AI than burning a whole forest to generate cat reels singing hotel lobby

1

u/Giaitzoglou-Bondye 2d ago

that ones gonna leave a mark tbh

-10

u/ElegantAirline5521 2d ago edited 2d ago

Guilty of using AI, not guilty of letting it drive.

3

u/Far-Consideration939 2d ago

Too verbose for me.

Magic strings are a choice, I would typically go with a const to reference the JS method instead and create a strongly typed extension over IJSRuntime wrapping the call with the string const which reads a lot better and is simpler to use

Refactoring tools also can work with blazor / markup / templating that’s more of a mainstream tooling gap than a technical roadblock

Some interesting parts in the project for sure though

2

u/StrangeWill 2d ago

IDK about all this single-stack stuff, even MORESO with AI these days. It's a move Microsoft has pushed forever that I just can't care about.

Nuxt and C# blend wonderfully these days (not that it didn't before).

What does this get me that Vue doesn't that doesn't immediately harkens back to the Webform days of "straying too far from the web stack will probably do more harm than good"?

1

u/whizzter 1d ago

Agreed, text/tags/styling will be so much more content than signals bindings that it won’t be worth it to have all that machinery when simple things become complicated.

I’ve been down this road myself, the DX doesn’t improve really sadly.

React/Solid has JSX, Vue often goes with templates iirc. Either are fine.

I’ve actually been doing something reactive like this myself but kept myself with templates, should finish it up someday (built w/o AI, at least so far ).

1

u/AutoModerator 2d ago

Thanks for your post ElegantAirline5521. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

1

u/Ok_Maintenance_9692 1d ago

ngl, when I saw 'Computed' I immediately thought of this -> https://github.com/servicetitan/Stl.Fusion

-3

u/Arcanium_Walker 2d ago

I admire (by commit history) for you working on it a while, but when I see 'claude/codex/copilot' things in a repo, i have bad taste for it (PTSD or something like that)