µJS vs Turbo: same idea, different philosophy
Source: Dev.to

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
| Library | Size (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
| Feature | Turbo | µJS |
|---|---|---|
| Supported methods | GET, POST | GET, POST, PUT, PATCH, DELETE |
| How to use extra verbs | Hidden _method field or server‑side conventions | mu-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 | µJS | Turbo (Hotwire) |
|---|---|---|
| Size | ~5 KB | ~25 KB |
| Build step | No bundler needed | Requires npm + bundler |
| Server requirements | None (plain HTML works) | Needs <turbo-frame> / <turbo-stream> for advanced features |
| Patch syntax | Plain HTML with mu-patch-* attributes | Wrapped <turbo-stream> elements |
| HTTP verbs | GET, POST, PUT, PATCH, DELETE | GET, POST (others need work‑arounds) |
| Triggers | mu-trigger on any event, polling, debounce | Only links & forms |
| SSE support | Built‑in, same patch format | Supported 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 | µJS | Turbo |
|---|---|---|
| Size | ~5 KB | ~25 KB |
| Build step | None | Required |
| Server changes needed | No | For Frames and Streams |
| Multi‑fragment updates | Patch mode (plain HTML) | Turbo Streams (<turbo-stream>) |
| HTTP methods | GET/POST/PUT/PATCH/DELETE | GET/POST |
| Triggers on any event | Yes | No |
| Debounce / Polling | Built‑in | No |
| SSE | Built‑in | Built‑in |
| Rails ecosystem | No | Yes |
µ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.
- Live playground — test each feature interactively
- Full comparison: µJS vs Turbo vs htmx
- GitHub
npm install @digicreon/mujs