The LARC Story

Or: How We Learned to Stop Worrying and Love Asynchronous Components

Every technology has an origin story. Some begin in garages, others in corporate research labs. LARC's story begins with a simple observation: web development shouldn't be this hard.

The Problem That Wouldn't Go Away

Picture this: You're building a web application in 2020. You need to fetch some data from an API, display it in a component, and maybe update it when the user clicks a button. Simple, right? Yet you find yourself drowning in boilerplate—state management libraries, effect hooks, loading states, error boundaries, and an ever-growing node_modules directory that could collapse into a black hole at any moment.

The React team gave us hooks. Vue gave us the composition API. Svelte gave us reactive declarations. Each solution was elegant in its own way, but they all danced around a fundamental truth: components are inherently asynchronous, yet we kept treating them as synchronous with bolted-on async features.

Think about it. A component might need to:

  • Fetch data from an API
  • Wait for user input
  • Subscribe to real-time updates
  • Coordinate with other components
  • Handle errors and retries
Every single one of these is an asynchronous operation. Yet our component models were built on synchronous rendering with async tacked on as an afterthought. We were trying to fit a round peg into a square hole, and then wondering why we needed so much glue.

Enter LARC

LARC (Live Asynchronous Reactive Components) emerged from a deceptively simple question: What if we built components async-first from the ground up?

Not "async as a feature you can add." Not "async as a pattern you can implement." But async as the fundamental paradigm—the water the fish swims in, so natural it becomes invisible.

The core insight was this: if components are inherently asynchronous, let's make them JavaScript Promises. Not wrapped in promises. Not returning promises. Actually be promises. After all, a promise is just a value that exists somewhere in time. A component is a UI element that exists somewhere in time. The parallel was too elegant to ignore.

// A LARC component is just an async function
async function UserProfile({ userId }) {
  const user = await fetch(`/api/users/${userId}`);
  return html`
    <div class="profile">
      <h2>${user.name}</h2>
      <p>${user.bio}</p>
    </div>
  `;
}

Look at that. No useEffect. No useState. No lifecycle methods. No loading states. The code reads exactly like what it does: fetch the data, then render it. The asynchrony is right there in the language, not hidden behind framework abstractions.

Design Decisions and Trade-offs

The Great Hydration Debate

Early in LARC's development, we faced a critical decision: server-side rendering. The React world had spent years perfecting hydration—that delicate dance where server-rendered HTML comes alive on the client. Should LARC follow suit?

We chose a different path: streaming server rendering with progressive enhancement. Instead of sending static HTML that gets "rehydrated" into a full client-side app, LARC streams components as they resolve. A slow database query doesn't block the entire page—it just means that component arrives a bit later.

This decision had consequences. You can't "hydrate" a LARC app in the traditional sense. But you gain something more valuable: true progressive rendering. Your page loads fast because it's actually fast, not because you've carefully orchestrated a theatrical performance of looking fast while secretly downloading megabytes of JavaScript.

Some people called this controversial. We called it honest.

Reactivity Without the Reactivity Tax

The next question: how do components update? Every framework has its answer:

  • React: Immutable state and reconciliation
  • Vue: Proxies and dependency tracking
  • Svelte: Compile-time reactive statements
  • Angular: Zone.js and change detection
LARC took yet another path: explicit subscriptions. If you want a component to update, subscribe to a signal, observable, or any async iterable. When the source emits, the component re-renders.
async function LiveCounter({ signal }) {
  for await (const count of signal) {
    return html`<div>Count: ${count}</div>`;
  }
}

This isn't the most magical solution. You can't just mutate a variable and expect the UI to update. But it's explicit, predictable, and has zero hidden costs. No virtual DOM diffing. No proxy overhead. No compiler magic. Just async iteration—a standard JavaScript feature since ES2018.

The trade-off? You have to think about your data flow. LARC won't guess what you meant. But in exchange, you get complete control and no surprising performance cliffs.

HTML Templates: Tagged or Literal?

Here's where we made our most controversial decision. JSX had won the mindshare wars. Even Vue 3 added JSX support. Surely LARC would use JSX, right?

Nope. We went with tagged template literals.

// LARC style
html`<div class="${className}">${content}</div>`

// Not JSX
<div className={className}>{content}</div>

Why? Three reasons:

  • No build step required. You can write LARC in a