HNHacker News
TopNewBestAskShowJobs

amaury_bouchard

69 karma · joined March 6, 2026

Born in Montreal and living in Paris, I’m both French and Canadian. I have an M.S. in System & Network from Epita (one of the best French schools in computer science) and +20 years of experience mostly in back-end web development and management. I also did an Executive MBA in Management & Entrepreneurship from Epitech and a certification in Software Product Management from the University of Alberta.

My personal work spans software, games, and tools (all published under open source licenses): - Temma (https://www.temma.net): PHP MVC framework, easier than the big frameworks, more useful than the micro-frameworks - µJS (https://mujs.org): Lightweight AJAX navigation library, 5KB alternative to Turbo and htmx - µCSS (https://mucus.org): Full-featured CSS framework, based on PicoCSS - Arkiv (https://github.com/Digicreon/Arkiv): Easy-to-use backup and archive tool - Dispak (https://github.com/Digicreon/Dispak): Extensible tool for managing Git repositories and project deployment - Rolis (https://www.rolis.net): A list of open-sourced tabletop role playing games, and multi-engine open-source scenarios - Pandocreon Menhir (http://www.pandocreon.com/jeux/menhir/): Award-winning boardgame

submissionscomments
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Both points are now addressed: relative paths are handled, and the documentation includes the integrity hash. Thanks for taking the time to suggest these improvements!
amaury_bouchard··on Show HN: µCSS, a CSS framework with 17 components, 20 themes, no build required
Thanks!
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks for the report! This is now fixed in v1.4.4.

µJS now resolves URLs using the native URL constructor and compares origin against window.location.origin. This means all same-origin URLs are correctly recognized as internal: - Absolute URLs: https://domain:port/page - Relative URLs: ../page.html, page.html - Local paths: /page (already worked)

Hash-only links (#section) are intentionally left to the browser for native anchor scrolling.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Good news, I've just added the server-side source as a tab in the Playground. Thanks again for the suggestion!
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks for the report! Using fully absolute URLs for internal links is not a very common pattern, and this hadn't been raised before. That said, it's a valid and interesting use case, I'll add it to the roadmap. In the meantime, using root-relative URLs (starting with /) is the recommended approach.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Yes, µJS manages browser history out of the box.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Fair enough, it's clearly not the right tool for everyone. HTML-over-the-wire works well for server-rendered apps where the primary interaction is fetching and displaying content. For highly interactive UIs with complex client-side state, a proper SPA framework is the better choice. Different problems, different tools.

That said, the vast majority of websites are purely transactional: click a link, load a page; submit a form, load a page. For those, there's little reason to add a full frontend framework. HTML-over-the-wire can improve responsiveness without adding complexity.

amaury_bouchard··on Show HN: µCSS, a CSS framework with 17 components, 20 themes, no build required
Thank you for the information. There was a DNS misconfiguration, but it should be fixed now (allow some time for DNS propagation).
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
That's a valid use case. For opt-in per link, you can disable global link interception with `mu.init({ processLinks: false })` and then add `mu-url` explicitly to the links you want µJS to handle. For the variable base path scenario, the `urlPrefix` option in `mu.init()` might also help. It prepends a prefix to all fetched URLs.

That said, proper support for relative paths is on the roadmap.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Glad it clicks! On the progressive enhancement point, µJS is designed exactly with that philosophy in mind: standard links and forms work without JS, µJS just enhances them.

Your concern about `mu-method="delete"` is valid. Without JS, the browser will fall back to the form's native method (usually GET or POST). The classic solution is the `_method` hidden field pattern: the form submits POST with a `_method=DELETE` field, and the server reads that field to route the request correctly. µJS doesn't need to implement anything special for this, it's a server-side convention

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Good points, thanks!

On relative paths: the current behavior is intentional for simplicity. µJS checks whether the URL starts with a slash (but not a double slash) to identify internal links. No one has reported this as an issue so far, but it's a valid feature request and I'll keep it in mind for a future version.

On the integrity attribute: the reason it was missing is that the library was evolving quickly and the hash would have changed with every release. Now that it's stable, I'll add it.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks! The short answer: µJS doesn't try to be a DSL in HTML attributes.

Here's what I deliberately left out compared to Turbo (~25KB gzip) and htmx (~16KB gzip):

- No client-side template/extension system. htmx has a full extension API, header-based control flow (HX-Trigger, HX-Redirect, HX-Reswap...), and dozens of response headers the server can use to command the client. µJS has 5 custom headers total; the server returns HTML, the client renders it.

- No CSS selector-based targeting in attributes. htmx lets you write hx-target="closest tr", hx-target="find .result", hx-target="next div" with a mini query language. µJS uses plain CSS selectors only (mu-target="#id"), no traversal keywords.

- No built-in transition/animation classes. htmx adds/removes CSS classes during swaps (htmx-settling, htmx-swapping, htmx-added) with configurable timing. Turbo has turbo-preview, transition classes, etc. µJS defers to the native View Transitions API: zero animation code in the library, and it falls back silently.

- No client-side cache or snapshots. Turbo keeps a page cache for "instant" back-button rendering and manages preview snapshots. µJS stores only scroll position in history.state and lets the browser's own cache + fetch() handle the rest.

- Patch mode simpler than Turbo Frames. Turbo has <turbo-frame>, a custom element with lazy-loading, src rewriting, and frame-scoped navigation. µJS handles multi-fragment updates via patch mode (mu-mode="patch") with regular HTML elements. No custom elements, no frame concept. It's simpler but it works perfectly.

- No request queuing or throttling. htmx has hx-sync with queuing strategies (queue, drop, abort, replace). µJS simply aborts the previous in-flight request. One request at a time per navigation.

- No form serialization formats. htmx supports JSON encoding (hx-encoding), nested object serialization, etc. µJS uses the browser's native FormData for POST and URL params for GET. That's it.

- No JavaScript API surface to speak of. htmx exposes htmx.ajax(), htmx.process(), htmx.trigger(), event details, etc. µJS exposes mu.init(), mu.fetch(), mu.render(), and a few setters and a few events. The goal is that you shouldn't need the JS API — the HTML attributes should be enough.

What I kept: the features that cover 90% of real-world use cases. AJAX navigation, 8 injection modes, multi-fragment patch, morphing (via Idiomorph), SSE, prefetch, forms with all HTTP verbs, triggers with debounce/polling, progress bar, history/scroll management. Just without the layers of abstraction around them.

The philosophy: if the browser already does it (View Transitions, FormData, fetch, AbortController, history API), don't reimplement it.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Fair point on shadow DOM. That said, µJS targets server-rendered HTML workflows, where pages are plain HTML generated by a backend framework or template engine. In that context, shadow DOM is rarely used, so this limitation doesn't affect the typical µJS user. If you're building heavily component-based UIs with Web Components, µJS is probably not the right tool anyway. A proper JS framework would serve you better.

On the native APIs: yes, the Navigation API and Speculation Rules are exciting, but browser support is still uneven. µJS works today across all modern browsers without any configuration. That said, I agree the gap will narrow over time.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
No worries! You can prevent the page from scrolling to the top by adding `mu-scroll="false"` to the link or form triggering the request. You can also disable it globally with `mu.init({ scroll: false })`.

In the Playground, the scroll-to-top behavior is intentiona, it's there to illustrate that feature.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Nothing special happens. µJS doesn't change anything for direct URL access. The server renders the full page as usual. µJS only intercepts navigation once it's initialized on the page. This is actually one of the advantages of this approach over a true SPA: every URL is a fully server-rendered page, so direct access, bookmarks, and sharing links all work out of the box.

Let's take an example. Say we have a website with 3 pages:

- Homepage "website.com/"

- Products "website.com/products"

- About "website.com/about"

Your browser can load any of these pages directly, as usual. But when you are on the homepage (for example) and you click on the "About" link, µJS load the "/about" URL, and replace the current page's `<body>` tag with the fetched page's one.

It's that simple.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks, looking forward to your feedback!

I deliberately chose the phonetic spelling 'mu' of the 'µ' greek letter, rather than the typographic approximation 'u'. The 'u' convention comes from ASCII limitations; when µ wasn't available, 'u' was used as a fallback. Since we're well past those limitations, 'mu' felt more accurate and more original.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
The sweet spot is everything between a static website and a full SPA, which is actually most of the web. Think e-commerce (filtering, cart updates, pagination), dashboards (refreshing metrics, notifications), admin interfaces (inline editing, form submissions), content-heavy sites (live search, comments, modals). Basically any app where the primary interaction is 'user does something, server responds with updated content'. For drag-and-drop kanban boards or collaborative editors, you're right, µJS is not the right tool. But those are a small fraction of what gets built every day. Most web apps are glorified CRUD interfaces, and for those, server-rendered HTML fragments are simpler, faster to develop, and easier to maintain than a full SPA.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks for the link. I'm aware of the debate around GitHub. For now it's where µJS lives, but noted.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Good point, thanks! That would definitely make the examples clearer. I'll keep it in mind.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
The target audience is developers who are efficient at generating HTML server-side using an MVC framework, a template engine, whatever they're comfortable with. Instead of building a JSON API and a JavaScript layer to consume it, you just return HTML fragments from your existing routes.

µJS handles the AJAX call and swaps the fragment in the DOM. No JavaScript to write, no state to manage client-side. jQuery is more powerful and flexible, but that flexibility comes with complexity: you have to write the fetch call, handle the response, find the right DOM node, update it.

µJS does all of that declaratively via HTML attributes. It's not for every use case — highly interactive apps (think Google Maps or a rich text editor) still need proper JavaScript. But for the vast majority of web pages, server-rendered HTML fragments are simpler, faster to develop, and easier to maintain.

amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Yes, htmz is a great project! The core idea is similar, but htmz is intentionally more minimal (it's literally 166 bytes). µJS covers more ground: patch mode, SSE, DOM morphing, View Transitions, prefetch, polling, full HTTP verb support.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
That's exactly what patch mode is designed for. You open a single SSE connection on a parent element with `mu-mode="patch"` and `mu-method="sse"`, and the server sends HTML fragments with `mu-patch-target` attributes pointing to whichever components need updating. One connection, multiple targets. The server decides which components to update on each event.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Nice! 100 lines is impressive. µJS is a bit more feature-rich (patch mode, SSE, DOM morphing, View Transitions...) but if you need something truly minimal, Swap.js looks like a great option.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Yes! `<script>` and `<style>` tags found in the fetched content are processed on the fly, as long as they haven't been loaded already. So you can include a gallery-specific stylesheet or script directly in the partial, and µJS will inject and execute them automatically. For triggering a JS action after rendering, the `mu:after-render` event is the right hook.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks! Yes, swapping the entire body by default felt like the right trade-off, it covers 90% of use cases with zero configuration, and you can always narrow it down to a specific fragment (or group of fragments) when needed.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Indeed! While the names are similar, µJS (mujs.org) and MuJS (mujs.com) are completely different projects. The former is a browser navigation library (a lightweight alternative to HTMX and Turbo), while the latter is an embeddable JavaScript interpreter written in C, developed by Artifex for MuPDF. Sorry for the confusion!
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
µJS is HTML-over-the-wire, so it doesn't parse JSON responses natively (same limitation as HTMX). That said, you can work around it using the `mu:before-render` event, which lets you intercept the raw response before it hits the DOM and handle it however you want. It's not as clean as native JSON support, I'll admit. If your use case is primarily consuming existing JSON APIs, µJS (and HTMX) are probably not the right fit, they're designed around server-rendered HTML fragments.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Thanks for the feedback! The RDFa `resource` attribute is an interesting angle I hadn't considered. That said, µJS's default behavior actually requires zero `mu-` attributes; it intercepts standard `href` links and `action` forms out of the box. The `mu-` attributes are only needed when you want to override the default behavior, use patch mode (updating multiple DOM fragments in a single request), or trigger requests from elements that are not links or forms. So in the simplest case, a single `mu.init()` call is all you need. That said, if you want something even more minimal, htmz takes that philosophy to the extreme.
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
I haven't tried it personally, but I don't see any reason why they wouldn't work well together. µJS manipulates the DOM via fetch and swapping, while web components live in the DOM like any other element. The only thing to be aware of is that if µJS replaces a fragment containing a web component, connectedCallback will fire again, which is the expected behavior. Would love to hear your feedback if you give it a try!
amaury_bouchard··on Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
pjax is actually listed as an inspiration in the README. It's a great project, but it hasn't been maintained since 2018 and requires jQuery. µJS is dependency-free and covers a much broader feature set.
Page 1 of 2Next →