EngineeringApril 4, 2026 · 8 min read

How We Built 67 Speed in an Expo WebView

Why we run MediaPipe Pose inside a WebView instead of native bindings, and how that decision made 67 Speed work on both iOS and the open web.

When we started 67 Speed, we had a small decision tree: native iOS with CoreML, React Native with a native module, or a WebView running browser JavaScript. We picked the WebView. Here’s why, and what it cost us.

The decision

MediaPipe ships a first-class JavaScript build of its Pose model. It’s well-maintained, the API is stable, the wasm bundle is ~2MB, and it runs comfortably in real-time on any iPhone from the last four years. The native bindings for React Native are third-party, intermittently maintained, and bind to either the older MediaPipe graph runtime or a non-Pose model.

Shipping native bindings means shipping Xcode work, dealing with MediaPipe’s iOS build process, and re-implementing the same thing for Android later. Shipping the WebView version means writing an HTML file once and getting two platforms plus a free public web game out of the deal.

We chose the WebView.

What the WebView cost us

Three things, honestly:

  • A WebView tax.WebView frame pacing isn’t quite native. On older iPhones (11 and below), we occasionally drop frames under heavy motion. The scoring model tolerates this, but it’s noticeable.
  • Message-passing overhead. The game needs to communicate score updates, game start/end, and errors back to React Native. We use postMessage and a handler on the RN side. It adds a millisecond or two per event, which is fine.
  • Haptics bridging. Getting a haptic to fire on every scored point meant sending a message to RN on every score. Too chatty. We ended up buffering and firing haptics on milestones (every 5 points) instead of on every point.

What the WebView gave us

Everything else. The same HTML file that runs on the iPhone also runs unmodified on the web at /play. The scoring logic is identical. The camera pipeline is identical. When we tune a threshold, the web and iOS both get it in the same release. No divergence.

The web version also means creators can link to 67speed.app/play in a stream, an email, or a tweet and anyone — iPhone, Android, desktop Chrome, or a Linux machine with a webcam — can play it instantly.

The tuning we couldn’t avoid

The pose model’s defaults are tuned for fitness apps — smooth, stable, easy on the eyes. For a speed game, those defaults were actively in our way; we tuned the model for responsiveness instead, and the top of the score distribution opened up.

We also lowered the camera resolution from the example default. The number of pixels the pose model has to look at is the dominant inference cost, and the smaller crop bought us a meaningful latency win without measurably hurting tracking quality.

What we’d do the same

The WebView decision, again. It’s the reason we’re on iOS in month one instead of month six. It’s also the reason there’s a free web version of the game — which turns out to be the primary way people discover 67 Speed before downloading the app.

For anything involving a third-party ML model and a browser-first SDK, the WebView approach is underrated. You pay a small tax for a lot of reach.

Ready to play?

20 seconds. No download. No signup.