Get AI summaries of any video or article — Sign up free
I built the same app 10 times // Which JS Framework is best? thumbnail

I built the same app 10 times // Which JS Framework is best?

Fireship·
6 min read

Based on Fireship's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

A framework choice is primarily about how state connects to UI and how lifecycle/events are handled, not about popularity alone.

Briefing

A single “best” JavaScript framework doesn’t exist—choosing one comes down to how each framework handles the same core job: keeping UI and application state in sync. To make that trade-off concrete, the walkthrough rebuilds the same to-do app ten times, starting with vanilla JavaScript and then repeating the task in Angular, React, Vue, Svelte, Lit, Alpine, Solid, Stencil, Mithril, and finally plain HTML/JS again. The exercise matters because the framework decision shapes everything that follows: how events are wired, how data flows, how lifecycle is managed, and how much boilerplate ends up in real projects.

The baseline vanilla implementation shows why frameworks gained traction. The to-do app stores items in localStorage, but vanilla requires imperative DOM work: querySelector to grab elements, createElement to build list items, innerHTML updates to reflect state changes, and event listeners to respond to form submissions. That approach leaves state and UI loosely coupled, making it easy for the interface to drift out of sync as features grow. Even basic maintainability suffers—markup doesn’t reveal which events are attached, so developers must hunt through JavaScript to understand behavior. Lifecycle adds more complexity: on initialization, the app must read localStorage and manually render existing items.

React is presented as a declarative UI “gold standard” built around components and reactive state. A component function returns JSX, state is managed with useState, and the UI re-renders automatically when state changes. Form submission is bound directly in JSX via onSubmit, and input values are read with hooks like useRef. Lifecycle work—loading saved to-dos—moves into useEffect with an initialization dependency array. The trade-off is that React stays minimal and leaves many architectural decisions (routing, animation, state management) to the ecosystem.

Angular flips the philosophy by being highly opinionated and structured. It uses TypeScript, enforces conventions through its project layout, and provides built-in support for routing, animation, and server-side rendering. Templates use Angular’s templating language (including ngFor for loops), and forms rely on directives like ngModel for two-way binding—at the cost of a steeper learning curve and more framework-specific concepts.

Vue is framed as approachable and similar in spirit to Angular, with official packages for routing and state management plus a large third-party ecosystem. Svelte and Solid push performance and simplicity further by compiling away runtime overhead: Svelte turns components into efficient JavaScript, while Solid compiles to native DOM nodes and avoids a virtual DOM. Both emphasize readable, minimal code patterns (Svelte’s bind and lifecycle hooks; Solid’s signals and onMount).

For web components, Lit and Stencil compile to standard custom elements, aiming for cross-framework compatibility. Lit leans on template literal syntax and web-component APIs like connectedCallback, while Stencil offers an Angular-like component model with JSX-like templating. Alpine takes the opposite direction: tiny, HTML-first reactivity for sprinkling interactivity into existing pages, using directives like x-data, x-for, and x-on plus an Alpine store for shared state. Mithril rounds out the set as a lightweight, JavaScript-first approach with a virtual DOM and a UI defined through pure JS functions.

Across all ten builds, the consistent message is pragmatic: frameworks can all implement the same to-do app, but they differ sharply in how they connect state to UI, how much structure they impose, and how they scale developer experience for teams and long-term maintenance.

Cornell Notes

The walkthrough rebuilds the same to-do app ten times to show that “best” JavaScript framework depends on trade-offs, not downloads or popularity alone. Vanilla JavaScript makes state/UI synchronization manual and error-prone, requiring imperative DOM updates, event wiring, and explicit lifecycle handling. React improves maintainability with declarative components, reactive state (useState), and lifecycle (useEffect), while Angular adds strong conventions and built-in tooling using TypeScript and template directives like ngFor and ngModel. Svelte and Solid emphasize compilation and performance by reducing runtime overhead, while Lit and Stencil target web components for cross-framework reuse. Alpine and Mithril demonstrate two different philosophies: HTML-first interactivity and JavaScript-first UI definitions, respectively.

Why does vanilla JavaScript feel harder to scale for a non-trivial app?

Vanilla requires imperative DOM work to keep UI synced with data: querySelector to grab elements, createElement/innerHTML to update list items, and manual event listeners for form submission. State and UI become decoupled, so it’s easy for the interface to drift as features grow. Even understanding behavior is harder because the HTML doesn’t clearly show which events are attached—developers must search through JavaScript. Lifecycle also becomes manual: on initialization, the app must read localStorage and render existing items.

What makes React’s to-do implementation more declarative than vanilla?

React binds UI output directly to state. useState holds the to-do list; when that state changes, React re-renders the JSX. Event handling is attached in JSX (onSubmit), and input access can use useRef. Lifecycle work like loading saved items from localStorage moves into useEffect, using a dependency array to run only on first initialization. The result is clearer data-to-UI mapping and less manual DOM manipulation.

How does Angular’s approach differ from React’s in structure and data binding?

Angular is opinionated about project structure and uses TypeScript. Templates use Angular directives such as ngFor for looping and ngModel for two-way data binding between the form input and a component property. Lifecycle is handled via class methods like ngOnInit. Because ngModel requires importing the Angular forms module, Angular tends to involve more framework-specific setup and concepts, which raises the learning curve.

What’s the key distinction between Svelte/Solid and frameworks that rely on a runtime like React?

Svelte and Solid compile components into efficient JavaScript/native DOM operations rather than relying on a virtual DOM runtime. Svelte compiles away runtime overhead and supports clean syntax like bind for two-way binding and onMount for lifecycle. Solid compiles to native DOM nodes and uses signals for reactivity plus onMount for initialization. The trade-off highlighted is that these frameworks may have smaller communities than React, affecting ecosystem and hiring.

What’s the purpose of Lit and Stencil in this comparison?

Lit and Stencil focus on building standard web components (custom elements) so they can work across frameworks. Lit defines components as classes extending lit element, uses decorators like property for reactive data, and relies on web-component lifecycle such as connectedCallback. Templates use JavaScript template literals with directives for events and bindings; two-way binding isn’t supported the same way, so extra event listeners may be needed. Stencil similarly compiles to custom elements and uses decorators like state and component, with JSX-like templating and lifecycle methods like componentWillLoad.

When would Alpine or Mithril be a better fit than a full SPA framework?

Alpine is designed for small interactivity on top of existing HTML—tiny footprint, HTML-first directives (x-data, x-for, x-on), and an Alpine store for shared state like loading from localStorage. It’s less suited for replacing large SPA frameworks. Mithril is lightweight and can perform well, using a virtual DOM like React, but its developer experience differs: UI is defined in pure JavaScript via m() calls and component objects, which some developers find awkward despite its flexibility.

Review Questions

  1. In vanilla JavaScript, what specific mechanisms must be implemented to keep state and UI synchronized, and why does that become brittle?
  2. Compare how React and Angular each handle lifecycle and form input binding in the to-do app.
  3. Which frameworks in the set compile to standard web components, and what problem does that compilation target?

Key Points

  1. 1

    A framework choice is primarily about how state connects to UI and how lifecycle/events are handled, not about popularity alone.

  2. 2

    Vanilla JavaScript requires imperative DOM updates and manual lifecycle logic, which quickly becomes difficult to maintain as features expand.

  3. 3

    React’s declarative component model ties UI rendering to reactive state (useState) and lifecycle (useEffect), reducing manual synchronization work.

  4. 4

    Angular’s conventions and TypeScript-first structure (ngFor, ngModel, ngOnInit) can improve consistency for large teams but increases the learning curve.

  5. 5

    Svelte and Solid reduce runtime overhead via compilation, aiming for simpler code and strong performance, with ecosystem size as a practical trade-off.

  6. 6

    Lit and Stencil target cross-framework reuse by compiling to standard custom elements (web components).

  7. 7

    Alpine and Mithril represent different philosophies: HTML-first sprinkling of interactivity versus JavaScript-first UI definition.

Highlights

The vanilla version makes state/UI synchronization manual—querySelector, createElement, innerHTML updates, and explicit initialization from localStorage.
React’s JSX plus useState/useEffect turns the to-do list into a direct function of data, with onSubmit binding living alongside the markup.
Angular’s ngModel enables two-way binding but requires framework-specific setup (forms module) and a more structured project model.
Svelte and Solid compile away runtime overhead, changing the performance and developer-experience trade-offs compared with virtual-DOM approaches.
Lit and Stencil compile to standard web components, aiming for interoperability across different front-end stacks.

Topics

  • Framework Comparison
  • Declarative UI
  • State Management
  • Web Components
  • Compilation vs Runtime
  • To-Do App

Mentioned

  • npm