Turbo

The speed of a single-page application without writing custom JavaScript, by sending HTML over the wire.

Framework
npm
v8.0.23
7,387stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
82/100Excellent
Development Activity76
Maintenance80
Community72
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture85
Code Quality72
Innovation82
Learning Curve40

Turbo is the navigation and DOM-update layer of the Hotwire stack from 37signals (the makers of Basecamp and Ruby on Rails). It replaces full page reloads with intercepted link clicks and form submissions, scoped page regions that can be independently loaded or lazily rendered, and server-pushed streams that patch the DOM using a small set of CRUD-like actions delivered over WebSocket or plain HTTP responses.

Rather than shipping a client-side rendering framework or virtual DOM, Turbo leans entirely on returning HTML from the server: Turbo Drive accelerates whole-page navigation, Turbo Frames decompose a page into independently navigable and lazily-loadable regions, and Turbo Streams apply targeted append/replace/remove/update actions to specific elements. Turbo Native extends the same model to native iOS and Android shells, letting a server-rendered “majestic monolith” power native app screens with the same HTML responses. Where more client-side behavior is needed, Turbo is designed to pair with Stimulus, the other half of Hotwire.

What You Get

  • Turbo Drive, which intercepts link clicks and form submissions and replaces full page reloads with a fetch + DOM merge, preserving scroll position and JS state where possible
  • Turbo Frames (<turbo-frame>), a custom element that scopes navigation to an independent region of the page and supports lazy loading via a src attribute
  • Turbo Streams (<turbo-stream>), a custom element that applies append/prepend/replace/update/remove/before/after/refresh actions to the DOM from server-sent HTML, deliverable over WebSocket or as a form-submission response
  • A morphing renderer (built on idiomorph) that patches changed DOM nodes in place instead of tearing down and remounting the page or frame
  • Turbo Native bridging so a server-rendered web app can power native iOS/Android app screens through the same HTML-over-the-wire responses
  • Zero runtime dependencies — ships as a small vanilla-JS bundle (UMD and ES2017 ESM builds) with no framework lock-in

Common Use Cases

  • Adding SPA-like navigation speed to a server-rendered Rails, Laravel, Django, or similar backend app without introducing a client-side framework
  • Streaming live UI updates (chat messages, notifications, collaborative edits) to connected clients over WebSocket via Turbo Streams
  • Lazily loading independent widgets or panels on a page (e.g. a sidebar, a comment thread) as scoped Turbo Frames instead of full page loads
  • Wrapping a server-rendered web app inside a native iOS/Android shell via Turbo Native to reuse the same backend views across web and mobile
  • Progressively enhancing traditional multi-page apps with partial updates after form submissions, without hand-rolled AJAX/fetch plumbing

Under The Hood

Architecture Turbo is organized around a single Session object (src/core/session.js) that composes single-purpose collaborators — a Navigator, History, PageView, a Cache, and a set of DOM observers (LinkClickObserver, FormSubmitObserver, FormLinkClickObserver, LinkPrefetchObserver, ScrollObserver, PageObserver, StreamObserver, CacheObserver) plus a FrameRedirector and Preloader. Observers capture link/form/scroll DOM events and hand them to the Navigator, which drives a visit through http/fetch.js and one of several renderer strategies (PageRenderer, MorphingPageRenderer, FrameRenderer, MorphingFrameRenderer) exported from src/core/index.js. FrameElement and StreamElement (in src/elements/) are custom elements that hook into this same session to scope navigation to a subtree or apply a server-sent DOM patch, respectively. This is a modular, observer-mediated design where Session acts as the central coordinator; replacing the Navigator or renderer abstraction would ripple through the entire visit lifecycle, but the clean separation between observers, navigation, rendering, and caching keeps each concern independently testable.

Tech Stack Turbo has zero runtime dependencies — it is plain JavaScript built on native browser APIs (Custom Elements, fetch, the History API, MutationObserver) and ships as a UMD bundle (dist/turbo.es2017-umd.js) and an ES2017 ESM bundle, built via Rollup 2.x (rollup.config.js) with Yarn as the package manager. idiomorph is bundled as a build-time dependency to power the morphing renderers’ in-place DOM patching. The dev/test toolchain uses @web/test-runner with @web/test-runner-playwright for headless unit tests and @playwright/test directly for full functional/integration browser tests, run in CI (.github/workflows/ci.yml) across Chrome and Firefox on GitHub Actions with Node 22.

Code Quality The project lints with ESLint (.eslintrc.js) and enforces it in CI alongside two distinct test suites: a small unit-test suite (5 files under src/tests/unit/) covering adapter and edge-case behavior, and a much larger functional/integration suite (26 files under src/tests/functional/ and src/tests/integration/) that drives real page navigation, frame loading, and stream rendering through Playwright across two browser engines — an appropriate tradeoff for a library whose correctness is fundamentally about DOM and navigation behavior rather than isolated units. There is no TypeScript and thus no static type checking; correctness instead leans on the breadth of the browser-driven functional suite and consistent naming/file-per-class conventions throughout src/.

What Makes It Unique Turbo’s differentiating bet is treating server-returned HTML — not client state or a virtual DOM — as the unit of UI update, and doing so uniformly across whole-page navigation (Drive), scoped page regions (Frames), targeted live patches (Streams), and native app shells (Native) within one small dependency-free library. The morphing renderers apply idiomorph-based in-place patching rather than full re-render/remount, and the Frame/Stream primitives are exposed as standard Custom Elements so any backend that can render HTML fragments can drive rich, dynamic UI — an architectural alternative to virtual-DOM SPA frameworks that has since anchored the broader Hotwire ecosystem (paired with Stimulus) and native-app bridging patterns.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search