DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

ReScript in 2025 — Fast, Sound, and Ready for React (Hybrid Guide)

ReScript in 2025 — Fast, Sound, and Ready for React (Hybrid Guide)

ReScript in 2025 — Fast, Sound, and Ready for React (Hybrid Guide)

Highlights

  • Sound, nominal types → fearless refactors.
  • ADTs + pattern matching → simpler state & business rules.
  • Pipes + labeled args → readable data‑first code.
  • First‑class React → JSX, components, async/await support.
  • Native compiler → fast feedback at scale.
  • Interop → NPM bindings + human‑readable JS output.

Why it matters (in one minute)

TypeScript improved JS by adding types; ReScript improves types and then targets JS. That shift unlocks soundness, ADTs, and a simpler mental model for data flow — the things that tame large frontends.


Core Language Cheat‑Sheet

Option, Result, Variant

type option<'a> = None | Some('a)

type Result<'a, 'e> =
  | Ok('a)
  | Error('e)

type Role = Admin | User | Guest
Enter fullscreen mode Exit fullscreen mode

Pattern match

let greet = (r: Role) => switch r {
  | Admin => "Welcome, admin"
  | User => "Hi!"
  | Guest => "Please sign in"
}
Enter fullscreen mode Exit fullscreen mode

Records & Tuples (immutable)

type person = {name: string, age: int}
let p = {name: "Ada", age: 25}
let older = { ...p, age: p.age + 1 }

let point: (float, float) = (10.0, 20.0)
Enter fullscreen mode Exit fullscreen mode

Labeled args & pipes

let sub = (~first: int, ~second: int) => first - second
let x = sub(~second=2, ~first=5)

let total =
  [1,2,3]
  ->Belt.Array.map(x => x + 1)
  ->Belt.Array.reduce(0, (a, v) => a + v)
Enter fullscreen mode Exit fullscreen mode

React with @rescript/react

module Button = {
  @react.component
  let make = (~label: string, ~onClick: unit => unit) => {
    <button onClick> {React.string(label)} </button>
  }
}
Enter fullscreen mode Exit fullscreen mode

Interop with DOM & libs via externals:

@module("react-dom")
external render: (React.element, Dom.element) => unit = "render"

%raw(`import "styles.css";`)
Enter fullscreen mode Exit fullscreen mode

Async/await is supported since 10.1; Core’s Promise helpers keep chains tidy.


TS vs ReScript — What changes day‑to‑day?

Concern TypeScript ReScript
Type model Structural (can mask errors) Nominal + sound
Nullability undefined/null, strict modes option everywhere
Unions Discriminated unions (verbose) Variants + switch
DX at scale Good → can slow Consistently fast
Interop Native JS Via externals, still on NPM
Output Erases types Readable JS, optimized layout

Migration Checklist (pragmatic)

  • [ ] Pick a leaf feature or route for the pilot.
  • [ ] Add bindings only for functions you call.
  • [ ] Replace string enums with Variant types.
  • [ ] Convert nullable data to option and match.
  • [ ] Adopt pipes & labeled args for clarity.
  • [ ] Keep compiled JS in VCS during the pilot (easy rollback).

Deployment note: Leapcell (fits the vibe)

  • Free unlimited projects; pay for usage only.
  • Async tasks + Redis built in.
  • Nice DX: CI/CD + GitOps, logs/metrics.
  • Auto‑scale without ops — great for ReScript + React SSR/RSC.

Final word

If you’re happy in TS, you’ll still be happy tomorrow. If you want stronger guarantees with fewer footguns and faster feedback, ReScript is ready today — and it doesn’t ask you to leave the JS ecosystem to get there.

✍️ 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)