Back

Image of the app's architecture

/ 12 min read

All-in-one meta frameworks - and how we got there

Last Updated:

It seems like we are at the brink of a new - third - era with regards to frontend technologies. Or maybe, we are just witnessing the end of an era which is the era of the prevalent Single Page Application (SPA).

It’s been a while since the last Star Wars movie was released, nonetheless we have all been able to watch the so-called Framework Wars for the past decade with supporters of each SPA framework (React, Angular, Vue, Svelte, etc.) claiming victory. It does indeed feel a little bit like Star Wars when I roll up the history of web development, bear with me.

The Original

Consider the Original Trilogy as the old days of web development - the first era - when the web was served by server-rendered HTML with little to no JavaScript sent to the client. Back then JavaScript was running only in the browser and its responsibility was to do simple content updates like hiding/showing an element, f.e. after a user clicked a button. Bigger updates to the DOM were server matters and required an additional server request just like any other asset (CSS or JavaScript files, images, etc.).

The Prequel

JavaScript - A New Hope

SPAs are a paradigm shift and take over the web two decades later by having the client do all the heavy lifting. By now, due to mobile devices and applications users are accustomed to desktop-like app experiences which the SPA eagers to introduce to the web and thereby break with the frequent server requests. Everything appears fancier, more interactive and modern now just like with the Prequel Trilogy (which - fun fact - also premiered two decades after the Original Trilogy).

Instead of separately requested, assets are now bundled in a pre-release build step and shipped to the client as part of the SPA. Through client-side implemented routing the browser navigates through the assets, pages and different states of the SPA. Except for the first initial request that downloads the SPA, requests are now only leaving the client for dynamic data transfers - but only the raw data transfers because the SPA itself handles the UI changes accordingly.

To power this shift in paradigm a lot of JavaScript needs to be send to the client. JavaScript that now has new responsibilities like client-side routing and state management. Data fetching had already been introduced to JavaScript a little bit earlier when AJAX (Asynchronous JavaScript and XML) gained popularity, allowing a page to update parts of it without a full reload. An API request to a SOAP endpoint would fetch the XML data of the part to update. This was a key development that paved the way for SPAs. In a SPA async API requests are issued to fetch JSON data from REST endpoints to update the state of an entire frontend application.

JavaScript’s original sole purpose of DOM-manipulation still plays a role in SPA but it differs a lot from what it used to be. Manipulating the DOM was straight-forward in the old days because the scope was just one page. Most of the page’s appearance manipulating JavaScript had its place in the script tag of the requested HTML page and event listeners attached to the relevant elements would trigger it. SPA frameworks, however, need to ship a complex change detection mechanism because the scope is now one of an entire application. React invented the virtual DOM1 and Angular works with zone.js2.

SPAs are making use of heavy client-side JavaScript in the seek of user responsiveness. They intend to be more reactive and interactive as they do not require full page reloads for user interactivity over a potentially latent network.

The Complexity Strikes Back

It’s less the risk of lower-end devices not being capable of running all that JavaScript and more the overwhelm in the realm of software development that curbed the SPA euphoria lately. SPAs have lead to a massive influx of packages by framework-suitably wrapping already existent libraries or offering patches/alternatives to framework flaws3. Unfortunately, the answer to too much JavaScript is even more JavaScript in the SPA world. It’s totally normal to create a SPA project from scratch and already have ten dependencies installed without having written even one line of code. Alongside the dependency chaos comes an abstraction that breeds ineptitude. Framework problems arise that occupy developers and that might have never arisen without the framework in the first place. Layers of framework abstractions and their necessary dependencies on top make it hard to get to the root of a real problem.

After all, is there really that much need for interactivity in frontends nowadays that could justify the complexity? Do we have so many Facebooks out there?4 I doubt it. As a matter of fact, the SPA framework creators themselves seem to doubt it.

The Sequel

Return Of The Server

Recently, every SPA framework has released a server-side version of its framework allowing pages to be server-side rendered within a respective framework.5 Presumably due to the discomfort in the community and the backlash of going all-in on the client.

These new frameworks are often referred to as hybrid frameworks because they can serve both client-side rendered (CSR) and server-side rendered (SSR) - including static site generated (SSG) - content. They are also referred to as meta frameworks because they are all-in-one frameworks meaning one codebase/project can serve as front and backend at the same time.

If the term full-stack application hadn’t already been invented it would have been a perfect name fit. Node.js gave JavaScript access to the backend as its first real server-runtime. Applications that had a Node.js backend and a SPA frontend were then quickly labeled as “full-stack applications” even though they had two different code bases with the frontend and backend being separated. But now, with meta frameworks we are actually really talking full-stack JavaScript.

How Does The Technology Work?

In a meta framework the developer declares per page6 whether the content needs to be rendered on the client or the server. The latter needs to be further differentiated: the developer also gets to decide if a server-rendered page needs rendering at request or build time.

SSG is the acronym for HTML that is rendered at build time. These sites cannot contain dynamic content as they are final when generated at build time and then bundled together with other assets like the JavaScript files for the client-side rendered pages. Therefore, loading SSG sites is the fastest as they do not need to await server responses nor run a lot of JavaScript on the client.

A page marked as server-rendered at runtime can be one that for instance makes use of a secret API-Key or that needs to fetch data from a database to determine the view of the HTML. It then generates the HTML and returns it as the response to the client’s request - just like in the old days.

At build time the codebase basically gets split into two deployables. One that is the Node.js backend (the SSR part + a project’s backend logic) and one that is the bundle of the SSG sites and other assets (the CSR part) that can be served trough a static asset server or a content delivery network (CDN) - the same way how SPAs are served.

For now, the technology wouldn’t be too disruptive or way different from the former full-stack application. Not too much changed except for the single JavaScript codebase and the marriage of SSR+CSR. One would still have to set up a Node.js server and distribute the frontend’s bundle.

Here comes the twist: the meta frameworks linked up with the major cloud providers to secure the one-app-feeling also for the deployment part of the development. There are installable adapters for Cloudflare, Netflify, Vercel, AWS Amplify, etc. that deploy a meta app via CLI as if it was one deployable meaning it conveniently distributes the app’s frontend via a cloud’s CDN and spins up the Node.js backend either as a serverless function or a serverful application.

The Anthology

A Personal Story

I recently had the time to try out a meta framework on a side project that I had wanted to migrate to another tech stack for a while. The frontend was a React SPA and my dissatisfaction with React grew over the last years. To be exact, I was fed up with the React wrapping of libraries which led me into being dependent on the wrappers’ maintainers and their speed to hold up with changes in the base libraries they were wrapping. I remember several releases of my frontend that would constantly throw errors or warnings in the console due to deprecations in the base libraries for instance. No offense against the library maintainers, I do know a noble developer would have made a pull request to contribute and help out but that’s not where I see myself. And to be honest, I was also angry with myself for having chosen that framework. Most of the problems I had would not have existed if I hadn’t chosen a SPA framework. And of course, over the years I also developed as a developer and looking at the code I started to see the amount of unnecessary complexity. The funny thing was that the frontend only really had three pages that needed interactivity and only one of them had dynamic data. The rest - let’s say 70% of the pages - was static.

I decided to go for a meta framework because I also wanted to have the backend inside the same codebase and to reduce its complexity.7 I had already migrated the backend once from a serverful Spring Boot (Java) application on AWS Elastic Beanstalk to a distributed network of five AWS Lambda functions, two AWS SQS queues and an AWS API Gateway endpoint.8 Back then I had picked Python for the serverless functions which soon made me hate weakly typed languages in backend projects. I also disliked the dependency, build and environment management in Python.

Astro To The Rescue

I chose Astro as my meta framework, which does not have its roots in a SPA framework. Quite the opposite, it was developed with the motivation to tackle some of the criticism that I have mentioned so far.9 With Astro, one can write pure HTML and opt in to TypeScript server-side and client-side, avoiding the wrapping and abstraction the other competitors impose. There is also no need to miss out on composability and reusability of code, hallmarks of the SPA’s component based model, as Astro adapted this good part of the SPA.

For me, the reduction in complexity was massive with Astro. The majority of the pages is now written in pure HTML/CSS and generated at build time (SSG). Only two SSG pages have some JavaScript in their script tags as they need a little interactivity. On top of that I only have one page that renders server-side at request time on a serverless Vercel function, which is the form page for booking an event. The form is an easy to understand 267-line Astro file that contains the HTML, the form validation, submit and server logic as well as the interactive form updating JavaScript in the HTML’s script tag (everything’s co-located). A mind-blowing comparison, my React version of that form needed the following counts of state managing React hooks throughout eight components:10

  • 8x useState()
  • 4x useEffect()
  • 4x useRef()
  • 3x useContext()
  • 2x useReducer()

Conclusion

Meta frameworks are too young and it’s too early to tell whether they mark the beginning of a new era or a paradigm shift in web development but I do think that the SPA era has come to an end - at least its unquestioned predominance. Apart from meta frameworks, SSR solutions have recently gained general popularity, which definitely shows that the server is back.11

Nonetheless, SPA is big in the corporate enterprise world, which is slow to adapt. I also fear that the strong bond between meta frameworks and serverless does scare off enterprises. Serverless never managed to win over the enterprise world and - I guess - the bad media lately about exploding costs with serverless have put an end to that quest.

I, personally, felt a huge release having finally closed this SPA chapter for myself by migrating to Astro. Going back to pure HTML and “Vanilla JavaScript” was an eye-opening experience for me by seeing how easy web development can be when you don’t make yourself a slave to a SPA framework.

Footnotes

  1. The whole DOM is replicated in-memory where changes are observed and executed before they are being rolled out to the real DOM.

  2. A library that basically rewrites all Web APIs by providing the possibility to group their executions in trackable contexts, this way the UI can be altered via the tracing of its async DOM events.

  3. react-query is probably one of the most famous examples offering an alternative to the flaws of React’s data fetching.

  4. React was created by Facebook because of their needs for more interactivity in clients.

  5. React has Next.js, Vue has Nuxt, Svelte has SvelteKit, etc.

  6. In a component based framework the declaration may be on the component level.

  7. Working on one codebase with a meta framework also reduces complexity by having the entire app served by one domain, eliminating problems like CORS or the management of a second domain for the backend.

  8. See my blog post for that migration.

  9. See their website for this.

  10. Hooks are the main tool in React to manage the states of a SPA. It can show where the complexity exists of an app. This, of course, does not even contain the server state yet.

  11. See the hype about HTMX.