We all want our sites to look attractive and feel fast and responsive across a multitude of devices and screen sizes. There are common tools developed in the frontend eco system to help build such interfaces.
The most common and well known is React, with many others sharing this space, such as Svelte, SolidJS, Angular, Vue, Qwik and more. All are impressive feats of engineering and come with bold statements.
React:
The library for web and native user interfaces
Solid:
Simple and performant reactivity for building user interfaces.
VueJs:
An approachable, performant and versatile framework for building web user interfaces.
Regardless, they all have something in common…
Javascript
If you're going to write your whole project with one of these frameworks, for better or for worse you're going to be writing all your logic in Javascript (or Typescript). After all, Javascript is the language of the web, right? It's right there in the browser, it seems only natural to write your web app in the same language.
Only, if you're going to write a fullstack app and have server rendered or statically rendered pages, you'll most likely use a meta framework like NextJs, Remix, SvelteKit, SolidStart.
This means you're going to need a server, and it's going to run Javascript too. You don't really have a choice here, the framework mandates your architecture in this instance.
Convenience over simplicity
But this isn't a problem right? I mean, it is so easy and simple to get started. Let's take a look at a couple of examples to get up and running.
NextJs:
npx create-next-app@latest
cd my-project
npm run dev
SolidStart:
npm init solid@latest
cd my-project
npm install
npm run dev
In both these examples it bootstraps a project with some sensible defaults and gets you up and running in minutes with a dev server with hot reloading out of the box. This is awesome and feels like a great developer experience, right?
Let's say you want to build a new web app for a project you are working on. You can get your server side rendered interactive app in NextJs up and running in minutes and start building. It might feel great, but does your app really need to be so engineered for what it needs to do?
Do you really need a build step to transpile your modules and JSX to a JS bundle that can be ran on the server to generate the initial HTML, stream to the client with a bunch of JS to hydrate the dom nodes and have client side state keeping track of a virtual DOM and re-rendering at every change?
It might be convenient to get up and running quickly, but let's not forget that convenience does not equal simplicity. Under the hood there are abstractions upon abstractions to make the 'magic' happen and this can come to bite you as time passes and the project scales.
Keeping up with dependencies
In the above examples we setup a basic project to get up and running in NextJs and SolidStart. There's something interesting here though that I'd like to highlight. After installing NextJs (below multiple deprecated package warnings) there is this line helpfully outputted by npm:
added 361 packages, and audited 362 packages in 10s
And for SolidStart:
added 569 packages, and audited 572 packages in 18s
These are the number of packages that were required and downloaded by NextJs and SolidStart respectively when we initialised the projects.
And this is just for the frameworks, it doesn't include any package you might need to reach for in your project. Want to have a code formatter to force consistent formatting? Download Prettier. Want to enforce some code rules? Download ESLint. Want to have types? Download Typescript. Want to run tests? Download Jest (or maybe Vitest) and this goes on and on.
Though this might seem trivial, I see this as a sign that we might be using Javascript not as it was intended and we end up having to reach for packages to perform relatively common operations. This results in dependencies with dependencies with dependencies (insert node_modules meme here).
Requiring so many dependencies inherently makes your project warm-blooded. If you don't touch this project for 6 months, you might see it will be out of date when you come back to it.
No worries! You don't need to keep things up to date, right? Well, it depends. Let's say you are security conscious and use a tool like Dependabot to scan your dependencies for vulnerabilities. You might find you have a few per week, especially if your project is into the thousands of dependencies, which is not too unusual for middle to large projects.
Let's say you're not bothered about updating at all. The older the project gets, the more difficult it will become to avoid unleashing the dependency waterfall. Eventually, you'll have to open the floodgates when it's time to update Node, want to use a newly released feature, or add a new package which is not compatible with another outdated dependency.
On the flipside, I've seen projects that want to keep up to date with all dependencies, which can result with multiple package updates daily, and sometimes introducing a range of interesting bugs and quirks that made it past tests and into production.
Extrapolate this out to multiple projects and all of a sudden it can feel like a constant background pressure and be a weekly chore to test and update projects to keep them relevant and from gathering dust.
Rapidly evolving ecosystem
You've probably seen the memes 'Yet another JS framework'. In a more positive way, we could say that there are constant innovations, evolutions and changes happening in the ecosystem. It truly is impressive the amount of work and passion that goes into such projects. However, this can have a negative impact too when working on web apps in production.
If you've been in the space for a while, you might have made your first React project with create-react-app
to get you up and running. Too bad this is deprecated now, maybe you should move to Vite. Maybe you made your first static React project with Gatsby. Well that's kind of deprecated now, you should probably migrate to NextJs or Remix. Using NextJs already? You should probably migrate to the app router. Maybe you started writing your React components as classes with logical life cycle hooks. Well that's the old way, you should be writing functional components now, and hope you pass the right dependencies to that useEffect
hook. But wait! Hold on, React Server Components are here and there's a smart compiler on the way.
This constant shifting and trying to stay ahead of the curve increases technical debt and adds to the burden of keeping projects up to date. Think about all the concepts you need to be aware of today when using React, vs someone coming in fresh in 2016. To React's credit, it has remained backwards compatible, but we cannot ignore the cognitive overhead that is required to stay up to date with today's best practices.
The developer experience fallacy
This brings me to the developer experience. Surely everything we've looked at so far can be seen as a trade off to the great developer experience these frameworks have to offer?
While I do believe the developer experience is great in the moment, what I feel is often overlooked is the developer experience over time. Sure it can feel great to spin up a new dev server in seconds with almost instant hot reloading on changes - but if you track every time you need to refactor, upgrade, or maintain a project over the years, all of a sudden the experience is not as great, and this shouldn't be overlooked.
So what are the takeaways?
Use the right tool for the job
Don't just accept that a web framework is the default and best option when building a web app. Look at your use case. Does it need to be a Single Page Application? Do you need such a level of client side interaction? Could you achieve something similar using something like HTMX and any language of your choice to generate the pages?
Keep things simple
Tech is cool, but sometimes we just need something that is good enough and does what it needs to do in a simple way. This can help us keep sane and avoid feelings of burnout. Avoid premature abstractions, try sticking to web standards and don't scale to the moon.
As developers we can worry too much, and fixate on the perfect and best way to do things. Lets not forget to have fun, explore and learn. If it doesn't work out, that means you learned something and you can always change it later.
Top comments (59)
The right tool for the job you say… but how do you know what your job will be in the next five years?
Say you start with HTMX, at which point do yo decide to switch to another framework because you can't manage state anymore or you don't have the right library? Those decision are often hard to anticipate, tend be postponed until it's too late, they involve heated discussions etc. It's not like you can migrate a codebase to a new framework just like that.
The reason why React (and to a lesser extend Vue, Svelte etc.) are so popular is because they respect a golden API design paradigm:
Simple things are super simple in HTMX. Harder ones might not be possible at all. Yes there is a tooling tradeoff when you start with React, but simple things are still simple, while scaling to much harder thing is possible. You know you're unlikely to get stuck, whatever your "future" is.
That's a really good point. You can't really say for sure what the job would be in the next five years, but you can make some educated guesses depending on the type of project you are building, and how much client-side interactivity you are anticipating.
I've seen many a NextJs project get deprecated overtime, when all it really needed to be is a simple static page hosted somewhere. I would happily have kept these 'simple', and spend time migrating a project that that really can take advantage of the strengths of of React, Vue, etc, when/if that scenario arises.
It's about finding a balance. Do you start from an index.html file, or a fullstack NextJs app deployed in a k8s cluster and all the bells and whistles? It's a good practice to sit down, think about it and be aware of your options.
You guys are stuck with abstracted bs, you don't even know what your own code does. Over 300 packages for a normal nextjs project. Really Bro ? Just because you like this GOLDEN API DESIGN PARADIGM ?
Do you have any idea how your memory is managed ? Can you even keep up with all these dependencies ? do you know what each of them do ? This is all confusing to me. These cooperation are building tools to aid their development and not yours. You think it'll fit your future but it doesn't. Features are already provided to you, you don't chose them buddy, Companies get behind these frameworks, what happens is you end up embracing frameworks so much you forget design patterns and end up stuck with frameworks. Your thought process is based on the frameworks design, not your design.
Keep things simple, if it needs to get complicated, do it your self. Take HTMX for example, if you can't manage state without a library, are you even a developer ? that's a skill issue bro. When something gets hard the option is to look for someone elses work ? Is that how programming works ?
I mean look what happened since ES6, do you really think what happened from 2019 to 2024 was the future ? Developers have adopted tiktok brains nowadays. Every problem they come accross has a library or framework nowadays. Good luck!
The Article highlights what's actually wrong with the industry. It's a solid article and points out exactly why you should use the right tool, choosing the right tool for the job requires RESEARCH, Research on what the job exactly needs you to do. Love the article.
Cudos to the writer
So you don't use any framework? You mutate your UI with Basic dom api?
Why not?
Isn't that what React is doing?
I guess if you like it there's no problem with that, doesn't matter what react does, also c++ compiler do compile in assembly but we don't write in assembly for that reason.
Personally for as much I use the Dom api and try not to abuse frameworks to do stuff that is not even ergonomic with frameworks I would never ever work in a project with vanilla Js or jQuery, I did my time on that kind of stuff and any framework even AngularJS is better both in terms of velocity you get and it being much easier to organize your code and keep a fucking value in sync with the UI.
Hard things are possible in HTMX, you just use JS without a framework in addition to it. The point is, not everything needs to be done by JS, JS is a tool for enhancing web content.
Great post
There are constantly new libraries being released, and many developers are eager to try them out or incorporate them into their upcoming projects. I prefer to keep things simple by minimizing the number of NPM dependencies and writing custom JavaScript code whenever possible. For server-side rendering (SSR), we rely solely on Node.js and do not depend on any additional frameworks or libraries. The more NPMs you add to your project, the more potential issues and challenges you may face in terms of management and upgrades.
When you begin to scale and have competition that has out-scaled you, you might begin to have a rethink on this approach lol.
Opensource libraries are necessary evils, we just need to choose the least evil among them.
But going full inventor mode on projects that are production facing is a recipe for disaster.
I totally agree that NPMs are the necessary evil. We just don't pick any library, we pick that makes sense and has good community support, lesser GitHub open defects, good stars and downloads.
As an Angular Dev, since it’s birth, in 2016, I must say I really enjoyed the experience of using Remix, recently, for a mini Shopify project.
Now thankfully, I have quite a bit of React experience, which helped with the Remix syntax.
But, it was really refreshing, to experience a new paradigm.
Remix allows us to write client side & server side code on a single page. Remix includes two server side utility methods called loader() & action(). The former pulls records from the database, when the page loads and the latter handles form data and submits it back to the database.
Anyway, enough of the details, but it is kind of cool being able to use both client & server side code, in the same place.
And it was good to be able to compare another framework with Angular, which is quite verbose & opionated [not a criticism, by the way].
I would say, that if you plan to do a small app that isn’t going to scale much and only involves a one person team [yourself], then don’t use a framework. Just use JS/HTML/CSS or even just a server side language like Coldfusion or PHP, that are compiled at runtime.
If on the other hand, you plan to build a big enterprise project that will scale, with a large team, then a framework is an absolute requirement. Frameworks allow us to work well, in teams, because opinionation helps us to understand how to write code in the same way, with the same methodology. Plus, frameworks like React & Angular, have first class documentation, which is vital, when working in a large team, with members that might differ, in skill level & experience.
Anyway, great article.
Thanks! I love your summary. And your point...
I think this is hitting the nail on the head of the biggest advantage FE frameworks in the JS ecosystem have to offer. It's just a shame that this then locks us into using Javascript (since it is the only language in the client), which is why I'm finding low JS alternatives like HTMX and Alpine so intriguing.
Great article @manonbox , agree with this take I enjoy Remix and Svelte for SSR the loader and actions and their focus on we standards, it is great fun to work with.
"Avoid premature abstractions, try sticking to web standards and don't scale to the moon."
Yeeeeeah!
Yeah! These kinds of things can feel clever in the moment. I think it takes experience to know where abstractions are worth while, and where they are just going go make things more complicated for you (or your peers) in the future. I'm still learning all the time.
Great post! But you are talking about dependencies, what about inconsistencies? Each tool has it's own special concept that you need to learn? And a documentation that is not always up to date? This is also part of the developers experience.
Thank you! Yes it's a good point. I had written something about this when mentioning abstractions on abstractions. The more abstractions there are, the more framework specific concepts you need to learn and a DSL is born. In the end I left this out, as maybe the 'hidden cost' over time is more related to how often these concepts break or change. There are some great examples of this (e.g. AngularJs > Angular), but others like React have remained true and a component written in 2016 would still work today, so I decided to exclude it for brevity.
Regarding documentation, my personal experience has been positive. It's almost like libraries use their documentation as marketing material, in making it attractive and approachable. My only negative experience is finding deprecated documentation while Googling... which to be fair is annoying, especially if it's not clearly marked as deprecated. Perhaps I could have touched on this in the article, thanks for the thought!
I suppose, things are a bit different for professionals, as - at least for some time - they can ignore the progress and just keep what they are used to. There are more than enough companies that earn good money using their old PHP-toolkits or Typo3. But for a newcomer, learning can be the biggest pain point. Often you find documentation that applies to older versions of a package. If you do not know, that a tool is outdated (or even in a per-beta-state), you can easily get lost. This can be extremely time consuming, and as we know - time is money!
As a C++-programmer, you will most likely update your tools frequently, but you do not get any package that introduces a completely new language with the next update.
"developer experience over time"
This should be a focus in tech discussions more often! Great article!
Absolutely! It's like buying a new car. Everything can be shiny and clean at the start, but you should really be thinking if it's the right car for your needs and with some miles on the clock.
I completely agree with the idea that convenience doesn't always equate to simplicity. As someone who's spent a fair amount of time maintaining older projects, I've experienced firsthand how quickly dependencies can pile up and turn into a nightmare to manage. While the rapid setup and hot reloading are great for quick development, the long-term costs in terms of maintenance and updates can be overwhelming. It’s a good reminder to critically assess our tool choices based on the project’s actual needs rather than just following trends or opting for the most popular framework.
Absolutely. The advantages that an older project has is you can see the trend of the project and its needs. Has it not changed in years? Does it need to be an SPA? Is there much client-side state? It could be worthwhile, cheaper and a time saving exercise in the longterm to assess if it's viable to refactor to something that requires less maintenance and better suits the projects requirements (that are now well known).
"It is very difficult to make predictions, especially about the future."
-- Neils Bohr
I fully agree with every word in this post. Unfortunately, nothing changes on my end -- front, as well back IYKWIM Lol
All I have (though!) is a very well-articulated re-confirmation of a problem that has existed, nor is going away anytime soon. Sigh.
Love the quote. Stay hopeful, there is a lot going on in the low-js space, like the resurgence of MPA's, HTMX, AlpineJS etc. I have more articles lined up in this space to come that I'm working on.
If I DIY, I will end up debugging my code. If I use a framework, I will end up debugging not only my code but also the framework. Because I code the way that works for me. Inevitably I will inject something into the framework that the framework didn't anticipate, and have to dig into the framework because frameworks are written for performance, not to help developers figure out why the framework chokes. (Alas, I have tried writing a framework from scratch and had my effort canceled by my boss.)