Ask HN: How does one build large front end apps without a framework like React?
I had a mind-blown-moment when I learnt that Obsidian was built without any frontend JS framework. ( https://forum.obsidian.md/t/what-framework-did-the-developer-use-to-create-obsidian-desktop-application/30724/11 )
The benefits, I can see.
JS frameworks move really quickly, and when we're working on a large, long-term project, it sucks when big breaking changes are introduced after only a couple of years. Sticking to slow-moving web standards (which are quite mature by now) increases the longevity of a project.
And the stability also means that more time is spent on delivering features, rather than on fixing compatibility issues.
There is also the benefit of independence. The project's success is not tied to the framework's success. And it also makes the project more secure, from supply chain attacks and such.
Because there is no "abstraction layer" of a framework, you also have greater control over your project, and can make performance optimizations at a lower level.
I feel not using a framework can even make us a better developer. Because we know more of what's going on.
There are benefits to using frameworks too, I'm not here to challenge that.But this alternative of using none... it seems rarely talked about. I want to learn more about building large (preferably web-based) software projects with few dependencies.
Do you have any suggestions on how to learn more about it? Are there any open source projects you know which are built this way? It needs to be large, complex, app-like, and browser based. I'm more interested in the frontend side.
Thank you!
86 comments
[ 2.8 ms ] story [ 81.8 ms ] threadThe frameworky code is under 100 lines of code, heavily inspired by what I think is a good idea from React: "components are pure function", the simplest example of the about page beeing visible here: https://github.com/mickael-kerjean/filestash/blob/master/pub...
Since the whole project was migrated over from React, I was able to improve performance to a degree that would have been impossible using React, it's a breadth of fresh air to open the performance and memory tab and to be able to optimise things from there to get 60FPS no matter what whilst preventing massive ram usage because those frameworks runs all sort of code that is out of your control. Also because there is no build system, I was able to introduce plugins to be able to patch frontend code via patch applied dynamically by the server to create features like the recall button to recall data stored on glacier when using S3, dynamic patching of the icons to apply branding and many other things that I would have had to fork entirely before
Anyway, I hope our industry would go more often the vanilla route, es6 is ready for prime time and solve all the pain point we had while internet explorer was still a thing
I hope this becomes common knowledge! There was a period somewhere between when the web platform didn't have good tools to make web applications (plus DOM manipulation was slow) and the mainstream adoption of es2015 in browsers when it made sense to do things like create a virtual DOM to allow for unavailable features at the expense of performance. It did have its place, but we don't need it anymore.
Remove a layer. In 2025 ES6 is ready for prime time, indeed.
Even pre-web. I remember, how quickly I could create Delphi desktop app with drag & drop UI builder more than 2 decades ago. If we speak about generic ERP, you could make dozens of screens in a day.
You just split your app to layers.
That might be for example:
1. Just UI;
2. UI logic (validations, preloading selects...);
3. Transformation of data between data model and UI model
All of the things above lived on users' computers.
All of the things below were on servers.
1. API gateway (terrible things like SOAP, XML-RPC or completely proprietary formats);
2. Business logic;
3. Database
There is no reason you cannot implement MVVC/MVC or whatever else on JS for reactivity, it’s just that react et al just abstracts that away for you.
You are effectively choosing some method of implementing and abstracting the observer pattern and handling cleanup since you can easily get into a situation where you have dangling references to objects that are never cleaned up (memory leaks) or pretty nasty GC hangs which can make the UI feel bad to use.
So yeah, JavaScript is nicer nowadays, and browser apis are more consistent than they were then.
React is a lot more stable than I think you're giving it credit for.
> And the stability also means that more time is spent on delivering features
Frameworks/libs also exist to save you time, thus letting you spend more time on delivering features. And fwiw, the obsidian team seems to agree in principle. Your link goes to a forum post of some kind, in which one may find a link to obsidian's third party deps: https://help.obsidian.md/credits#Third+party+acknowledgement...
These do not include React, but do include:
- i18next - lezer - moment.js
Plus a bunch of others. Why didn't obsidian write their own date lib and chose to use moment.js? Because it saved them time, despite the fact that moment.js does make changes, and many people are moving on from it in any case.
The idea that not using a frontend framework will let you focus on delivering features seems reductive, and the obsidian anecdote doesn't support the idea anyway.
Whatever you're building, it's never a bad idea to deeply understand the tradeoffs that using a library will bring. Obsidian probably couldn't accept the downsides of React due to them needing a bunch of custom renderers for content, which React makes harder. But that is likely a rare constraint for apps in general.
Generally speaking, libs like react exist to save you time and help you focus on delivering features.
I'm glad they take good care of backwards compatibility. React was the most common example, I didn't intend to target it specifically.
Not all frameworks are like this though. Svelte 5 introduced many breaking changes, with plans to deprecate the classic Svelte 4 syntax. So that forces many projects to spend time migrating to the newer version.
> The idea that not using a frontend framework will let you focus on delivering features seems reductive...
Agreed, appreciate the healthy arguments. :)
From there one big question is if everything needs to be dynamic or not. You often have menus and navigation that you don't need a framework for and that even something like jQuery is well suited for. On the other hand a framework would probably slow things down for the writing experience in Obsidian, which is the main draw for me to use Obsidian. Once the writing module is written from scratch, it won't seem like such a big lift to also do all the navigation and controls by hand.
That doesn't mean this is the right way for everyone. A lot of times the speed to releasing new features is more important, while the handicraft of the UI/UX is less important and in those cases using a framework is absolutely the right choice.
I’ve switched to web components with lit/html driven by AI.
Could not be happier.
With the framework, you have structure. It might not be the best structure to use, but you have one. React lasted really long and is still works today.
Going without framework works if:
- you have a really small project
- you are so advanced developer or have specific use-case that using a framework creates more burden than time saving
- you are creating something so valuable that reimplementing it yourself is worth it
I think that regular website will bleed via maintenance costs in the long-run if you don't use a framework. Instead of paved road with framework migration you would be fixing and debugging bugs in your framework/codebase.
- Routing
- State
- Rendering
A lot of frameworks try to cover all three of these, and that's where you tend to see a lot of feature churn. I would recommend picking a rendering library (like react) and writing your own routing and state library, even if it's just a light abstraction on top of an existing library. This will make it way easier to upgrade or move in the future, as you will control the API.
* greater flexibility
* faster execution, My current application produces a large single page application that fully renders visually and executes state restoration in about 105ms localhost or under 200ms across a network
* faster maintenance, I can do massive refactors that touch most files in the application in about 2 hours thanks to thorough use of TypeScript interface definitions
---
Here is how I do it:
1. Just start building your application. Realize that for any significant application the front end UI is probably only about 20% of the application code, or less.
2. Start the UI by writing static HTML. Reserve JavaScript, in the browser, only for handling events and state restoration and execute state restoration only on page load.
3. I prefer to use WebSockets over HTTP as much as possible for absolutely everything I can get away with. WebSockets are full-duplex, where HTTP is not, and have far less overhead. That will substantially reduce the complexity around messaging in and out of the web browser.
4. Everything after that is just execution, planning, refactoring, and feature addition.
5. You can do absolutely all of it in TypeScript now without any compile step. Node.js now executes TypeScript directly, but the browser does not. To solve for the web browser just include the browser code into your Node application using ES6 module import. If the imported code is a function you can convert it directly into a string for transport, and otherwise use JSON.stringify for objects/arrays.
I don't follow. How do you include the browser code without the compiling step?
However, you'll inevitably end up with your own after some time. Even if you try your best to stay away from it, you'll need abstractions for:
But you can have minimal layers for that.For platforms, just go native - it's easier than ever with LLM assistance.
In the past we used http://microjs.com/ and similar to find small libs to work with when we threw together marketing websites, not sure how maintained that list is nowadays but the idea is that you can make websites like lego, sometimes you don't need a whole box of Eiffel tower to make a little tree.
If your functionality is well thought out and not super complicated, don't have 10s or 100s of devs on the same project then working without a well known framework could make sense, otherwise there can be steeper learning curve when onboarding new devs, scope creep / bloat, etc that will likely cause issues down the road.
To learn about this you could try to replicate for example the mentioned Obsidian by yourself and research solutions for all the issues you run into, e.g. minimal routing, custom view renders, reactivity, performance, etc.
Edit: for a more practical example, you don't need to go down the web components rabbithole just for native reactive components, MutationObserver can watch for your template fragments entering and exiting the DOM and run your init/destruct routines on them. Now you can just push HTML back from the server with fetch or xmlhttprequest or whatever, and as soon as your component's HTML enters the DOM it will be "hydrated" by your init routine via MutationObserver. Honestly, add some event signaling and XHR and you're further along to a smooth SPA-like experience than you might think.
If that's not enough, Lit enhances it a bit such that either LitElement and/or litHtml can accommodate whichever style you want: https://lit.dev/
Incidentally, many of the issues people have with React are attributable to this mistake: knowing what a framework is supposed to be, one might expect React to take care of everything, and when it turns out you’re on your own and using React in a complex project means (whether consciously or not) coming up with a homegrown React-based framework of your own, or using a collection of disparate extras that fill in the blanks (like CRA), by then it’s already a hairy mess of a codebase in a desperate need of refactoring.
React lacks most of framework traits. The library does reactive rendering and contains a somewhat restricted set of low-level primitives around it. Even its DOM integration is handled by a separate, optional library (ReactDOM).
Someone could complain it feels like with Vercel the direction for React is to become closer to a framework (now with RSC and JSX runtime being part of React), but still it is nowhere near a state where the phrase “a framework like React” rings true—and, in fact, Vercel’s interests are aligned with keeping React more of a library than a framework so that more people use Next and are upsold Vercel’s hosting, and Next is in fact a framework in the full sense of the word, comparable to Django or Rails.
Similarly, Vue, Angular, Marko, Astro, Qwik, etc. are frameworks. They provide you with project structure, scaffolding, and a lot of batteries. The fact that many of them are, in fact, using React under the hood speaks volumes.
I'm working with JS for already 25 years. Tried all of the frameworks, and continue on doing it. And every time I try something new, the refactoring flow turns most of them into NextJS (if it's very UI rich or customer facing or something very web-oriented), or Vite+React+Tailwind (client) and Hono (backend) if it's more of a tinker toy needing more custom solutions.
The boilerplate with NextJS is cleanest (compared to all the other frameworks) and API is the most straightforward one, and you can safely ignore the vendor lock in. Its just a pretext to hate on NextJS. They all have some kind of a "vendor" lock in. Be it a vendor-or-a-specific-approach-or-whatever-lock-in.
And Vite+React+Hono — simplest to set up for quick experiments, and very powerful with minimal boilerplate. Will probably create a starter for this one, as I have been using this stack quite a lot lately.
EDIT:
You can pretend vanilla JS is all you need, but then your app grows, then you suddenly need types, and state, and more events and their handlers, and SSR or something else. Thus React has been the most stable bet for quite a while for me now.
The vendor lock-in on NextJS is certainly much more egregious than other frameworks. They have their own undocumented build flag to give different build outputs that Vercel uses vs the build outputs that are documented. Hosting nextjs on your infrastructure is not as simple as sticking it into a docker file as with most frameworks
And I would also push back on the idea that every framework has vendor lock in. Remix was so focused on "using the platform" that it basically willed itself out of existence. It's no longer even a framework. Just part of the react-router library. I've also used Astro which is a framework of similar complexity and feature richness as Nextjs and certainly has no "lock in". At least as far as
My advice is to go and learn a proper architecture (MVVM is a great fit in my opinion for MOST things) and then figure out how to implement that with something you enjoy using. My only bit of advice here is that the closer you stick to the underlying web platform the better off you’re going to be so if you want a concrete recommendation something like Lit here gives you all the primitives you need along with great performance, documentation and developer experience.
JSX encourages sloppy HTML. Props are are overly verbose. Hooks are a nightmare. Contexts are gross. Portals are a stupid hack for the very, very stupid reason of making it nearly impossible for parent elements to read anything about their children. The whole "props down, events up" paradigm is woefully naive and half of React exists to get around it because of that.
So yeah, if all you know is react, you've already been on the hard path.
This is what I used for fontofweb.com's snipper chrome extension.