Web stuff that got my interest


· 9 min read

I started writing about patterns, tools, and opinions I would consider if I were to build a framework, since I don't have the time or knowledge to do it.

But then I realized the scope has to high, and the post was becoming a mess 😅

So here is a small part of it: the patterns, tools and emerging web technologies that I believe will be important in the future of the JS ecosystem.

Server Components

NextJS introduced React Server Components, making it the only mainstream frontend framework with a backend-first approach made for apps.

import { FancyAnnouncement, Navigation } from "./client-components.tsx";

async function ServerComponent() {
  const announcement = await db.announcements.findFirst();

  return (
    <>
      {announcement ? (
        <FancyAnnouncement announcement={announcement} />
      ) : null}
      <Navigation />
    </>
  );
}

To understand them, I recommend Josh Comeau's Making Sense of RSC, the RFC, and the video introduction. In short, RSC is to UI as GraphQL is to data.

The debate over whether RSC are worth it has likely exhausted many people over the past year. RSC is great for requests that cascade. RSC is bad for client data invalidation. And so on, a long list of poorly documented tradeoffs.

I believe that Server Components have a future because they provide control on what is server (only) rendered and what is client rendered, while “traditional” client first frameworks only give control on what is client rendered. This while keeping an application-like model that does not really depend on HTML.

One could mount client-first apps made with client components (for example, an old React Router app) on a monolith made of server components (for example, any RSC framework). Old assumptions and current tooling might degrade the experience, but it isn't a limitation of the server component pattern.

Now, we are missing best practices and tools that help enforce them. Like “move client boundaries higher” or “hydrate streamed server data in client components”. And also, new frameworks with different takes on that pattern.

Checkout Vercel ideas in NextJS docs, Nuxt Server Components, Fresh Island Architecture (Deno + Preact) and Waku (minimal RSC compatible framework).

Fine grained reactivity with signals

SolidJS shows that it is possible to provide top notch reactivity without a templating language, with JSX and function-based components like React. They're even experimenting with fine grained async and error recovering.

function Component() {
  const [amount, setAmount] = createSignal(0);
  const price = queryCurrentPrice();  // async signal

  return (
    <div>
      <Suspense fallback={<div>$...</div>}>
        <div>${price()}</div>
      </Suspense>
      <div>
        <button onClick={() => setAmount(a => a + 1)}>Add more</button>
        <div>{amount()}</div>
      </div>
      <div>
        Total:
        <Suspense fallback={<div>$...</div>}>
          <div>${price() * amount()}</div>
        </Suspense>
      </div>
    </div>
  );
}

Svelte switched from compiler-based reactivity to a runtime-based reactivity with runes. Vue improved its reactivity performance thanks to research done in alien-signals. Angular added signals in version 16.

There are attempts to provide a shared signal primitive. The alien-signals library, Preact with their signal libraries, Solid signals, and even the TC39 signal proposal. The idea is to provide a minimal, performant and standard primitive to build reactivity in JS, without requiring framework or library adapters.

Signals isn't a new concept, but it's becoming a core primitive. It's simpler than observable. Instead of doing been a multiple-push collection (see RxJS guides) with some railway pattern on top, signals focus on fine grained reactivity, independent and without exposing the underlying reactive algorithm.

Checkout Ryan Carniato's blog. The React vs Signals: 10 years later post is a good start with great linked resources. Also, read Dan Abramov's responses talking about the tradeoff that React made with reactivity: treating "computed" as an optimization, not a requirement, for reactive components.

Streaming objects

Streaming is fundamental to the web. Data transfer from one device to another isn't instantaneous. Sections of pages could require data that is slower to fetch.

The main benefit of streaming is possible thanks to out-of-order flushing of async fragments. Some frameworks like Marko focus on HTML-first based solutions, and now, some JS-based solutions are gaining a lot of traction.

// server
define("getTodos", async () => {
  return {
    fetchedAt: new Date(),
    data: db.todos.find() // find is a promise
  }
});

// client
const response = call("getTodos")
console.log(response.fetchedAt) // the date object
const todos = await response.data
console.log(todos) // the todos

Mainly, React Server Components with it's streaming solution: RSC wire format. There isn't documentation specifically about it, so read about Alvar Lagerlöf's RSC Devtoll. Another great solutions are Seroval, used by Solid, and Turbo Stream, used by Remix and in experiments like Preact Server Components.

Promises are becoming a core primitive for streaming data between client and server, from Remix, NextJS, TanStack's Router, SvelteKit, and more.

A lot of frameworks implement their own internal implementation due to the need for specific features and optimizations. A primitive way to stream objects could be the “JSON 2” for frameworks, providing an interface to send complex JS primitives like promises across the wire.

New takes on backend primitives

AsyncLocalStorage is an Node API that allows to create stores (or context) that persists across async calls, that has been stable since Node 16.4.

import { request } from "flask-js";

async function getUser() {
  return db.users.getByHeader(request.headers.Authorization);
}

handler("/users/me", async () => {
  const user = await getUser();
  return { user };
});

Stores allow passing values implicitly. A popular framework that does that pattern is Flask, that has a global request proxy that is set to the current request implicitly. In JS, NextJS has started using AsyncLocalStorage to provide dynamic request APIs thought RSC, like with the headers() function.

This pattern allows creating primitives for a complex and composable system, somehow like React Hooks or Vue's Composition API. Nitro has experimental support to avoid passing the event thought utilities. Even in the browser, React is waiting for the Async Context TC39 proposal to improve state transitions.


There is also a surge of server functions. React has Server Functions included in their framework. SolidStart follows a similar pattern, adding a single-flight strategy. TansStack goes for an different, more personalizable approach. There probably are going to be standar vite plugins in the future to build them.

// server (create a referenceable function post bundle)
export async function saveTodo({ text }: {text: string}) {
  "use server";
  await db.todos.save({ text });
}

// client (references a server function post bundle)
await saveTodo({ text: "Finish this post" })

The main advantage of server functions is the deep integration with the framework, like form submission without JS in React and single-flight queries to avoid cascades in SolidStart. But I don't thing that we have clear primitives yet to build on top, like the other primitives and patterns mentioned in this post.


On the more BaaS side, I want to shout out Convex, that has a imperative primitives following the command-query separation model with real time and consistent queries and ACID transitions. There are also great innovations in Cloudflare: SQL DOs and the soon to be released container platform.

Unified tooling

In the same way Astral, launched at 2022, with VC money, from the development of amazing and rusty tools for Python (Ruff, and then uv at 2024), void0 launched at 2024, for the JS ecosystem, from tools like Vite and Oxc.

Vite released the environment API, breaking the constrain of having only client and SSR modes, allowing to target different environments, from different runtimes like node and workerd, and different targets like server, SSR, and client builds (like RSC). This will make frameworks to be “just a vite plugin”.

// Example configuration file on React Router (Remix)
export default defineConfig({
  plugins: [tailwindcss(), reactRouter(), tsconfigPaths()],
  tests: { browser: { enabled: true }}
});

Besides new capabilities, Vite is expected to have a performance boost soon thanks to Rolldown, a new bundler designed for the future of Vite. It replaced the dual bundling strategy with a simpler, performant and powerful one. Rolldown also is the fastest bundler for JS in the browser.

Oxc is the toolchain that powers Rolldown. Besides the build tools, it has a Linter (oxlint) and an prototype of a formatter (Prettier port). Also, it its really fast parser, tranformer and linter.

Vitest replaces Jest, thanks to been tightly integrated to the Vite toolchain. It has a lot of features included, including an experimental Browser Mode to run tests on a browser.

The importance of this tools is greater looking at the ecosystem around them. For example, Storybook has first class Vite support from 2022, and in 2024, partnered with Vitest to add great testing capabilities to their component isolation tool. Vite's commitment to an active ecosystem is admirable.

Local-first

Our devices are fast. Many tasks that previously required a server can be done on the client. And even if some stuff requires a server, it could be synced later.

Google Chrome is pushing to have AI on the browser. Last year we got the really experimental window.ai, that didn't event get an announcement, yet it had the same philosophy of Apple Intelligence: AI Should be build-in.

Now, Chrome has some AI API experiments, multiple on witch are available on Origin Trials (can be tested on browsers without users changing features flags). Even Firefox is doing some experiments. There are initial drafts of standards in the Web Machine Learning Working Group, for example, the Prompt API.


The other side of local-first that I'm watching is sync engines. Sync engines are typically build on top of databases to sync data to clients, mostly in real time. Firebase is the old giant in this space, and now there're solutions like InstantDB and Zero, both Open Source and with arguably better DX for DB interactions.

Where a lot of those systems fails is on the extendability of the backend. Most expose the database schema, and rely on implicit database triggers and limited permission rules to add business logic. Convex, that has plans to add a object sync engine that looks promising. Checkout their Map of Sync post.

Other, more concrete stuff