DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Tired of TypeScript? Why ReScript Finally Clicked for Me (Narrative)

Tired of TypeScript? Why ReScript Finally Clicked for Me

Tired of TypeScript? Why ReScript Finally Clicked for Me

I like TypeScript. I also like not shipping bugs at 2 a.m. ReScript gave me something I didn’t expect: calm. The “if it compiles, it runs” kind of calm you feel in Rust or Haskell — but inside the JavaScript ecosystem.

Here’s the story of what convinced me, minus the hype.


The Moment

A teammate refactored a model, CI was green, and production quietly exploded — a structural typing footgun. Two objects “looked” compatible, the compiler agreed, runtime didn’t. That day I learned what soundness means in practice.

In ReScript’s nominal world, that assignment never compiles. The class of “seems ok, blows up later” bugs drops to near zero. That was the first click.


The Second Click: ADTs + Pattern Matching

I replaced a web of booleans and strings with a single Variant:

type Auth =
  | LoggedOut
  | Pending2FA({ phone: string })
  | LoggedIn({ userId: string })
Enter fullscreen mode Exit fullscreen mode

Then matched exhaustively:

let view = (state: Auth) => switch state {
  | LoggedOut => <Login />
  | Pending2FA({phone}) => <CodePrompt phone />
  | LoggedIn({userId}) => <Dashboard userId />
}
Enter fullscreen mode Exit fullscreen mode

No forgotten branches. No “what does 'ok' mean again?” comments. It felt like cheating.


The Third Click: Speed

My TS monorepo needed coffee breaks for typechecking. ReScript’s native compiler just… doesn’t. Edits -> feedback in hundreds of ms, even huge codebases. You feel it in your shoulders after a week.


The Fourth Click: Simplicity That Scales

  • option over null — ergonomics like Rust’s Option.
  • Named args remove mystery positional params.
  • Pipes remove nesting and “callback pyramids”.
let total =
  orders
  ->Belt.Array.map(o => o.price)
  ->Belt.Array.reduce(0.0, (acc, v) => acc +. v)
Enter fullscreen mode Exit fullscreen mode

“But Interop?” (The Fear)

Bindings looked scary until I wrote two. I only bound what I used:

@module("path-to-regexp")
external match: string => (string => option<{.}>) = "match"
Enter fullscreen mode Exit fullscreen mode

Five minutes later I had a typed router helper. The compiled JS was readable enough that I’d be comfortable committing it if we ever had to eject.


React Feels Native

@rescript/react gives you JSX, components, and great type inference. Event handlers are safe, props are labeled, and async/await has been in for a while. It’s the familiar React mental model — just sturdier.

module Friend = {
  @react.component
  let make = (~name: string) => <div> {React.string(name)} </div>
}
Enter fullscreen mode Exit fullscreen mode

When I’d Reach for ReScript

  • Complex state machines (auth, payments, workflows).
  • APIs with tricky error handling — model results, match them.
  • Apps with many hands in the codebase — soundness fights entropy.

When I want untyped flexibility or quick scripts, JS/TS is still great. This isn’t religion — it’s a tool choice.


A Practical On‑Ramp (No Big Bang)

  1. Drop a new feature in ReScript.
  2. Bind just the libs you touch.
  3. Replace string enums with Variants.
  4. Convert nullable fields to option.
  5. Add pipes and labeled args as you go.

You’ll feel the DX change in a day; your bug chart in a week.


Bonus: Where to Ship It

I’ve been deploying small services on Leapcell — serverless, usage‑based pricing, Redis and async tasks built in. Fits that “move fast, pay little” vibe for side projects and PoCs.


Final Thought

ReScript didn’t replace TypeScript in my head. It replaced the anxiety around “did the compiler miss something?” If you’re feeling that, try ReScript for one feature. You might not go back.

✍️ Written by Cristian Sifuentes — Full‑stack developer & AI/JS enthusiast focused on resilient frontends and scalable architectures.

Tags: #react #frontend #performance #ssr #architecture

Top comments (0)