The Good, Bad and the Maybe

Webtech is always changing. The times of software sitting on relatievly the same stack for ten years are gone. You’ll be lucky if the frontend you write today lasts without major user interface churn for two years, and incredibly fortunate if the ui framework doesn’t lose developer popularity or crawl to a slog in five years. The backend ecosystem is more stable, but you might say it is just as off the Rails as frontend development.

Here’s my recommendations on all things JavaScript Webtech.

Languages

We’re talking about JavaScript webtech here. If you skipped over the CoffeeScript era, that’s great. For now, the languages we really pay attention to are TypeScript and JavaScript itself.

TypeScript

If you can start a new project today with TypeScript, using TypeScript is easier. Migrating a legacy codebase is a challenge. The initial warmup time learning TypeScript feels steep, but that’s OK. You don’t need to use recursive types, you don’t need crazy interfaces. Just have it setup. Use types where they feel like inline documentation. Object shapes, arguments, and maybe that’s enough to get started.

If the tooling can support TypeScript, turn it on.

With TypeScript, you should turn on strict. It will cover so many edge cases for truthy, null and undefined checks. You will save time with this on, becuase you will avoid rewriting dozens of files.

🍌 As a cautionary plea, it’s easy to get into the groove of TypeScript feel like you’re empowered to create every typeful concoction you can think of. Resist the urge.

JavaScript

At the language level, there are occasional bursts of progress. ES6 kicked things off in 2015. Roughly eight years later, the cadence of progress is still present though sometimes it feels slower than perhaps other ecosystems (thinking about Java, surprisingly, and PHP 8.x, incidentally). Nevertheless, language churn for change’s sake is wasteful.

If you can only write plain old JavaScript, that’s fine. You will need diligence though. You will need to have additional runtime validation to ensure shapes and structures are what you say they are. You will need many more tests!

Modern Bundlers / Transpilers / Compilers

Babel and Webpack, they’re our good friends. But they’re slow now.

swc

  • swc
  • swc is written with Rust
  • SWC is sponsored by Vercel, primarily because of their integration into the “Next Compiler”
  • kdy1 is the primary core developer

esbuild

  • esbuild are the fast core tools
  • esbuild is written in Go
  • Evan Wallace is primary core developer

It’s not unlikely that another core tooling option will come around.

React and friends

It’s a React world out there. There are still other options though. Svelte, Solid, Vue. We will not mention Angular. Alpine, htmx and probably dozens more. Oh, plus jQuery.

But I think it is safe to say, if you’re reading this, you’re likely going to use React by default.

Modern React “Frameworks” for “Apps”

The React ecosystem is the largest frontend “framework” developer ecosystem for “apps”.

What is a “framework” and and what is an “app” anyway?

Single Page Application (SPA)

There’s a lot of debate about single page apps right now. With the advent of the “new” react.dev docs, and in particular the “Start a New React Project” page, there’s even more confusion and consternation about SPAs, MPAs and Hybrid apps.

There are all sorts of reasons why an SPA might have poor performance, directly impacting user expereince. Slow connections, high latencies, huge bundles, slow first paints, the list goes on.

For the most part, I think that for commodities apps like adminitistative portals, back-of-house configuration management tools, back-office management tools, data dashboards, these signle page apps are fine.

Given that, I think there are situations and circumstances where a Single Page App is not what you should reach for:

  • When you need your site and pages on Search Engine Result Pages (SERPs)
    • You’ll never get an SPA properly decorated in on the SERPs and properly ranked; not enough content, not enough meta tag control, no matter what they tell you about Googlebot crawling SPAs
    • If you’re thinking about e-commerce at all, you cannot use an SPA
  • When the majority of your pages are full of static content
    • The two other scenarios cover this; server side rendering / hybrid apps, and static site generation
  • You need robust redirection
  • When you cannot make portions of your frontend code effectively public
    • This almost never happens; put special secret sauce business logic behind APIs where they belong and your treasure will stay software

CRA

create-react-app is dead. What will happen to create-react-app?

Vite

Vite is your best bet for an SPA. It is the alternative to create-react-app.

It’s fast because it uses esbuild or optionally swc as of v4. Its community is thriving, its configuration out of the box is great, it supports TypeScript, it has a third of the dependencies of CRA. There are some downsides of course. Jest is wacky with ESM, so Vitest is a critical alternative testing tool, but the porting process is tedious although not laborious.

I have not ported an application from CRA to Vite yet, but aside from TypeScript and some other high level configuration, I can’t think of anything in the actual application code that could break if your previous CRA configuration was truly vanilla. Having gone through a few fresh Vite projects now, I am quite happy with it.

Webpack

You might say: “hey, I have such special needs that I must have a customized webpack option.”

You’re wrong. You do not.

Server Side Rendered / Hybrid Apps

In 2021, everyone’s svelte friend Rich Harris presented the talk “Have Single-Page Apps Ruined the Web? | Transitional Apps”. In this talk, Rich discribed the pros and cons of Single Page Apps and Multi-Page Apps and tried coining a new term called Transitional Apps. I think the term fell short of popular adoption. Usually I hear this class of app as a hyrbid app. That’s fine, but I feel like we had hybrid apps for a long time, where we were doing funny tricks. This generation of hybrid apps are much more integrated and perhaps deserve that new name now.

Get your pages listed on SERPs! Get indexed today!

Rolling your own SSR with React today is not easy. Sure, you can do it, but it will be so customized, unrepeatable and unknowable. This is a Dunning Kruger decision. If you’re asking if you should build your own framework that straddles server side and client side, the answer is no. If you’re asking if you should build your own framework because you have expereince with their deficiencies, have articulated needs for their affordances and have explored many available options, then perhaps.

Next

Next and Remix are the transitional hybrid options here today.

Next has been around for a while. It has some quirks, most of them obligations of its transitional hybrid nature. Not all of these quirks are bad, but they’re things to think about, and things to be unfamiliar with if you have only worked on JSPs or CRAs (one of these is not like the other, almost by definition).

  • routing is based on the filesystem route
  • components are not colocated with their pages
  • there are weird magical functions that run if they’re defined (getStaticProps, getServerSideProps, etc)
  • i18n is a rabbit hole
  • authentication and authorization is a deeper rabbit hole
  • middleware gets a funny runtime
  • middleware gets weird behind a cdn

These quirks may change or disappear with the upcoming App Router from Next. This new mode also enables React Server Components (RSC) with Next 13 in the app/ directory.

The internals of Next are solid. It runs swc under its hood, so its fast enough. If you need some prerendered pages, some server side rendered pages (on demand) and some SPA-like functionality, then Next is great.

My hestiation is not the technology necesarily, it’s on the use case. The split between hybrid and spa are still pertinent. Next gives you great powers, but with great powers comes great responsbility, and in this case, new downsides to balance in your compromise.

  • 🙉 Do you really need your pages indexed? Not really, especially if they’re locked behind corporate firewalls or are entirely authenticated experienced.
  • 🙈 Do you have a centralized data source (instead of distributed)? With data centralization, running at the edge will not be a major latency improvement.
  • 🙊 Do you really have developers that are ready to solve entirely new types of problems that come up with state shared between the server, the edge and the browser? You might, you might not. But it’s not free.

Remix

Other than Next, there’s Remix.

Remix is still on my watch list. Hydrogen is sort of Remix too, and Remix joined Shopify. I think the tooling demos well in videos, but have not spent the time to try it and tinker with it. Let’s wait another six months and see where things land.

This cannot be all there is. All of these tools are tool complex for their own good.

Static Site Generated

Brochure-ware at its finest. Or maybe a relatively stable marketing page?

If you have markdown, or some realtively narrow set of pages with data sourced from an API, static site generation works. This can break down when you need interactive editing (for that go back to Next), or when you have so many pages rebuilding them is wasteful.

  • 🔴 Not recommended: Gatsby: it feels a lot like JavaScript flavored WordPress
  • 🟢 Tentatively Recommended: Astro: it works with everything, even React!
    • .astro files use jsx syntax, but act more like one of those new React Server Components in that they only render HTML at the end, and bring no additional JavaScript payload down
    • seriously, incredible work was done to ensure interop between React, Vue and others
    • react usage would bring in the react bundle, but its on demand and situational
    • uses vite under its hood, which in turn uses esbuild under its hood; new tools all the way down
    • successor development team to snowpack; well, hopefully it does better than that
    • island architecture is awesome; only load if it’s in the viewport and only this small chunk is impacted
    • you can even use plain js, with the bundler, and modern tooling, and packages… no framework at all
  • ✅ Recommended: Next: it works with everything, including static sites!
    • My annual card site is built with Next and uses no SSR features
    • Next shines in this regard when coupled with its parent Vercel service or a similar edge provider
    • Static exports are available for a truly S3 class experience

React flavored State Management

Redux is a lot. Sagas are too much. Redux Toolkit makes it better, but it is still a lot.

Maybe you don’t need a service bus on the frontend.

Consider:

Tree-Local-State

Components might have state, subtrees might have state, and your entire app might have state.

For subtree, or what I’ve liked calling, tree-local-state, you can do a lot with context.

  • Context often has performance issues; you put a context at the top of your entire app, you shove stuff in like Redux and your app gets slower and slower because your entire tree rerenders
  • Context is fine if you use it as a read/write 80/20 store; auth states might be fine in a Context as long as you read and you don’t write ever again
  • Really consider Context as an escape hatch for props drilling

Server & Client Shared State

react-query is usually more than enough for fetching server side state. It provides for a great developer experience, and lends itself out of the box to a pretty decent user expereince too.

Complex State

zustand is a great option for more complex state, SPA-style cross page data propagation use cases

If you have extraordinarily complex state, Redux with Redux Toolkit has its place.

Offline

A few years ago, I was involved with multiple projects that insisted client side state persistence for offline support. Despite all these years of development and industrial growth, I still think offline webapps are a very poor experience. Users have no idea what’s going on. Engineers barely have any better tools to make it a decent experience. In general, if offline is where your web app will be used, give all that PWA business a try and enjoy the fruitless pursuit of the perfect sw.js, but also seriously consider a real app or being dependent on generally available Internet access.

Server Side / API

With something like Rails, you can have your frontend and your backend all sort of as one thing. Even though the JavaScript ecosystem is uniquely suited towards driving the frontend and backend, the interop bridge between is still clearly in active development. Just look at Next and Remix for those examples. Even if you consider those a “backend”, they’re not the whole story. There’s no Rails. There’s no Laravel.

If anything, Next and Remix are the fabled Backends for Frontends dream fufilled, but instead of being an composition API, its composing data from API into UI. What about the actual backend, where your database lives and you interop with your own API friends?

Nest

Historically, I have promoted Nest as a viable option for an actual backend.

For Nest, I have unresolved issues, even after years of trying:

  • Annotations are half baked, not spec, and feel ergonomically odd in the language
    • They’re great when they’re magic you consume, but if you need to go any deeper, they’re just so obtuse, and it certainly does not help that TypeScript and JavaScript have no runtime reflection, so its just an illusion
  • Silly defaults
    • Why was strict off?
  • The Nest service container is kind of weird, it uses, well ironically now that I think about it, nested modules that isolate services into logical units
    • Dependency Injection (DI) isn’t for everyone, though it has its uses
    • With Spring, it all lived in one service contain freely, no modules required
    • The register/bootstrap process, unlike Laravel, isn’t clear and it’s not well described
  • TypeORM, the ORM that was highly promoted in the first party docs, was effectively abandoned
  • Integration with npm ecosystem tools is often painful
    • There are adapters for many popular libraries and patterns, but not everything
  • Dependencies… so many… dependencies
    • The more dependency surface area you have, the more time you spend auditing your security than building something useful
  • Nest still ships without a modern compiler backend (esbuild, swc) making it slower than it needs to be
  • Nest ships without new options for ESM

Arguably it is valuable in a larger team setting because it provides much needed structure. Files go here, they’re named this way, there are interfaces you can recognize and research.

I think you can use it, but you need to make the choice. The hard choice.

Express

Yep, good old Express. Express 5 will never come out of beta. It uses an API design that’s from the early days of Node and we’re stuck with it for now.

Popular middleware like Passport are beginning to age out, which is great, because the momentum that express brought will age out too and something else can come along.

Other backends

Well, why not..

  • Fastify
    • It’s definitely work looking into, but everyone knows express
  • Koa
    • Effectively a rewritten fork of Express, with async out of the box
    • Too much taken out of the box unfortunately

In the end, if you use express, with typescript, with swc, I think you have a winning combination to explore your ideas and grow your team. Then you can port that into or Go or Rust or whatever. That will be fine.

Non-JavaScript backends

I think JavaScript earns a rightful place in your backend stack, but only up to some threshold. It is not perfect by any means. If your team has more familiarity with another stack, consider that. But know that the vision of “backend for frontend” persist and it is coming sooner than later.

Package Managers

  • npm has a legacy of poor performance, lagging feature sets and not being yarn
  • yarn has a legacy of being great, being fine, and having no viable upgrade path to any later version
  • pnpm is probably fine
  • berry is probably fine, but you will have to learn more about it

In many ways, npm vs yarn is like maven vs gradle, everyone picks gradle but maven hosts the registry and works as a tool OK too.

For now I recommend yarn, and while I have tested berry I have not properly field tested it against real codebases.

Validation

Validation is often overlooked and undercooked.

  • 🟠 ajv
    • pro: json schema oriented
    • pro: TypeScript support
    • con: limited support for imperative validation
    • con: moving target
    • con: verbose
  • 🟠 yup
    • pro: easy to get started
    • con: mixed support for complex validations (or imperative validation)
    • con: major type-rewrite in recent history
  • ✅ zod
    • pro: easy to get started (similar to yup)
    • pro: there are adapters to convert zod schemas into json schemas

Schema validation is not the same as business logic validation. For example, if you have a schema {count: number}, that’s pretty easy to validate for all of these libraries. Suppose you need to validate the number is in a range. Also easy. What if the number has to in a fixed range, and also related to a value in a database? That feels like it is outside the scope of schema validation, but having schema validation that could do this, or share its error structure with you is worth it.

For now, I recommend zod.

fetch and friends

For hybrid apps (with Next), I recommend fetch. Out of the box, Next will provide fetch in its middleware and server side. fetch will work in the browser on the frontend. fetch is great and all, though the developer experience is not always so productive. Consider building your own middleware and your own intermediate API layer to handle common situations and oddities.

For an SPA, I recommend axios. Axios has been a go to library for years. Likewise, for many years it felt like it was abandoned, languishing in a 0.x version. Now that it is 1.x, I feel better about it. I hope a 2.x variant comes around with fetch internals.

got and ky might be awesome. However, part of the ecosystem is locked out of using their latest versions because of ESM incompatability.

Follow me on Mastodon @ryanmr@mastodon.cloud.

Follow me on Twitter @ryanmr.