µJS vs Turbo: same idea, different philosophy

Published: (March 11, 2026 at 07:39 AM EDT)
5 min read
Source: Dev.to

Source: Dev.to

Cover image for µJS vs Turbo: same idea, different philosophy

Turbo (part of Hotwire) and µJS solve the same problem: make server‑rendered websites feel faster without rewriting the frontend in JavaScript. Both intercept link clicks and form submissions, fetch pages via AJAX, and inject content into the DOM.

The differences lie in scope, weight, and server requirements.

Size

LibrarySize (min + gzip)
µJS~5 KB
Turbo~25 KB

Turbo is 5 × heavier. For a library whose primary job is “fetch a page and swap some HTML”, that’s a significant gap.

Build step

Turbo requires a build step – it’s distributed as an npm package meant to be bundled.

µJS does not:

mu.init();

This matters for projects that deliberately avoid a JavaScript build pipeline (static sites, PHP/Python/Ruby apps, or any project where adding npm + a bundler is a step backwards).

Server‑side requirements

µJS requires nothing from your server. It sends a standard HTTP request and expects standard HTML in return. Your existing pages work as‑is.

Turbo has conventions:

  • Turbo Drive (basic navigation) works like µJS.
  • Turbo Frames require your server to return specific <turbo-frame> elements.
  • Turbo Streams (the equivalent of µJS’s patch mode) require your server to return <turbo-stream> custom elements wrapping content in <template> tags.

Adopting Turbo Streams changes your server‑side HTML output. With Rails and the Hotwire ecosystem helpers handle this for you; with other back‑ends you’re on your own.

Multi‑fragment updates: patch mode vs Turbo Streams

Both libraries can update multiple parts of the page in a single response. The syntax tells the story.

µJS – patch mode

The server returns plain HTML. Each fragment carries mu-patch-target and mu-patch-mode attributes:

<div mu-patch-target="article" mu-patch-mode="replace">
  <h2>Great article!</h2>
</div>

<div mu-patch-target="comments">
  <p>14 comments</p>
</div>

<form mu-patch-target="comment-form">
  <button type="submit">Submit</button>
</form>

Turbo Streams

Each fragment must be wrapped in <turbo-stream> / <template>:

<turbo-stream action="replace" target="article">
  <template>
    <h2>Great article!</h2>
  </template>
</turbo-stream>

<turbo-stream action="append" target="comments">
  <template>
    <p>14 comments</p>
  </template>
</turbo-stream>

<turbo-stream action="replace" target="comment-form">
  <template>
    <form>
      <button type="submit">Submit</button>
    </form>
  </template>
</turbo-stream>

With µJS, the fragment is the content. With Turbo, each fragment requires a wrapper structure. µJS’s approach has less boilerplate, and the HTML is directly readable as‑is.

Another practical advantage: µJS’s mu-patch-target attributes are ignored on the initial page load, so you can use the exact same HTML fragment in your normal page template and in your patch response.

HTTP methods

FeatureTurboµJS
Supported methodsGET, POSTGET, POST, PUT, PATCH, DELETE
How to use extra verbsHidden _method field or server‑side conventionsmu-method attribute on links, buttons, and forms
<a href="/posts/5" mu-method="delete">Delete</a>

<form action="/api/publish/5" mu-method="patch">
  <button type="submit">Publish</button>
</form>

Turbo’s lack of native support for PUT/PATCH/DELETE forces work‑arounds, whereas µJS handles them out of the box.

Triggers and polling

Turbo only handles links and forms.

µJS adds mu-trigger, letting any element initiate a fetch on any event:

<button mu-trigger="click" mu-url="/refresh" mu-poll="5000">
  Refresh every 5 seconds
</button>

Real‑time: Server‑Sent Events (SSE)

Both libraries support SSE.

  • µJS has it built‑in and reuses the same patch syntax:
<div mu-patch-target="notifications" mu-patch-mode="append">
  <!-- Server will push <div mu-patch-target="notifications">…</div> fragments -->
</div>

The server pushes standard HTML fragments with mu-patch-target attributes—the same format as a regular patch response. Nothing new to learn.

  • Turbo Streams can also be delivered over SSE, but you still work with the <turbo-stream> / <template> format, and the server must produce that structure.

TL;DR

AspectµJSTurbo (Hotwire)
Size~5 KB~25 KB
Build stepNo bundler neededRequires npm + bundler
Server requirementsNone (plain HTML works)Needs <turbo-frame> / <turbo-stream> for advanced features
Patch syntaxPlain HTML with mu-patch-* attributesWrapped <turbo-stream> elements
HTTP verbsGET, POST, PUT, PATCH, DELETEGET, POST (others need work‑arounds)
Triggersmu-trigger on any event, polling, debounceOnly links & forms
SSE supportBuilt‑in, same patch formatSupported but uses Turbo‑specific markup

If you want a tiny, zero‑config drop‑in that works with any backend, µJS is the clear winner. If you’re already deep in the Rails/Hotwire ecosystem and need the richer feature set it provides, Turbo may be the better fit.

When Turbo makes more sense

Turbo is the right choice if:

  • You’re in the Rails / Hotwire ecosystem — the integration is deep, the helpers are mature, and the community is large.
  • You need Turbo Native for iOS/Android apps.
  • Your team is already familiar with Turbo conventions.

Outside the Rails ecosystem, Turbo’s conventions become overhead without the ecosystem benefits.

Summary

FeatureµJSTurbo
Size~5 KB~25 KB
Build stepNoneRequired
Server changes neededNoFor Frames and Streams
Multi‑fragment updatesPatch mode (plain HTML)Turbo Streams (<turbo-stream>)
HTTP methodsGET/POST/PUT/PATCH/DELETEGET/POST
Triggers on any eventYesNo
Debounce / PollingBuilt‑inNo
SSEBuilt‑inBuilt‑in
Rails ecosystemNoYes

µJS is a focused library: drop it in, call mu.init(), and your site gains AJAX navigation with no server changes. If you need more, the attributes are there. If you don’t, you don’t pay for them.

npm install @digicreon/mujs
0 views
Back to Blog

Related posts

Read more »

Intro About Java Script

Introduction In today’s class I learned a short introduction to JavaScript, so I’ll share some facts about JavaScript in this blog. What Is JavaScript? JavaScr...