DEV Community

Alex
Alex

Posted on • Edited on

So I Quit $200k Job To Write A Framework

This may sound like the worst idea ever. But I think there is no better time than now.

Trying To Fix The Web Dev: Part 3, The Formula.

Despite new frameworks popping every week, devs' burnout keeps escalating; the ecosystem is bursting at the seams, and Vercel monopoly gives billing nightmares.

For my solution to succeed, it just needs to be:

1. Modern like SPA

Composable components, shared and local state, reactivity.

2. Straight like MPA

Natural SSR (not by running front-end code on the server), fast loading, functional href, real FormData, and execution transparency (that was lost).

3. API free

Static endpoints only for serving files and pages. Everything else — wrapped and secured by framework.

4. NPM free

Not required to write/run, but not rejected if integration is needed.

5. Server-Driven

Keep business logic-related stuff on the server and user-related stuff in the browser.

6. Straightforward to self-host

Batteries included, a $20/month VPS + basic CI should be enough to kick-start.

7. And don't compromise UX along the way

Give users a genuine feel of a lightweight SPA.

Meme: me planning vs me implementing

Spoiler: I made it.

How??

1. Architecture: Stateful Server + Extra Thin Client

Imagine - while constructing the "front-end" form (button, whatever), simply attach a "back-end" form handler function, knowing that only a specific user (anonymous or logged in) on that particular form can trigger it without any additional header verification.

How it works:

  1. User loads page
  2. Server assigns session cookie (just a guid) and initializes page instance
  3. During template rendering, the server collects handler functions (hooks) into the page instance and prepares event-binding attributes
  4. Browser loads the page, attaches listeners, and connects to the server for synchronization
  5. Server routes client requests via session and instance to hooks
  6. Hooks trigger state changes inside page instance and layout updates
  7. Server-Client synchronization does its job

Schema how it works

Approach Trade-offs:

  • Each active page will occupy a piece of the server memory
  • Server restart may interrupt active users
  • Load balancing requires specific strategies

If you are curious, "back-end role" in such an approach is not gone, now it is a separate module/lib in your project or private (internal) API you call in a secure server environment.

2. Language. Type-safe, Relevant, and Easy to Learn.

Typescript is JavaScript; there is no way to deny it. It's an awesome language to hack on, but it is a shaky foundation for business logic. Additionally, because of our architecture, we can't afford an interpreted language.

There are three industry-proven candidates: Kotlin (as a JVM representative), C#, and Go.

Golang is one of the simplest languages to learn, offering good performance and a low memory footprint, while being boring.

Kotlin is a feature-rich, modern alternative to Java. It is likely the ideal choice for an enterprise. But its toolkit is too diverse - appreciated with experience, but overwhelming for most of us.

I had no experience with C#, but I suspect that what I said about Kotlin applies here as well.

The boring choice is obvious.

Fast forward — that was a perfect choice. Its concurrency model fits right, standard lib is fantastic, and there is templ, which is just genius.

3. Concurrency Control System

To synchronize parallel HTML updates/rendering, event processing, and state change reactions, and ensure the front-end receives only the latest state. No UX compromise in favor of internal simplicity

I need my version of React Fiber. Probably deserves a separate article.

4. Dynamic Components (and why, probably, React is broken)

Component in the front-end framework is essentially made out of three archetypes:

Role Description
State And Input Data that is used to determine rendered content, often a component is "watching" it to initiate a re-render on change
Template HTML pieces used in component logic to construct the final output
Container The place in the page layout that is affected or controlled by the component

Component Flow: state/input processing -> template filling & construction -> container update

An update can affect the virtual DOM first, so only the diff will be applied in the "container"; however, the principle remains the same.

Typically, this trinity is tightly coupled and represented as a single entity. I think that is a bad design decision, introducing a new dimension of problems on scale because of a lack of execution transparency by design.

By restricting component control to state manipulations alone, it creates indirect, unpredictable flows even for straightforward UI updates.

I kept those three as separate entities, which you can combine however you like.

Entity Description
Node (container) A dynamic HTML placeholder that does not affect visuals (displays its content) and can be updated, replaced, or removed. Internally stored in a tree structure to manage lifecycle and concurrency.
Beam (state) A state and communication primitive that can be subscribed to or combined with Node. Respects the tree to ensure consistent render. Derivable.
Fragment (component + template) Entity with Render method. Composable. Can encapsulate multiple Nodes, Beams and have control methods and fields

How it works in practice:

  1. You create a Fragment (component) instance, providing a Beam (shared reactive state) as a dependency (or initialize an internal reactive state if you need one).

  2. The Fragment's render function inserts dynamic Nodes with initial content into the layout and establishes Beam relationships (either by explicitly subscribing or by making parts of the layout automatically re-render on change).

  3. On UI events, hook functions update dynamic Nodes explicitly with new content or trigger reactivity via Beam mutations.

  4. The Fragment can expose an interface to provide abstracted control over its internal dynamic Nodes or Beams.

If you're interested in trying it out, please submit your email address here for an early preview. I would appreciate your feedback. Try it out

5. Real-time Synchronization Mechanics

That does not build on top of WS or SSE, while providing similar or better performance characteristics.

Ended up with rolling handover request, simplified logic:

  1. Client sends a regular request to the server
  2. Server streams updates (HTML fragments and remote JS calls) with some metadata
  3. Client opens a new request with a report (batched results and metadata)
  4. Server switches streaming to the new request from the old one
  5. Cycle continues

That approach requires a more advanced synchronization error correction protocol (because it involves two independent data streams compared to WS). Probably deserves a separate article.

So the client is always connected, delays are minimal, and QUIC is utilized.

6. Page Routing

Declarative, reactive, and type-safe.

Concept

Each page is a mini-app; if navigation occurs within the single-page routing pattern, the HTML is dynamically updated.

Page Route

Page paths are deserialized into annotated structs (Path Models), which support multiple path variants, route parameters, queries, and catch-all patterns.
The resulting Path Model is wrapped into Beam for reactive updates throughout the application, enabling type-safe routing with automatic parameter parsing and propagation.

Of course, Path Model can be serialized back to path+query string, and Path Models Beam is synced with the front-end.

Limitations

A full reload occurs when you switch between mini-apps (and thus, their Path Models); this is by design. You can still declare many patterns and derive smaller state pieces from the path structure to achieve a 100% SPA experience, though this approach prioritizes clean boundaries over maximum SPA fluidity.

No parameter or query value validation has been implemented yet.

7. Event Handlers

Pointer, Keyboard, Form, and Input related events are captured by the client library and proxied to the back-end if a handler function (Hook) is provided during rendering.

Other events require manual (but trivial) integration and will be supported out of the box later.

8. And Other Things...

Static Serving

Serve assets, images, etc, publicly or session-scoped.

JS integration tools

To call front-end functions from the server and back-end functions from JS for advanced control and integrations

Resource and session/instance management mechanics

Each active page will have representation in server memory, and UI updates will utilize server CPU, so it's a must-have.

JS/TS tools

If the developer needs to enable rich on-page interactivity or embed a mini React app — esbuild is embedded and integrated.

Front-end indications and concurrency control

To display pending states, debounce input events, block duplicated form submissions, etc.

CSP tools

To automate CSP headers insertion

It will be a paid product, but don't worry!

  • Affordable lifetime ownership license, no subscription
  • Source code will be available on GitHub, like any open source
  • Free for development, buy only to go prod
  • No telemetry or any form of data collection

Why??

  • Not backed by a big company or VC funding
  • Not relying on spare time maintenance
  • Here to give you the best tool, not serving other interests. It's part of The Formula.
  • Explain to my wife why it should not be

I believe that technologies should be accessible and free. In fact, everything should be. However, that's not a world we currently live in.

Additionally, I am currently working alone, and this cannot continue forever. I need to hire dedicated professionals (it's also part of The Formula), and the project requires top-tier talent.

Status

After nearly eight months of hard work and four or five complete rewrites, I finally checked all the boxes (and I can't describe how tough that was). It's more of an application runtime than a framework. Currently, I'm working on tests (coverage is now around 70%) and documentation. And I'm honestly enjoying the experience it provides.

Server RAM usage for a simple active page is below 1MB*, and CPU utilization is low. Probably, a $16/month (8GB RAM, 4 vCPU) Hetzner VPS will be enough to host a theoretical average app for over 1000 concurrent users with balanced performance settings. However, more detailed benchmarking is needed — I plan to dedicate an article to this topic in the future.
*UPD. Below 50KB...

My goal is to launch a public beta in August. However, before that, input from both experienced and less experienced developers would be invaluable. If you'd like to participate in early access or be notified when the beta is available, please sign up here Try it out.

Production licenses will be gifted to early access participants as a token of appreciation.

In the following article, "The Framework", we will examine its API with real code snippets.

Thank you very much for reading and your feedback. Stay tuned.

Top comments (15)

Collapse
 
fhsheridan profile image
Hunter Sheridan

Hi Alex

Thank you for your creativity and entrepreneurial sacrifice (leaving the day job :-) ) in trying to innovate in this space and solve current architectural problems with web apps.

Sorry for the bother. Have i understood the strategic aspects of your framework correctly below?

  1. Eliminate the need for APIs via html partial page requests
  2. Solve Websocket/SSE fragility via rolling http connections and align with "SPA" UX
  3. Coordinate traditional request/responses across rolling http connections with a protocol of status metadata (about the request/response) in the header of each rolling connection

Node, Beam, Fragment is an architectural improvement in it's own right, but breaking out "beam" and "node" from the traditional component approach better aligns with 1, 2 and 3 above?

Thanks in advance for your time.

Collapse
 
derstruct profile image
Alex • Edited

Hi Hunter

  1. Eliminate the need for APIs via html partial page requests

It is more precise to say that API is eliminated by delegating UI event handling to the back-end (then server streams partial HTML updates)

Solve Websocket/SSE fragility via rolling http connections and align with "SPA" UX

Right, this is how bidirectional, low-latency communication is achieved via QUIC.

Coordinate traditional request/responses across rolling http connections with a protocol of status metadata (about the request/response) in the header of each rolling connection

The rolling request is used to synchronize the DOM state with the server, while UI events are sent with their dedicated requests (and have separate coordination mechanics, which I will cover later). With HTTP/2+, the overhead of additional and parallel requests is minimal. This approach allows me to achieve better parallelism, support multipart form data as a native payload, and maintain a communication organization that is not too different from the standard.

Node, Beam, Fragment is an architectural improvement in it's own right

At least I believe it is an architectural improvement. 1, 2, and 3 make it possible to perform as expected.

Thanks for your questions!

Collapse
 
fhsheridan profile image
Hunter Sheridan • Edited

Thank you for the answers Alex! I understand!

I can imagine quite a lot of time/effort goes into this .. key to the dance:
"That approach requires a more advanced synchronization error correction protocol (because it involves two independent data streams compared to WS). Probably deserves a separate article."

This is important. Everyone is invested in their backend ecosystems.
"If you are curious, "back-end role" in such an approach is not gone, now it is a separate module/lib in your project or private (internal) API you call in a secure server environment."

This is quite a scope of work :-).

Thanks
Hunter

Collapse
 
turboturtle profile image
TurboTurtle

really feels like a game-changer. Huge respect for the crazy leap and chasing what you believe in. The industry needs that kind of energy. Good luck!

Collapse
 
cmaxm profile image
cmaxm

Great job! I can see a lot of experience, common sense, and erudition. Good luck!
Just first thoughts to share:

  • Not sure that I understand everything correctly, especially in Node/Beam/Fragment chapter I am a bit lost - a diagram could make it more obvious.
  • Go is a great choice. Could be a real game changer.
  • Reminds me Vaadin framework a bit (greetings to Finland). Server state, synchronisation, JS components, etc. From my experience with Vaadin sync surprisingly was not a problem (back then in 2010 it was based on GWT), load balancing with sticky session was all right, but initial bootstrap time and server RAM consumption were problematic. And, yes, release was tricky for active sessions.
Collapse
 
derstruct profile image
Alex

Much appreciated!
I added a detailed flow to clarify the Node/Beam/Fragment section.

Checked out Vaadin; it's an impressive platform, so I'll explore it further for inspiration. I found it amusing that, compared to my thing, it feels like Java compared to Go on multiple levels.

Collapse
 
parag_nandy_roy profile image
Parag Nandy Roy

This is wild...in the best way..

Collapse
 
shnjd profile image
shinjith

Sounds great! All the best.

Collapse
 
derstruct profile image
Alex

Thanks, that's very important for me to hear.

Collapse
 
shnjd profile image
shinjith

Sure! just signed up for the early access.

I'm willing to contribute to the project in some way, but don't have enough experience in Go, may be I can do something with tooling part or so. Anyways nice start, keep going!

Collapse
 
andriy_ovcharov_312ead391 profile image
Andriy Ovcharov

Interesting. Thanks for sharing!

Collapse
 
brunoelo profile image
Emeka Elo-Chukwuma

Man if it makes you happy it is worth it.

Collapse
 
derstruct profile image
Alex

I hope it will make more people happy.

Collapse
 
kurealnum profile image
Oscar

This looks awesome, but jeez, that's a massive leap of faith.

Collapse
 
derstruct profile image
Alex

Thanks. Yeah, I feel the pressure.

Spoiler for the next article: here is a basic code snippet with a counter implementation.

// UI Fragment type Counter struct { // Dynamic element node Node // Local State count int } // Click-handler function func (c *Counter) handler(ctx context.Context, _ RequestEvent[PointerEvent]) bool { c.count += 1 // Updating the node with new content c.node.Update(ctx, c.display()) // Not done; keep hook active return false } // Display click count templ (c *Counter) display() { Clicked { c.count } time(s)! } // Fragment-render function templ (c *Counter) Render() { // Render dynamic element @c.node { // Initial content Click  } // Button with an attribute constructor and on-click action <button { A(ctx, Click { On: c.handler })... } > Click Me! </button> } 
Enter fullscreen mode Exit fullscreen mode