Northvane Logistics

Northvane's dispatch console couldn't scale past 200 fleets. We rebuilt it as a real-time React app on edge, cut onboarding from 9 days to 11 hours, and gave their ops team a console operators actually liked using.

// sector
Supply chain · SaaS
// year
2025
// duration
14 weeks · then retainer
// role
Lead engineering · design system · platform
Northvane dispatch console — live load board with real-time event feed

// dispatch console · production · q3 2025

// 01 — brief

Northvane runs the dispatch backbone for mid-size logistics carriers across North America. Their original portal was a Rails app from 2016 with a jQuery UI layer bolted on. It worked, until it didn’t — somewhere around 200 fleets the latency between a driver event firing and the dispatcher seeing it crossed five seconds, and onboarding a new operator took the better part of two weeks.

They came to us with a clear ask and an unclear path: the console had to feel instant, the team had to keep shipping during the rebuild, and the existing Rails API had to stay in place. No greenfield rewrite. No down-week.

// 02 — constraints

The real ones, not the polite ones.

  • 01Existing Rails API stays — no backend rewrite, no schema changes in flight.
  • 02Zero downtime cutover. Operators dispatch live freight; a 30-minute outage cancels routes.
  • 03Two senior engineers from Northvane, one PM, no dedicated designer.
  • 04SOC2 Type II in flight — every dependency had to clear procurement.
  • 05Mobile-first for field ops, but desktop-dense for the dispatch room.

// 03 — outcome

+312%

operator throughput · 6 months post-launch

Measured as dispatched loads per operator-hour. Onboarding time fell from 9 days to 11 hours. P95 event-to-render latency dropped from 5.4s to 184ms.

11h

onboarding (was 9d)

184ms

p95 event→render

0

P1 incidents post-cutover

// 04 — approach

Strangler fig, not big bang.

We didn’t replace the Rails app. We put a new React surface in front of it and migrated screens one by one, behind a feature flag scoped per fleet. Each migrated screen used the existing REST endpoints, plus a thin WebSocket gateway we wrote to push the events the legacy backend was already emitting to its internal queue.

The win wasn’t the framework swap. It was the data model: we modelled the dispatch board as a single source of truth on the client (a normalized store hydrated on connect, then patched by events) so the UI never had to re-fetch to feel current. Everything operators interacted with became optimistic by default, with reconciliation against the server within one tick.

The first time we ran a peak shift on the new console, two dispatchers asked if something was broken because the screen wasn’t lagging anymore.

Maya R., VP Operations, Northvane

// 05 — architecture

// edge & runtime

  • React 19
  • TanStack Start
  • Cloudflare Workers
  • Durable Objects

// realtime

  • WebSocket gateway (Hono)
  • Redis Streams
  • Server-Sent Events fallback

// data

  • Existing Rails REST API
  • Postgres (read replicas)
  • Normalized client store

// observability

  • OpenTelemetry
  • Grafana Cloud
  • Sentry
  • PagerDuty
dispatch.events.ts — the seam between legacy and new
// One Durable Object per fleet keeps the connected
// dispatchers in sync without a shared broker hop.
export class FleetRoom {
  sockets = new Set<WebSocket>();

  async fetch(req: Request) {
    const pair = new WebSocketPair();
    this.accept(pair[1]);
    return new Response(null, { status: 101, webSocket: pair[0] });
  }

  broadcast(evt: DispatchEvent) {
    for (const ws of this.sockets) ws.send(JSON.stringify(evt));
  }
}
Real-time event feed showing timestamped dispatch events with sync status dots
// event feed · what dispatchers actually watch all day

// 06 — design system

24 components, dense by default.

Built in Tailwind + Storybook, optimised for keyboard-first operators who need to dispatch a load in three keystrokes — not for marketing screenshots. Every component ships with a default and a hover state, and most have a dense and comfortable variant for different parts of the console.

Storybook overview of the Northvane component library
// component library — extracted from real screens, not designed in isolation

// 07 — timeline

14 weeks, in commits.

  1. w01discovery & instrumentation

    Shadowed two dispatchers for a full shift. Wired OpenTelemetry into the legacy Rails app to get a real latency baseline before we touched anything.

  2. w03design system v0

    Built a 24-component dense-data system in Tailwind + Storybook. Optimised for keyboard-first operators, not marketing screenshots.

  3. w05websocket gateway live

    Hono service on Cloudflare Workers, fan-out via Durable Objects. Rails publishes to Redis; the gateway subscribes and pushes to connected sockets.

  4. w08first migrated screen

    Live load board behind a per-fleet flag. Two pilot fleets ran it for a week. We held the rollback line for 6 days before opening it wider.

  5. w12full cutover

    Last legacy screen retired. Old portal kept as read-only history for 90 days. SOC2 evidence captured throughout.

  6. w14handover + retainer start

    Internal team owns the codebase. We stay on as platform retainer — one senior engineer, two days a week.

// 08 — what broke first

The first thing to break wasn’t the framework or the gateway — it was assumptions about clock drift. Driver tablets in the field reported event timestamps that disagreed with server time by up to 11 seconds, and our ordering logic trusted the client clock. Two pilot fleets saw events render out of order on day three.

Fix was small (server-stamp on receive, surface a “synced” badge in the UI), but the lesson was bigger: when you move logic to the edge, every assumption about wall time becomes a thing you have to test for.

// 09 — reflection

What we'd do differently.

We over-invested in the design system in week 3. With one designer-shaped engineer on our side and two from Northvane, we should have shipped the load board first and let the system grow out of real screens. We’d have hit the cutover two weeks earlier.

We’d also keep the legacy portal in read-only mode longer than 90 days. Three different auditors asked for historical screenshots in month four, and we’d already turned the lights off.

// start a project

Want a build log like this for your team?

Send a brief — we read everything that comes in and reply within two business days, usually with questions before a proposal.

start a project →

// next case

Packwell

Headless Hydrogen storefront with edge-rendered PDPs. Mobile LCP cut by 1.4s, RPS up 22% in a quarter.

read build →