The LARC Story
Or: How We Learned to Stop Worrying and Love Asynchronous ComponentsEvery 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
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
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:
tag and it just works. Try that with JSX.class to className or for to htmlFor. HTML is HTML.The downside? You lose some type safety and your linter can't validate your HTML structure. We considered this an acceptable trade-off for the ergonomic wins. LARC is for people who like writing HTML, not for people who tolerate it.
The PAN: A Network Protocol for Components
Here's where LARC gets weird (in a good way).
Traditional component communication follows familiar patterns:
- Props down, events up
- Global state management
- Context providers
- Dependency injection
We needed something different. Something that felt less like a hierarchy and more like... a network.
Enter the PAN (Page Area Network).
The Origin of PAN
The name came from a brainstorming session that was getting nowhere. We'd considered "component mesh," "reactive bus," "signal network," and other boring enterprise-y names. Then someone joked: "It's like a personal area network, but for a page."
PAN. It stuck immediately. It was short, memorable, and just slightly cheeky. Plus, the network metaphor was perfect. Components aren't calling methods on each other—they're broadcasting on channels and listening for messages. It's publish-subscribe, but for UI components.
// Broadcasting on the PAN
pan.emit('user:login', { userId: 123, username: 'alice' });
// Listening on the PAN
async function WelcomeMessage() {
for await (const { username } of pan.on('user:login')) {
return html`<div>Welcome back, ${username}!</div>`;
}
}
The PAN isn't revolutionary technology. It's event emitters and observables, patterns that date back decades. But giving it a name and making it first-class changed how people thought about component communication. You're not fighting against the framework's hierarchy—you're using a purpose-built communication layer.
PAN Design Principles
The PAN follows a few key principles:
user:login or cart:add. This prevents collisions and makes systems self-documenting.console or window. One less thing to configure, one less thing to inject, one less thing to get wrong.The trade-off? The PAN is implicit global state. Some people hate this. They've been trained that global state is the devil, that everything should be explicitly passed through constructors and function parameters. They're not wrong—for most code.
But UI components are special. They're already global-ish—they exist in a single shared page. Making them pretend to be isolated, pure functions is ceremony without benefit. The PAN embraces this reality.
Evolution and Growing Pains
LARC didn't emerge fully formed. Version 0.1 was... let's call it "enthusiastic." It had ideas. It had ambition. It also had bugs, missing features, and APIs that made sense at 2am but not at 2pm.
The Streaming Crisis
Early versions of LARC tried to stream everything, all the time. Every component was a stream. Every update was a new stream message. This was theoretically beautiful and practically unusable.
The problem: backpressure. If a component updated faster than the browser could render, messages would queue up, memory would bloat, and eventually your tab would crash. We learned this the hard way when someone used LARC to display real-time stock prices. Thousands of updates per second met an unmovable object: the browser's rendering pipeline.
The fix required a philosophical shift. Not everything needs to be a stream. Sometimes, you just want the latest value. This led to the distinction between signals (which buffer the latest value) and streams (which preserve the full sequence). Most components want signals. Streams are for the rare cases where order and completeness matter.
The TypeScript Years
In 2021, we had a reckoning with TypeScript. LARC was written in vanilla JavaScript. The docs showed vanilla JavaScript. The examples used vanilla JavaScript. But increasingly, users were asking: "Where are the types?"
We resisted at first. LARC was supposed to be simple, lightweight, no-build-required. TypeScript felt like adding weight. But we were wrong. TypeScript wasn't about compiler strictness—it was about developer experience. Autocomplete, inline documentation, catching bugs before runtime. These weren't nice-to-haves; they were essential for anything beyond toy examples.
So we rewrote LARC in TypeScript. Not because we loved types (though they grew on us), but because our users did. The library stayed tiny—types are free at runtime—but the DX improved dramatically.
The lesson: Listen to your users, even when they're asking you to compromise your aesthetic vision. Especially then.
The Build-Tool Wars
LARC's "no build step" philosophy hit reality hard when we tried to integrate with existing applications. Yes, you could write LARC in a tag. But most real projects use Webpack, Vite, Rollup, or whatever the JavaScript ecosystem has decreed is cool this month.
We couldn't fight the build tools—we had to join them. This meant:
- Writing plugins for every major bundler
- Supporting JSX (yes, really) as an alternative to tagged templates
- Providing pre-built bundles for CDNs
- Creating a CLI for scaffolding projects
Real-World Use Cases
LARC found its audience in unexpected places.
Live Data Dashboards
Financial firms discovered LARC early. When you're displaying real-time market data across hundreds of components, traditional frameworks struggle. Too much reconciliation overhead, too many re-renders, too much wasted work.
LARC's streaming model fit perfectly. Each widget was an independent component subscribed to its own data feed. Updates flowed through the PAN. No global state to synchronize, no render batching to tune, no performance cliffs to hit. It just worked.
One trading desk reported replacing 10,000 lines of React + Redux with 2,000 lines of LARC. The new version was faster, more maintainable, and actually understandable by the junior developers.
Progressive Enhancement Sites
Ironically, LARC also found success in the opposite domain: simple content sites that wanted a touch of interactivity. A blog with a newsletter signup form. A marketing site with a live demo. An e-commerce store with real-time inventory.
These sites didn't need massive client-side frameworks. They needed a sprinkle of JavaScript that enhanced the HTML without taking it over. LARC's async components could render on the server, stream to the client, and add interactivity only where needed.
The "no build step" feature stopped being a compromise and became a selling point. Designers could edit HTML files directly. Developers could add components without configuring Webpack. It was web development like it used to be—just with better asynchrony.
IoT Control Panels
The Internet of Things people found LARC too. When you're building a web interface for smart home devices, you're dealing with:
- Unreliable networks
- Devices that appear and disappear
- Real-time state updates
- Lots of independent components
One smart home startup built their entire control panel in LARC—500 devices, 2,000 data points, 60 FPS updates. It ran smoothly on a Raspberry Pi. Try that with a typical SPA framework.
Community and Ecosystem
LARC's community didn't grow explosively—it grew steadily. We never hit the front page of Hacker News for a week straight. We never became a meme on Twitter. But developers who tried LARC tended to stick around.
The Component Library
Early on, someone started a "LARC Components" repository. Basic stuff—buttons, forms, modals. It wasn't fancy, but it was practical. More people contributed. Soon there were data tables, charts, calendars, and all the widgets you'd expect.
The library followed LARC's philosophy: components were independent, async-first, and communicated via the PAN. A modal component didn't need a "modal manager"—it just listened for modal:open events. A notification system didn't need to be wired into every component—it subscribed to notify:* events.
The result felt different from other component libraries. Less configuration, more convention. Less wiring, more broadcasting. It wasn't for everyone, but for those who got it, it was liberating.
The Plugin Ecosystem
Developers started writing PAN plugins. Some were simple utilities:
pan-persist: Save PAN events to localStoragepan-time-travel: Replay event sequences for debuggingpan-analytics: Track user interactions automatically
pan-firebase: Bridge Firebase Realtime Database to PAN eventspan-graphql: Subscribe to GraphQL subscriptions via PANpan-webrtc: Coordinate WebRTC connections through PAN
The Documentation Journey
LARC's docs went through several iterations. The first version was technically accurate and completely opaque. We'd made the classic mistake: writing docs for people who already understood the system.
The rewrite focused on mental models. Instead of "here's how to use this API," we explained "here's how to think about async components." Instead of exhaustive API references, we provided guided examples that built intuition.
This book you're reading now is the culmination of that journey. Not just a reference manual, but a guide to thinking in LARC.
Lessons Learned
Building LARC taught us things that no amount of theorizing could:
1. Async-first is a different paradigm. You can't just add async to a sync model. You have to rebuild from the ground up. This is hard, but worth it. 2. Trade-offs are real. Every framework makes trade-offs. React trades simplicity for ecosystem. Svelte trades runtime flexibility for compile-time optimization. LARC trades magic for explicitness. Own your trade-offs. 3. Developer experience matters more than you think. The best API in the world is useless if developers hate using it. TypeScript support, good error messages, and clear documentation aren't optional. 4. Weird names stick. "PAN" was a joke that became a feature. Sometimes the silly idea is the right idea. 5. Community isn't about numbers. A small, engaged community beats a large, passive one every time. We'd rather have 1,000 developers who truly understand LARC than 100,000 who cargo-cult it.The Future
Where does LARC go from here? We have ideas:
- Better streaming server rendering, possibly with resumability
- First-class support for edge computing platforms
- Enhanced time-travel debugging and replay tools
- Integration with emerging web standards like View Transitions
- Maybe, just maybe, a visual component builder (but probably not)
LARC isn't trying to replace React. It isn't trying to kill Vue. It's offering a different perspective—a path for developers who looked at the existing options and thought, "there has to be another way."
Sometimes the best tool isn't the most popular one. It's the one that fits how you think, how you work, and how you want to build software.
If that sounds like LARC, welcome to the journey. If not, that's okay too. The web is big enough for many approaches.
Epilogue: The Name
We should probably explain what LARC actually stands for. You've seen "Live Asynchronous Reactive Components" throughout this chapter. That's the official expansion.
But here's a secret: the name came first. Someone said "LARC" and we all liked how it sounded—like "spark" but with an L. The acronym came later, reverse-engineered to fit.
Some of the rejected expansions:
- "Lightweight Async Rendering Components"
- "Lazy Async Reactive Components"
- "Live Applications with Reactive Components"
- "Larry's Awesome Reactive Components" (no one on the team was named Larry)
But really, LARC is just LARC. Sometimes a name is just a name.
And sometimes it's the start of something bigger.
Next: Chapter 4 - Core Concepts, where we dive deep into the building blocks that make LARC tick.