When it comes to selecting techs that will be used in a software development project, I see mostly two approaches: picking a framework that will take most of the important decisions for you, or handpicking a variety of libraries and tools, each of which will handle a specific aspect of the system, and make them work together within a software architecture of your design. I refer to the latter as "assembling your own stack". It is easy to see why frameworks are so popular in many areas of software development: they provide a quick path, perhaps the quickest, from design to production, with a way of doing things that will be consistent across projects. However, some experiences that I have had with frameworks in the past have led me to believe that making all your tech and design choices can provide a better health insurance for a codebase in the long run.
Frameworks vs Libraries
First of all, I want to clarify what, in my opinion, qualifies as a framework, as opposed to a library or a tool. I consider a piece of software development technology to be a framework if it dictates the way all or most aspects of an application built using it will be programmed and how the code will be organized, while a library covers only a specific aspect of the system and is usually less opinionated about architecture.
According to that definition, React is not a framework, but Gatsby is. React is only concerned with UI rendering, and needs to be combined with other techs in order to build a full system. Gatsby, on the other hand, manages pretty much everything across the stack, from Static Site Generation or Server-Side Rendering to making things work in the browser, including aspects such as routing and data querying. Sure, it uses other techs to do it — such as React and GraphQL —, but the point is: you don't really get to pick. Furthermore, if you want to extend or change Gatsby's behaviour in any way, you usually need to rely on plugins that were built for it rather than using completely independant libraries.
In other words, assembling your own stack gives you more control over your application's inner workings, and software components are not as tightly coupled. More control and less tightly coupled components mean more flexibility.
That being said, I do like Gatsby: I am, in fact, using it for maintaining this website, and am generally happy with it. I think it is a great piece of tech that makes it very straightforward to build a statically generated site using techs that I love, such as React and GraphQL.
There is, however, one serious point of pain that I have experienced with Gatsby, and with other frameworks as well: that is the pain of updating a project to a new version of the framework.
Update Nightmare: When Frameworks Actually Slow You Down
All pieces of software, whether they are frameworks, libraries, tools or other things, get major updates from time to time, and upgrading from an older version to a newer one that introduces breaking changes is rarely a walk in the park. That being said, updating a framework is, in my experience, usually way more painful than updating a single library. I see mostly 2 reasons that explain this:
- Updating a framework means that you're updating your whole project at once (you usually can't do it gradually)
- Damn Plugins!
The first bullet point is pretty self-explanatory: since you usually can't have part of your project using a different version of the framework than the rest of it, that means you need to update your whole project, or none of it. On the other hand, when using a stack that you assembled yourself, you can often update a single component without having to update everything else, even though compatibility issues forcing you to update something else do happen from time to time (being forced to update absolutely everything still remains highly unlikely).
My second point is that very frequently, the actual pain points of updating do not come from the framework itself, but instead from the necessity to update a ton of plugins at the same time. Plugins are often tightly coupled to a specific version of a framework, and are usually provided by third parties. Sometimes, updating the framework even means you need to change some plugins, for lack of newer versions of them that would be compatible with the new version of the framework.
Again, updating a library can also be painful. I have simply experienced more pain from updating frameworks. Furthermore, it is often easier to live with an outdated library than with an outdated framework, since it does not cover your entire app — and you can update libraries one at a time.
Things Get Worse When You Don't Pay Attention
A few months ago, I was shocked to realize that this website's Gatsby project was two major versions behind, and I needed to add a plugin that would not work with V2. I managed to upgrade to V3, which broke my project and most of its plugins. I had to spend hours figuring things out and making them work again. The official migration guide was barely of any help, mostly because of uncooperating plugins. And after all this struggle, I am still one major version behind. The experience was particularly frustrating considering that this is a personal website: I don't have time for this! Nobody is paying me for spending days trying to make it work on a new version of its framework.
Now, I didn't write an entire blog post just because I had trouble updating Gatsby once. I have had even worse experiences in the past with other frameworks. One that comes to mind is that time I had to spend over a week migrating a Meteor project from a very old version to a newer one. In the end, I wasn't even able to bring it up to date: I had to give up after reaching a "new enough" version of the framework. It is not an issue unique to JavaScript frameworks either, as I have also experienced similar issues with Django back in the day.
Sometimes, updating is so difficult that you get "stuck" with an old version for ages, until you are ready to put in the time and effort to do a successful update... after another major version or two were released.
Projects That Rot
My two aforementioned examples have one thing in common: they both concern projects that were not maintained everyday. In the case of the Meteor project, I am talking of an application that had been sleeping for years until the client revived it.
In such cases, I find that software projects tend to "rot", that is, after a while of not maintaining them, they will get way more difficult to get back to and to maintain again, mostly because of things being many major versions behind. In some extreme cases, the application will even refuse to start because of some obscure dependency issues.
Of course, libraries "rot" too. With a major difference though: it's not your entire project rotting all at the same time and at the same pace. My experience is that projects built using more custom stacks tend to rot more mildly and more remediably.
It is Easier to Switch Libraries Than to Switch Frameworks
If your framework-based project is long lived, you might want to migrate it to different technology at some point. Or, as your project grows, you might come to regret your choice of framework, if it turns out to not scale as well as you need.
The last point that I want to make is that it is way easier to migrate to a different library than it is to a different framework. When switching libraries, you are not changing your entire codebase: only a single aspect of it. And you can even do it gradually: for example, if you wanted to move from React to Vue.js for some reason, you could start coding new pages using Vue.js. It would however not be so easy to combine Gatsby and Next.js in a same project, for instance.
A Matter of Choosing Your Trade-Off
As with most things in software development, choosing between using a framework or building a project on a more custom stack comes down to deciding what trade-off is most suitable to your situation.
A framework does all the heavy-lifting for you. It provides you a standardised, proven architecture, and you can just start coding. On the other hand, it is less flexible, might be more difficult to maintain in the long term, and is more subject to risks related to future changes in the technological landscape.
As for using a more custom stack, well, it gives you the opposite trade-off: you lose some simplicity and you have to take more decisions, but you gain in flexibility, maintainability and evolutivity.
In some cases, it is difficult to do without a framework at all. There might not be, for example, a clear path for avoiding frameworks like Gatsby and Next.js when it comes to buidling statically generated or server side rendered React websites with all the features that those frameworks provide. I have heard, however, that Next.js allows for more flexibility than Gatsby, which might help avoid some of the issues that I have described here. If I had to start this website over again, I might actually give Next.js a try for this reason.