JavaScript

On the State of JS Frameworks

Ethan Glover

After I wrote Moving Away from Vercel and NextJS, I converted a couple of projects over to AstroJS. I do not regret it at all, Astro's simplicity made it very easy to move over my projects and get deployed to CloudFlare Pages with 0 effort.

But there's another project I've been struggling with, Introspect. My home for my recommended courses.I'm set on taking full advantage of modern JS frameworks for Introspect. But the problem is we're in the early stages of changing times. It's something I hinted at but didn't fully understand in React Development is about to Change.A move to make static websites as dynamic as single-page apps. I think it's very interesting that a lot of frameworks seem to be converging on this idea. But the question is, who is doing it best?

I've been exploring different JS frameworks and I've started to develop a few requirements that I need met.

  • Uses JSX for eslint-config-ethang compatibility.
  • Router is coupled with forms for data invalidation.
  • Makes use of Browser APIs over React APIs.
  • Has adapter support for CloudFlare Pages and using Edge runtime rather than Node. (With NodeJS compatibility for things like Buffer.)
  • Makes use of form action interception so I don't have to write JSON endpoints, but provides good escape hatches for building and using JSON API's.

Let's talk about the options.

HTMX: The JavaScript library that pretends not to be JavaScript to appeal to backend-only devs who hate JavaScript. The HTMLX Essay page is full of rants about how nothing follows REST, and why HTMX's specific opinion of REST + HyperMedia was the way the web is MEANT to be. HTMX is not a framework, it's an opinion. It's use case is more for augmenting frameworks that lack the capability to build modern frontends. But HTMX itself can not be used to build a modern frontend. As it lacks abstractions to deal with DOM manipulation (it has only a few basic functions). HTMX has no router, and thereby no intelligent way to handle data holistically.

No JSX

No Router

Abstracts Browser APIs into HTML.

Needs framework for work with edge such as creating Buffer's for PassKey Auth.

No form action interception, defaults to AJAX APIs.

Astro: This is of course what I landed on originally. And it works great for my blog and CMS backed apps. Linting it isn't great. Even with modifying my own config, dealing with .astro files have been a small, but acceptable headache. Because Astro just builds to static files, you can do the same things with it that you can with HTMX. Except you can use JSX and TypeScript to achieve it. Which means much faster development. Also, with Astro you have to choose between static HTML forms (with full page reloads) or using a UI library for AJAX submissions + data invalidation. For an app like introspect, this means writing JSON API routes for a lot of interactions (including many future planned features with a lot more user interactions). Astro is a little weak with dashboard type apps, but to be fair, so was SPA. SPA only made them viable/realistic.

Uses JSX

Frontmatter allows you to write server side logic for form actions.

Exposes plain Request/Response objects to use native browser APIs.

Excellent CloudFlare adapter that automatically adds NodeJS compatibility. Deploying to pages doesn't feel like you're deploying to a different environment. Even environment variables are available on the newer import(dot)meta(dot)env. (process does not exist on edge runtime).

Astro does not have a client side router that allows form actions to invalidate data and refresh that data without full page reloads.

NextJS: The framework that swears it's stable, even if the entire world is disagreeing. NextJS released an incomplete version of what I've been looking for and it's the reason I'm looking for an alternative. Before App Router, people used NextJS JSON routes and tRPC to write endpoints for their server logic and called them with TanStack Query which handled client side data invalidation. It's the writing endpoints I'd like to get away from. A GET request to a route should return HTML, and a POST request should run some logic and invalidate the content/cache of the GET request. That is the basic idea Next has been trying to move to, but by choosing to go server-side cache instead of client-side cache they've created some difficult to work with, and very broken APIs that depend on Vercel infrastructure. And to achieve this, they are exposing experimental imports from the React library for tracking form status, and using RSC which is canary in React. But according them... it's “stable”.

Uses JSX

This gets a no because this is not currently possible without using experimental React imports. Anyone using stable APIs must depend on JSON API routes and TanStack Query the same way we did before app router.

Use of browser APIs is severly limited because of the Vercel infrastructure strategy, server-side caching, and HTML streaming. Developers have lost access to simple things like headers and cookies which makes it difficult to work with Browser APIs.

NextJS was built with edge runtime in mind. However basic things like the Image component do not work outside of Vercel. Every provider has to go through great pains to support NextJS without guidance on how do so. To get images to display with CloudFlare, they have a guide on creating a custom loader for images on CloudFlare CDN. But that is not a complete solution.

Same as the second point, in terms of intercepting HTML form actions, these features are experimental.

Remix: This framework has always felt like a response to NextJS. Always comparing themselves to Next, always proclaiming that their way is the right way of doing things. And granted, in terms of how things are evolving right now, Remix is in a great position. I rebuilt parts of Introspect with Remix and really like the way they handle forms. Most of it's core features are specifically what I'm looking for.

Uses JSX

Remix was basically made to couple React Router, server side logic, and HTML forms.

Does a great job of exposing Browser APIs where they're used and encouraging their use as much as possible.

The CloudFlare adapter is a bit rough. You have to get environment variables off of context in loaders/actions, which isn't the end of the world, but can be annoying. And NodeJS compatibility is not available.

By default Remix encourages using React Router + HTML forms for data invalidation, which is amazing. But it also provides a ‘useSubmit’ hook that can be good for running client side logic before posting a form while still keeping the invalidation features.

QwikCity: I have been watching Qwik for a very long time and have been enamored by it's idea of resumability. Qwik is able to make the page interactive extemely quickly by using a service worker to pre-populate the browsers cache instead of hydrating with JavaScript. However, the framework built around it isn't great. There's not a lot of quality support or much of a full time team. APIs are inconsistent and reactivity is often delayed causing server and client to be out of sync enough to cause major bugs. This alone makes QwikCity a non-starter for me.

Uses JSX

QwikCity doesn't really have much of a client-side anything. It uses a very raw and incomplete implementation of signals to synchronize state which causes the server to often be one step behind the client. And form values are not pulled from input values. Put these together and forms are unreliable.

Doesn't really use most browser API's properly. You don't get pure request/response objects on the server and redirects often only work in what they call “MPA mode” which apparently just means when JavaScript is disabled.

The CloudFlare adapter for QwikCity works great as long as you use the CloudFlare template. However, the issue I have here is Qwik serialization. If you care about NodeJS compatibility, you're thinking about things like Buffer and Crypto. Because these can't be serialized, it's often difficult to figure out how to get them through Qwik's numerous “serialization boundaries”.

Again the issue here is synchronization. You can not reliably predict what a server will return, and when a UI will update. The API's here are not clear and have many inconsistent behaviors due to bad state management and the framework apparently being built primarily for having JavaScript disabled.