Hacker News new | past | comments | ask | show | jobs | submit login
Django, HTMX and Alpine.js: Modern websites, JavaScript optional (saaspegasus.com)
450 points by czue 10 days ago | hide | past | favorite | 273 comments

Can someone genuinely explain to the the desire/interest to use HTMX/Hotwire/LiveView etc?

I was writing web apps when rendering templated HTML views on your server was standard, and you had controller endpoints that returned HTML content, or things like Rails "Unobtrusive Javascript" where you had code like:

  $(‘#user-list’).html(“<%= j render(‘index’, users: @users) %>”)
As an automatic callback to some action.

Whether or not you love or hate this model of development is irrelevant, because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.

IE, so that your Android/iOS app in Java/Swift or whatnot, can ask your backend for data and render it using the tools available on that platform.

When we turned apps into pure API's and just exchanged data with them, it opened up the ability to be platform-agnostic.

I guess I don't understand why someone would choose to build an app that sends HTML over the wire instead of JSON/XML/whatever, that any client can render. All it means is that you have to write this functionality later when you want it, in addition to now having this HTML stuff.

First, many sites do not need a mobile app — people who sell apps like to present that as a requirement but there large categories where it doesn't make sense and most users won't install it because it takes space on their device and they [correctly] fear ads, notification spam, and possible invasions of their privacy. I've seen figures for this where pretty well-known organizations spent millions on the app and then saw install rates under 10% with most users opening it only once or twice.

Second, this is something of a false dichotomy: if you have a website, you need to have HTML somewhere. One approach is to use an SPA but that increases the cost somewhat significantly and absent a significant engineering effort will reduce performance and reliability because you are moving functionality out of a high-performance data center where you control everything onto the users' devices where you have to work with whatever browser and extensions/ad blockers they have installed.

An alternative approach is to render HTML on your servers — quite possibly using the same JSON API as the source — and then progressively enhance it with the functionality which makes sense to do on the edge. This gives you a hefty performance win for the initial page-load times, helps with things like search engines, and ensures that your users get at least the basic experience when something goes wrong with client side JavaScript (network errors, browser compatibility issues, ad blockers, etc.) rather than nothing working at all.

> First, many sites do not need a mobile app — people who sell apps like to present that as a requirement

Yes, Testify! Also, I'd like to mention, so many of these upsold apps end up being a wrapper around the normal web view. I've seen this being advertised/proposed as a plus, as well "No need for your customers to learn a new interface, we specifically engineer your app experience to be just like the web experience". It's interesting to see heads nodding in reply to this.

Oh, yes — definitely saw those. My favorite were the ones in the pre-WKWebView era where the app was basically the website, except slower because it didn't have the JavaScript JIT enabled.

The idea is discoverability. Once, people looking to purchase Nespresso coffee online would go to Google. Now they go to their app store. If your app isn't there, they those people are going to install a competitor's app and buy your competitor's coffee.

Do they? Really?? I know some people who don't even go the the store for apps and do everything through the Google app that includes app results as well, but the other way around?

I just searched for "buy coffee" in G Play and not a single one of the top 15-ish results let me buy coffee apart from one coffee shop in New York, which is off by roughly one half globe circumference. If anyone ever did that, they'd quickly realise there's nothing there and go back to a search engine.

According to the guy who does metrics for my client - and his company does metrics for the largest e-retailers in my country - this is the case. Maybe he's pushing his own agenda or dream? Maybe it's market-dependent? But this is a company who can be trusted in the industry.

Wouldn't be the first time industry research results were conveniently aligned with profits, but I'm obviously not accusing anyone of anything as my evidence is 100% anecdotal and any research is better than none.

I guess it's just that users are stupid? Like, I know calling users stupid is a bad trope and all, but this is one of those cases where the behaviour just straight up wouldn't produce usable results. Like, if they're looking to buy a bike, will they just search for "bike buying apps"? I get looking for "apps to buy things with" (like amazon, wallmart, whatever) and then picking one and seeing if they offer bikes, but looking for the product directly??

This actually seems to also contradict the recent trend of people moving to big apps like DoorDash and Amazon replacing individual apps with different niches like the McDonald's app and apps from smaller retailers. If you're looking for "buying a bike" the Amazon app won't even be on the list...

Again, all of this is my logic, I have no evidence. This is just one of those situations where something goes so strongly against my intuition that I can't imagine how it could possibly be correct. I'll have to look for some real research on this, if any is available...

» This is just one of those situations where something goes so strongly against my intuition that I can't imagine how it could possibly be correct.

I think there are a few things at play here

1. Principal agent problem https://en.wikipedia.org/wiki/Principal%E2%80%93agent_proble...

2. Illusory truth effect https://en.wikipedia.org/wiki/Illusory_truth_effect

So like you said in the beginning, it makes sense that people selling something will highlight "evidence" that it works (principal agent problem).

Marketing is counterintuitive though. Please take a look at illusory truth effect and tell me what you think.

For what it's worth, I agree with you. But I don't know if that is because I was just trained to google everything.

I’d really want to check the sources on that to see the methodology. I’m in different spaces so it could be different here but that’s pretty much the polar opposite of what I’ve seen – people use social media or web searches, and hit the App Store only when pushed with a hefty drop-off rate. I’ve heard that this is very different for a few categories like games, which makes sense, and I think a key part is how well you can sell the benefits to the user: games, Uber, etc. have a pretty clear benefit but it seems like installing an app is seen as committing to some kind of ongoing relationship.

Is this really the case? It never occurred to me but I'm web native, not app native.

If it is, in most cases we can just release a webview-in-an-app and render the usual responsive website on the phone. This is the low cost route to apps.

However there are products that could genuinely need an app. A Nespresso machine could be one of them: control everything from your phone and if a machine has some kind of cup automation, also make coffee from remote and walk to the machine to get the cup whan it's ready.

I agree with the sibling comments, I've never actually seen anyone go to the app store before Google/Amazon for simple purchases.

When it comes to these companies selling apps though, it doesn't matter what the market thinks... What matters is what the suppliers think the market thinks.

I am open to HTMX. Seems refreshing but I am a little confused about how to structure the backend API returning small bits of HTML? Wouldn't that be a colossal nightmare to reason about?

I can think over time I'll have endpoints like this:

  POST https://foo.com/bar-section/baz-button
No? Haven't quite thought about it deeply but I can imagine logic mixed with UI bits in the backend.

Have a look at how Phoenix(Elixir) does it with LiveView. You simply write everything as a server rendered view and the framework pulls things apart and sends just what changed over a web socket. Its mind blowing how fast it is (can be. Latency is real)

I have no doubt that this style of front-end management is going to be a game changer in the next few years. Of course full-on SPA frameworks will still have their place. But if you aren’t writing the next Google Maps clone, maybe you don’t need all that. It’s made me love web dev again after hating the complexity explosion of recent years.


What worries me (I write Django, Rails, Phoenix web apps - different customers at the same time) is that I'll have to learn yet another three different ways to do the same thing. What I'm doing now is either creating HTML pages (the 3 ways are pretty much the same, Django a little bit more set apart) or a JSON backend (basically the same everywhere.)

But to be fair it's fear of the unknown: I read about all of these approaches but the only new project started with a SPA and the other ones will never move from backend generated HTML.

The concept is absolutely fantastic, I am very intrigued, thanks.

> API returning [...] HTML

The endpoints that return endpoints are not API endpoints, they are UI endpoints like `/index.html` or `/login` or `/Results.aspx?id=3984272`. Now you can have `/login/email` or `/login/google` and switch between them like <https://htmx.org/examples/tabs-hateoas/>.

As for the machine-consumed API, you have quite a few options. e.g.: The server that serves these resources may serve JSON to the same URLs through content negotiation, or the API may be separate, or you can have a generic API that is consumed by mobile app, desktop app and HTML-producing server (as opposed to JS app delivered by static file server or framework SSR).

The UI runs on the server, using REST to change client state over a stateless protocol similarly to how a React app may use Redux.

Not really, as long as you keep your HTML-driven interactions from becoming too fine grained.

For example, if you have a search page that provides management of contacts + active search & click to load functionality, it might look like this:

  GET /contacts - get a list of contacts + search & click-to-load functionality
  POST /contacts - create new
  GET /contacts/:id - get the details for the contact
  PATCH /contacts/:id - update the contact
  PUT /contacts/:id/archived - archive the contact
  DELETE /contacts/:id - remove the contact
All pretty reasonable and all can reuse the same few templates and sub-templates for rendering responses.

Thank you. To continue this conversation, how would one implement searching and filtering? My use case is an online ecommerce store (think amazon.com or newegg) where you sell multiple different categories of items. For example, if someone searches for SSD, we should show a list of checkboxes to filter by capacity such as 1 GB or below, up to 127GB, ... (based on inventory and other business requirements). How would I do that?

In your example, we can say something like if you could grab lists of by contacts by some common trait such as employer or domain of email address?

You'd have category pages with different filters. E.g. for the 'Storage' category you'd have, among others, the 'capacity' filter. HTMX doesn't take over your entire render like a SPA. It affects targeted areas of the page. E.g. when you would select the '<1GB' checkbox and click 'Filter' on the left panel, that's where HTMX would step in, to swap in the response of calling the 'search' endpoint with the current filter params, e.g. '/search/storage?capacity=<1GB'.

The trade-offs in terms of how granular to go will always exist -somewhere-, whether here, or in terms of react/etc. components, or template include directives.

I suspect 'template include directives, except with some client smarts' may be a good (approximate) model to use to initially get a mental feel for how stuff like htmx plays out in practice.

I think you can get pretty far by having good conventions and not over-thinking it, but remember also that this is all about being pragmatic instead of taking a one-size-fits-all approach — if you found yourself doing a _ton_ of highly-granular requests, that might be a clue that you're not in the sweet spot and an API client might be a better fit.

You can also render a full page and htmx to will intelligently update the current page where things match up.

Unless we are speaking about games (WebGL/GPU are never going to compete with GL ES 3.2/Vulkan/Metal capabilities), or hardware access not exposed as Web APIs, all CRUD apps and magazine/newspapers content can be delivered as mobile Web.

Can you name one, that the group would know, that wouldn't be improved by a convergent phone or tablet offering?

When was the last time you used an application from a gallery, archive, museum or library? How many people use https://apps.apple.com/us/app/wikipedia/id324715238 instead of visiting wikipedia.org? I'm not naming names but this came up a number of times at meetings I was involved with in the GLAM community where people mentioned having spent considerable amounts of money on apps, even winning design awards or getting featured in the Apple AppStore, and then seeing that their daily active users never came within 4 orders of magnitude of their web traffic.

What they generally had in common is that they're content-heavy (i.e. solidly in what the web was designed for), might not require authentication, and most importantly aren't something most of the users wanted to use all of the time. If you are trying to get the hours for a tourist destination or even a restaurant, look at public information, etc. the time needed to login to the app store, authenticate to approve the purchase, wait for a large file to download, open the app and complete any mandatory enrollment, wait for it to retrieve content, etc. was often orders of magnitude slower than using the website. Restaurants and bars are a great example of this: if it's your hangout and they have something like integrated ordering, maybe a trivia night or something like that, the app can be useful. For most people, however, a webpage with the contact info and a menu with Apple/Google/Stripe buttons to pay is going to be better than an app they use a couple of times a year.

The other key thing to understand is that this is a question of priorities and budgets. It wasn't that there is no possibility of doing anything useful for these organizations in a mobile app but rather than the cost wasn't worthwhile for the incremental benefit to the users, especially when you considered the opportunity cost of not spending that much money on something else, especially since most of the cool things you could do in an app could also be done on the web with very similar quality but with the difference that you only had to build one version rather than 2+ and the web version required much less ongoing maintenance.

Yes, I think we're in agreement: fuck apps, use webapps.

Progressive web apps with proper manifests so that I can put them onto my home screen.

I for one will never download an app when I can just use the mobile website instead. That's what modern websites are — they're single-use disposable applications. Why would I throw that away for a native app that eats up disk space and has much greater access to my device?

  > Why would I throw that away for a native app that ... has much greater access to my device?
You wouldn't, because you understand. But for every one of you there are 99 other who will install such a app if it is pushed to them. Apps are almost like bookmarks now.

Bookmarks with access to your location, contacts, and camera.

> Bookmarks with access to your location, contacts, and camera.

Also bookmarks that usually point to an obsolete and/or inferior version of the web site.

Hn would be worse as an app, but more notably Twitter, and reddit would be better as pure web pages if they didn't purposely cripple them to funnel people into the app.

Well, i usually read HN on my mobile with Materialistic. It is one of the apps i use the most often.

There's no way to be notified of comment replies, or see votes, though. But otherwise I love it as well.

Dumb question time… How do you get notifications on comment replies? I thought this missing feature was “by design” to limit bickering.

Edit: I won’t (eventually) see your reply because I just hit the comment depth limit. Reach out by email if you want to answer. :P

There's a handy site if you see this: https://hnreplies.com/

There is no comment depth limit per se; to continue replying, you must click the timestamp of the comment you wish to reply to.

Same, it works offline, loads faster with bad mobile connection, better gestures & UX overall.

I use twitter's PWA. Not planning to ever use the proper app.

Even twitter is a PWA now — the app is redundant.

In the same way a mobile-first website can be done poorly—or non-existent—so too can a native app not add any value.

Lots of reasons:

1) No need for 3.7GB of node_modules to crank out a simple site

2) Single solution, easier to bring on junior developers and reason about application logic

3) Caching much easier, too many people think they need real-time push when NRT-polling + caching is far easier and more performant

Broadly speaking it's often a case of YAGNI. The JS ecosystem really does still suck for its complexity, I remember that every time I ramp up a new developer onto a React project. It's smoothed out with tooling but the litany of JSON configuration files and the mess created by hot-reload + proxying + microservices etc etc. Often just YAGNI.

People today don't remember the VB6 IDE days where you could slap a button on a Form, write a line of code for a MessageBox, and smash that play button with zero effort. JS development is often about working your way through a tangled spider-web of helper utilities and config files and JS modules at which point when it finally works you're afraid to rev anything.

bang on, especially that last part

a bit focus of htmx, alpinejs (and htmx's sister project, https://hyperscript.org) is putting the logic on the element, so you can see exactly what a button, etc. does just by looking at it, and without trawling through ten files of indirection

i am trying to popularize the term Locality of Behavior to describe this design goal:


I'm curious about your thoughts on that locality of behavior idea vs aspects of precision and composability. For example, the argument goes that the behavior of `hx-get` is obvious upon inspection, but the terseness is achieved by omitting the event type (i.e. it's assumed/implied that hx-get runs on click). The quintessential widget where that breaks down is the table w/ checkboxes for batch operations over an arbitrary subset of rows. That's where less DSL-ish frameworks' abstraction via state management tend to shine, since they allow the developer to more precisely specify how disparate checkboxes relate to a bigger picture that may also involve table sorting (both client and server varieties) or nested modal structures or other forms of complexity.

Button should not have any behavior, it should generate an event. Your app can have one or multiple listeners for this event. This pattern is battle-tested for decades.

OK, but the response to that event should be localized to the button.


Why? Have any reading on this?

1.) Don't be a jackass.

2.) I know what reactive programming is, I make extensive use of RxJS at work. And I obviously know what an event listener is. I was asking why is it wrong to have the button have functionality and only emit events. And I did google that, but nothing from your comment you posted that I googled lead to anything about reactive programming.

If your node_modules is 3.7GB, you are doing something seriously wrong. My node_modules for a 100k+ line app is 243MB and that includes all my dependencies, compiler and testing frameworks which seems very reasonable. All compiled down to a 4MB blob which seems pretty reasonable for a big app :)

3.7GB was obviously an exaggeration for comic effect, however, I laughed just as hard at 243MB. How on earth do you think that's OK?

243 MB?! If that's all code (which is unlikely, sure) and it has an average line length of 100, that's 2.4 MILLION lines! Do you really need that, (no), or have you just 'stockholm syndrome'-d yourself into believing this is not a problem?

I just tried running WinDirStat on the `node_modules` folder in a Create-React-App project I had lying around. Total size was 234 MB. Of that, 129MB was JS files. By far the biggest piece was TypeScript, which has 45MB of .js in 6 files making up its parsing and IDE language service implementation.

Beyond that, the breakdown is:

- .ts source files: 25MB

- Sourcemaps: 23MB

- Markdown: 15MB

- JSON: 10MB

It also looks like there's a 12MB Linux binary that was only in there as a publishing accident for the `user-event` library that has since been resolved ( https://github.com/testing-library/user-event/issues/266 ).

Or, take Redux Toolkit, which I maintain. If you look at https://unpkg.com/browse/@reduxjs/toolkit@1.6.2/ , the published package for our current version, it adds up to about 10MB on disk. But, that's because we ship three separate entry points (RTK core, RTK Query UI-agnostic core, RTK Query with React-specific additions), and for each entry point we compile the code to multiple file formats (CommonJS, ESM legacy, ESM modern, ESM with "dev" and "prod" already built in, UMD dev, UMD production), and each of those has sourcemaps. The actual amount of code that ends up in your bundle is about 20KB min+gz.

So, it's a combination of many things: TS itself is big, libraries typically publish packages containing both the original source code plus multiple build artifacts to run in different environments, and packages also include various metadata and other files as well.

I'm not saying this is necessarily a _good_ situation :) Goodness knows that JS dependency trees _should_ be much smaller. But there's a lot more nuance to it than just "245MB of code wut lol".

There's definitely an anchoring effect going on. I remember when dart came out and people were like "lol 100kb hello world, wtf" and now people say "4mb app is reasonable" and nobody bats an eyelid.

Heh, that's actually part of the point I'm making - big difference between "size of build dependencies unzipped and extracted on disk", vs "size of actual app bundle".

As an alternative view, while living in a very unremarkable North American area I have atrocious internet service. I don't think it's particularly uncommon, but it certainly is enlightening to browse the internet. Depending on the time of day a 4MB blob by itself can take ~8 seconds to download. 8 seconds is a _long_ time.

Obviously I have no idea what your app is, I have found very frequently it is not reasonable to have blobs of similar sizes (especially if text content waits for some front end framework to download/initialize).

Certainly. My hope is things like WASM eventually make Javascript obsolete if we're into pushing giant blobs of client UI over the wire. Blazor / Bolero in C#/F# are promising starts in that direction.

How do you compile it to a single blob?

With tree shaking, and it is usually split into several files for caching purposes and to download only what's really necessary.

As a developer who worked both with Windows Forms and React, IMO the only explanation for longing for the former could be that lovely nostalgia feeling when you already know what react offers.

I think people approach it with prejudice (ew, Javascript!) and never properly learn it.

There are projects where react would be overkill but many benefit from the clear separation of the front end.

Also configuring a react project is easier than many other projects I've seen in my career. People just love getting internet points by constantly talking about left-pad, node_modules size, config files etc. I personally know how crazy managing maven or gradle (.gradle folder hell) or nuget or composer or pip (venv or virtualenv...) or [name of your favorite package manager] can get, so I'm thankful for the great community that work on the problems with the npm.

I believe the Model View Controller or Model Template View architectures provide sufficient separation of concerns without requiring separate codebases, teams, tooling, and project management.

I didn't mean the classic separation of concerns. I meant that front-end development should be taken seriously enough to (sometimes) warrant its own focused team.

A proper FE developer who knows about accessibility (not just ARIA-attributes but also contrast and usability), UX, semantics, and also being capable of delivering performant user interfaces is rare these days, because people think full-stack development really is a thing. I think it is one of those mythical things like a 1000x engineer. People think that they can get away with 90s style websites, but please test stuff with a screen-reader, and also while you're there get some front-end consulting, you will be surprised how badly you may be doing. IMHO, many need dedicated FE development. Whether they use React or not, I think many do. You can distribute many responsibilities on non-technical members of the team, but then you start thinking "couldn't they just learn a bit templating so I don't invest so much time on implementing their seemingly never-ending requirements"!

BTW, this separation is of course also possible with your traditional MVC stack, which I also use in my personal projects a lot, in which React is indeed overkill, and there are many cases that a bunch of templates deliver all the things you may ever need. If it gets complicated enough some day, return your View Model as JSON and there you have a perfect API. IMHO REST APIs aren't suitable for any dynamic front-end development anyway.

The only reason I wrote that single sentence in my previous original comment is that React forces your hand a bit, in this matter. It's a small point among other things.

Doing a ground up rewrite of our app at work. I made the call that the application needed to be semantically correct (well, mostly correct. We avoid div everywhere), have proper tab order, and be screen reader accessible. It takes more time but honestly it's not that difficult to do. Most people just.... Don't do it.

It doesn't end there. Even caring a bit for that makes a huge difference, I agree, and I appreciate the effort. The last time we had an accessibility audit, however, it was consisting of more than 200 problems with different priorities for an app with 70 something views. Our FE people were immediately up to the task of getting them fixed; a motivation I simply cannot expect from anyone who doesn't have a front-end focus. My basic checker reports in this simple comment-editor already a couple of problems.

It also doesn't end there. There's the UX part, re-usability (huge difference to have a cross-team front-end service-team), performance (not just initial load... Are the widgets you build with, say, Alpine.js tested for performance?), development speed, caching, offline-capabilities, opportunistic rendering, other best practices, branding, consistent design and interaction and so on. It's a science on its own.

Front-end is not just some basic thing which got a complex solution for no reason. It is a complicated task.

Using JS/CSS in classical Java and .NET frameworks is so easy (and bleeding fast) when compared with dealing with 1).

Unfortunely it is an option I can only dictacte on personal projects.

1) Who cares about the size of node_modules? What is the reason to care about it at all?

2) “Junior-centered” app has to be simple as a “ToDo list example”, all the time. It's not possible for the real apps - they constantly evolve, and to keep their code readable and clean you need skilled developers, not the other way around.

Worth pointing out that HTML is more ubiquitous than you're making it out to be: just plop a WebView in your native app, and you're done. I know, for example, that the Uber app does webviews in some places; you probably wouldn't even be able to figure out where just from looking at it as a user.

Another case in point: is your dev team going to support PSP devices[0]?

[0] https://shkspr.mobi/blog/2021/01/the-unreasonable-effectiven...

> because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.

The answer lies in the "if" there. The speed and lack of complexity developing server-side with no API is unparalleled to doing client -> api -> backend. Not to mention, you aren't tying yourself into several different JS technologies (Which GraphQL or REST lib? Which view framework? What testing frameworks? And what's the whole backend stack? etc. etc.) I can only speak for LiveView but the whole top-down experience is in Elixir with sprinkles of vanilla JS (the community is big into Tailwind and AlpineJS as well, but not exclusively).

I have real world experience of dev'ing API-first only to have the company nix any plans to open up the API. Adding new features is always a slog as we have to think about if the business logic needs to be on the client, the server, or both... not to mentioning have to update about 18 different places the name of a new field you're adding.

The company I work for actual has a LiveView widget that is embedded on their client's apps. I can't speak very well to it since I only just started working there and don't actually start until next week, haha.

But ya, I'll also echo the whole idea that a lot of companies don't need mobile apps. I'm certainly one of those people who uses the browser on my phone whenever I can.

Because of the simplicity, flexibility and power of the hypermedia programming model.

- simplicity: e.g. something like active search can be implemented with a few attributes: https://htmx.org/examples/active-search/

- flexibility: e.g. because of the uniform interface, versioning is much less of an issue

- power: e.g. deep linking is trivial

Additionally, there is increasing awareness[1] that Application APIs and Data APIs have different needs and "shapes", and, once you have split them up, reconsidering using hypermedia for your application API[2] is a reasonable thing to do

[1] - https://max.engineer/server-informed-ui

[2] -https://htmx.org/essays/splitting-your-apis/

you have to start from the beginning and write an API now.

Is it really that much more work? Much of the time the big difference is just be replacing

   return render(my_template, my_data)

   return to_json(my_data)

A few years back, I implemented this as a Django CBV which worked quite well:


That handles a few things like pagination for list views, calling get_absolute_url() on objects in the template context which have that method defined, and running all URLs through request.build_absolute_uri().

> because if at some point you need to render on a client other than a browser

Been doing this for decades and this has cropped up so few times I can't actually think of any.

Your clients might not be my clients. Your projects differ from mine. Don't generalise from your own experience. There's a lot more websites than there are apps.

The need for a native mobile app (or any kind of platform-agnostic behavior) probably negates much of the value of these stacks.

That said, many projects don't have or need mobile apps, and many more can probably get away with a web-first mobile app.

For those apps this stack can vastly simplify and streamline development

  > That said, many projects don't have or need mobile apps, and many more can probably get away with a web-first mobile app.
To me this is like building a house that can never be expanded, instead of starting with an open-ended design.

  > For those apps this stack can vastly simplify and streamline development
I guess this is an opinion/subjective?

I am very open-minded and experimental. I love trying new things.

I've tried these technologies (HTMX, it's precursor Intercooler.js, Turbolinks + Hotwire, LiveView, etc) and for me they are less productive than the ways I know how to build API-first, JavaScript web apps.

Have nothing against them, just genuinely having a hard time seeing the appeal.

  I've tried these technologies ... and for me they are less productive than the ways I know how to build API-first, JavaScript web apps
I think I feel the same way you do, except the other way around. I genuinely can't understand how you build apps productively with a separate client/server model.

How do you do form validations? How about authentication and authorization? What about a consistent domain model? Is there one copy of it on the server and one on the client? Or does your client just treat everything as dumb data and make network requests for every question of business logic? What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app? Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever? etc. for ever and ever.

Every time I try to build an app where the frontend and backend are split into a separate client and API, it ends up adding so many more considerations and so much more complexity than the traditional model. Mostly because now everything that just required a database query or method call before now also requires a network request, but also because data consistency in a long-running app is so hard to get right.

> How do you do...

Those are all questions I have, as a 2-decade "traditional web app" developer who is now doing frontend work & hybrid mobile apps. I know the backend sucks for some of them too, so this isn't a set of questions to bash with.

They are the questions I'd spend money on a book or learning materials to answer. They are philosophical questions that seem overlooked.

I think the answer is JS/TS on server and client-side for a lot of them, so models/classes/structs are shared; form validation rules are shared (something let's face it that "traditional" web apps suck massively at).

The one Q I feel qualified to answer is: > What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app? Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever? etc. for ever and ever.

Assuming you're not talking realtime (because that's a problem for both) then this is why the concept of data stores (Redux, Vuex, Mobx to name a few) are so popular. Because when the data is changed, and you're using a reactive frontend framework, it updates all components that use that data. It is, frankly, magical and wonderful. But as you say, data consistency can be a problem...

Your questions are all... contrived?

> How do you do form validations?

How do YOU do form validations? Is it entirely on the backend so it requires a round-trip before the user gets feedback, and additionally requires you to wire both the HTML endpoint for user feedback AND for rendering some results?

For myself, I have a reusable Form component I can plug in arbitrary tests. You can run the tests when the user changes an input or on submit, with some options/conditions for when you clear the feedback.

Then, of course, my API gives feedback as well; usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields, and I certainly don't see how HTMX would accomplish this, so without some machinery I already feel your side lacks QOL features like "highlight error fields" and "autofocus first offending entry".

> How about authentication and authorization

You are joking or what? There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would

Finally, you ask a bunch of questions that clearly aren't any better in an API-endpoint-randomly-renders-HTML paradigm...

> Is there one copy of it on the server and one on the client

Obviously, but you load the client version from the server at API request time... just like an HTMX powered client... except an SPA keeps objects instead of a rendered representation so you can do more in-depth things without network trips...

> What about data consistency throughout the interface—when you update e.g. Document(id=45) with new data, how does that update get propagated to every other place in the app?

Because you use singletons like a not-crazy person... how does this happen in HTMX world where you have no data representation on the front-end? Does your form have to be manually wired to update every single present data view that is holding that document? Seems like you're worse off than me. I hold a stateful representation and update the views when that representation changes, automatically... Wow, literally no work required because it's declarative

> Does that involve a client-side identity-map? Or are you manually trying to send updated data to other places with callbacks/whatever?

Laughable. the HTMX paradigm is clearly worse off here

> Every time I try to build an app where the frontend and backend are split into a separate client and API, it ends up adding so many more considerations and so much more complexity than the traditional model

You clearly did not bother to understand how state & views relate in an SPA then. Your complaints make no sense either, this happens in HTMX world more than mine:

> now everything that just required a database query or method call before now also requires a network request, but also because data consistency in a long-running app is so hard

I honestly can't tell if you're trolling me. Let me break it down for you.

In client/server world, client asks APIs for the data. Easy. Imagine a paradigm like GraphQL, the client literally just loads the data. It's the same as it would be on your server endpoint for HTMX, that's literally the client in your world. I hold a global singleton representation if my app is interconnected, or I load different singleton representations for unrelated subtrees if it's only partially interconnected.

Next, if "collaboration"/real-time features are important, you make this data loading "aware" of when the data changed server-side, and you refresh your data. Easy as pie; you use the same function to reload. Lots of options on how to update the data, but if you correctly make your "load" function idempotent it is clear this is trivial.

Finally, you write views that describe, declaratively, how to render the data. Since application state automatically re-renders these views, ideally not in their entirety if you used a good state management, you've already solved every single concern you listed about "stale data" by how you wired your data loading.

So, great, in conclusion, HTMX endpoints are like a scattered version of a client that makes all nice "application" tricks virtually impossible, giving you no gains in any fashion that relates to data consistency or continuity, and requiring loads of manual wiring and awkward data-HTML manipulation in places that have no business doing it.

  How do YOU do form validations?
I use some server-side library to do it (take your pick for your language/framework of choice) which then sends it back to the user, in most cases automatically wiring up old field values and error messages. Checking string lengths or whatever is equally easy in both cases, but what's really difficult is the part you conveniently hand-wave away:

  usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields
This is the entire point of validations. When I render everything on the server, I have my entire application and its associated logic to play with; can this Person access that Document? Has this Person already enabled Notifications for new Revisions on this Document? etc. Most server-side libraries make it equally easy to surface validation errors for those questions as they do questions like "Is this Password no shorter than 10 characters." Every time I've tried to do this on the client there's been no easy way or you have to wire it up manually every time.

  There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
I've found this not to be the case. Here are the potential options I see:

- The client contains all the logic for who can view what, and redirects or shows a message appropriately using whatever mechanism is appropriate (e.g. Angular has route guards, I'm sure other routers have similar functionality). Because you want real data protection, this logic is also on the server, so now you have duplicated logic and you have to hit the server multiple times on every navigation, which is basically impractical. - The server contains all the logic for who can view what, and when the client asks the server for some data, if it responds with 403 it can show a message or redirect or whatever. This is okay, but now you have so little information on what happened on the client side that it's a much worse experience. e.g. if I try to access a document that I don't have access to on the server-side, I can redirect_back and maybe show a pretty message that says "hey you can't access that, maybe click this link to ask for permission." If I just get a 403 from the server, all I can say is "uhh I don't know, you can't do whatever you just tried to do."

This is much easier if all your logic resides on the server. To check if a Person can access a Document, you just call person.owns?(document) or whatever. If they don't, you just redirect_to wherever, and since you haven't actually rendered anything yet it's all in a single HTTP request-response cycle.

  Obviously, but you load the client version from the server at API request time
I'm not sure you understand my problem here; it seems like perhaps client-heavy people just aren't doing this anymore? Ember, Backbone, et al got this part right, and everything since then seems to have forgotten about the "M" part of MVC.

What I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions, and they're stored in an array or something and I can get the newest revision by calling document.latest_revision, or I can get properties of the latest revision by just doing document.latest.title. Maybe when I update a field on a Document I really want to create a new Revision or something. Whatever, I'm just making stuff up here, but you get the kind of business logic I'm talking about.

How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server? Do I put it all on the client...oh but wait I can't do that because the server needs to do integrity checks or make database calls that the client simply can't do.

In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too. This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this.


Your final few paragraphs are essentially all the rosy writing out there about client-heavy approaches: look how wonderful it is! Just fetch your data and we'll rerender it! I understand all that. And maybe it's evidence that we're just living in such different worlds that there's difficulty explaining requirements to one another.

If I was creating Desmos or something, where basically every ounce of logic exists on the client, and the client's job is just to do a bunch of stuff with client-generated data and display it, then yeah I'd probably reach for a client-centric approach. But I'm not. Most of the time I'm creating GitHub or Wikipedia or Fastmail or something, where there could be some nice benefits of having a fat-client that talks to an API, but the massive complexity increase just isn't worth it.

    usually, it's harder / more awkward to wire the API feedback to highlight the offending Form fields
> This is the entire point of validations

Now, let's be explicit - I can effortlessly show an error toast describing those errors, and my client-side validations are generally meant to be a 1:1 match. I also say "awkward" rather than "not possible" because you can directly access the validation engine in my paradigm and set error messages for individual fields, but it requires the API endpoint author to be returning a data structure and not 400 'Requires some other thing' style.

And now I see what you are saying about doing this server-side, but the whole point of the client-side method is that for free you get this:

> in most cases automatically wiring up old field values

I get it in 100% of cases, for arbitrarily complex form elements. I also greatly object to this point:

> Most server-side libraries make it equally easy to surface validation errors

If I am using arbitrarily heavy code of course I wire these error messages perfectly. I will not concede any points about time-to-execute going forwards, I see it serves only to muddy the waters as you have strong capabilities

    There is no difference in how an HTMX app would authenticate to its endpoints and how an SPA would
> I've found this not to be the case

I don't really follow your objections / cases. You argue that the server is always aware of who can see what, and this is transparently true, but you try to argue that the client dynamically loading who can see what from the server is a problem due to network round-trips yet the server-rendered app where there's network round trips for literally every user action isn't a problem. That is where my uncharitable tone is sourced from; you can dynamically return this stuff to your client with ease, and all you've done in an HTMX-style paradigm is force the user to accept the "bad case" outcome for client-queries-server nav in the best case.

You will have to go deeper if I am meant to see why the client couldn't just load some batched map of legal routes they can and can't see based on their authorization and update any nav options around the app (sidebar, page-to-page, etc.) accordingly. I regularly load configuration batches to configure my client-side applications, and you clearly execute extremely similar code to bridge the UI layer and ACL layer on your server. Moving this to the client is entirely unoffensive and standard web development work

To be precise, your example / motivation is something like:

> This is much easier if all your logic resides on the server. To check if a Person can access a Document

In order for the user to have a copy of this document to click on at all, I have made a network request to get some data about this document. If ACLs are important for this interaction, I load them at the time I load the document's data. For example, I have "view" vs "edit" functionality in some collaborative modeling app I've built, which is based on whether the user has read-only or write permissions to the model. To nest this functionality to gate parts of the model graph would be trivial, once the server implementation was complete. Thus I struggle to understand the difference between the server/client paradigm wrt this topic.

    Obviously, but you load the client version from the server at API request time
> I'm not sure you understand my problem here

Again, I struggle to understand what the difference is between the client & server paradigms here.

> what I mean is that on the server I have a Document class, a Person class, etc. and the associated business logic for dealing with it. Maybe a document has a bunch of Revisions

Any relational data (and as discussed, associated ACLs) is transparent to deal with on the UI; the model is the data, and any exposed action gets a method that you will write to suffice its needs. If the data is somehow non-relational or requires something awkward that your server paradigm magically solves, I simply load it from an API your server exposes which can describe these things to me as it did to you and then I have those same capabilities. Have I omitted something in this case with this claim from your view?

> How do I do any of this on the client? Do I have a separate Document class that maintains all the same logic? Do I split the logic so some of it is on the client and some is on the server?

Whatever you like. Why would I prescribe this to you? I don't prescribe how you've done it on the server.

I personally create MobX stores to hold the data & relations (generally implemented using maps sending instance IDs to instances), and MobX classes at the "semantic layer" (so they might include a small part of the relational graph) and instantiate them with their instance data. As I discussed, I prefer the singleton pattern, so I do not keep multiple copies of the relational graph ever. MobX is nice because it ties the view layer updates directly to data updates, preventing unnecessary renders (fast), but the key is of course to integrate these objects with a stateful layer that will correctly trigger re-renders.

When it comes to observations like "the server needs to do integrity checks or make database calls that the client simply can't do" then it is clear you use an API at this point. While I understand you possibly have this "API call" for free as some server-sided library at the logic time of executing the action, the ease is only for you as the developer and the end-user will see identical performance between a client-side and server-rendered app in this case in terms of action completion time. In my described MobX world, things which are client-side manipulations are implemented as methods on the client-side, and of course if it is something that will require multiple server-side actions I just offload it to an API so you fall back gracefully into however you would already have done this.

Again, if I am omitting something somehow please say it, but I find it immediately clear from what you describe a trivial solution to client-siding your code would be: the exact times to make an API call are when your server code uses your nice server-library-provided capabilities, and the times it doesn't can be implemented as a client-side method.

> In an ideal world, what I want is essentially like my ORM on the server-side: a cache of every instance of every model that I've loaded from the server, normalized with references to one another and an easy way to retrieve an object with its relationships either from memory or from the database (network/API). It's then easy to update Document(id=45) in one place and have it propagate to the rest of the app, and if it's referenced by any related objects to have the update propagate to them, too

Yes, this is what I accomplish with MobX stores & classes. I have 1 copy of everything, I keep the lists of relationships on the class or it's directly query-able thru my stores (& their maps), and anything which "walks the graph" to render some related object of course actually loads the related object's class instance in order to not be stale.

> This is a hell of a lot of work to create, and basically none of the modern frameworks provide anything resembling this.

You see why my tone was uncharitable then, perhaps.

> Your final few paragraphs

I appreciate your in-depth response, I truly do, and I hope this conversation helps you understand why I hold my view and perhaps find yours difficult to understand. Any server-side-magic you use, which I truly recognize exists, is the place where API calls happen in my clients. It's really that simple, and the net result is way less or way better disguised network round trips than HTMX from the user perspective.

> Most of the time I'm creating GitHub or Wikipedia or Fastmail or something

Most of the time I'm creating what I've come to hear referred to as "dataflow editors", so I would literally have people murdering me if I had to introduce network delays to every action and could never let their happy path be lag-free. This is why I vehemently loathe HTMX and it's overbaked claims, it's a reduced user experience for anything with moderate complexity IMO.

If it would be valuable for you to see what I mean, I'm happy to create you a small React client with MobX stores & classes so you might start to play with these things. It is certainly a high-complexity process to create strong client capabilities like you and I discuss and describe, but now that I have them the act of making a new app is very much not complex, not any more than creating your servers is given that you have your libraries & capabilities of choice.

I would like to highlight that the above responses are more than uncharitable. They are somewhat condescending. It might be useful to practice a less confrontational style of online discourse.

No, that doesn't matter. If both users in the thread above still haven't realized that they're debating tabs vs spaces, they won't no matter what you tell them. Of course, tabs vs spaces debates always, 100% of the time, devolve into angrily shouting at each other, it's unavoidable.

> tabs vs spaces

> backend guy claiming clients can't dynamically handle auth by arguing server round-trips aren't ok for SPAs but totally for HTMX vs frontend guy saying not lifting as many interactions to the client makes for worse user experience

Interesting stance. Let me guess, you have never implemented a user interface?

No? I mean, I've been implementing user interfaces since Visual Basic (+/-) 5.0? What does that have to do with anything? I'm not going to argue with you guys, sorry. I'm talking meta - the way you talk between yourselves makes me want to scream and escape. Whether you're right or not really doesn't matter at all here.

EDIT: And obviously, I know I can just ignore you and move on, which is what I did. Notice that I didn't reply to your or your adversary posts, but to another person. That's mainly because I get nervous around delinquents - which is how you two sound to me...

Great takeaway, glad you stuck with the chat just to add that.

> To me this is like building a house that can never be expanded,

Or alternatively it's a good application of YAGNI.

There's always a difficult balance between that as a principle and "do work upfront as it will take you longer to do later down the line"

Everyone makes this decision based on client budget, time constraints and a host of other factors. My clients tend to prefer to save money in the short term. That's not uncommon and it's a rational position to take for many.

If you already have an app with no AJAX going on, HTMX is hugely helpful in progressively bringing it in. You just provide endpoints for the forms or other pieces of pages that need updating, and wire them up to the page with the right HTMX tags.

Even starting from scratch, it can be very productive to keep all your HTML in one place- eg Django templates- and use HTMX and a sprinkling of JS to provide client-side interaction. You can jump straight into laying your app out without building the whole thing up as platform-agnostic APIs first. When you need API endpoints you can start adding them based on the context objects you're providing your Django templates, since all the information is in there anyway. There's real benefits to putting all your presentation and most of the logic on the server.

And keep in mind that many web apps will never ever need to also be a native app.

I've used Intercooler.js (the predecessor to HTMX) a bit on top of Django and small Go apps (no framework). If you don't need a lot of complex state and interactivity on the front end it can work well. This approach avoids the complexity of having two codebases, you can get the performance/usability benefits of partial page updates while keeping 99% of the codebase in one language.

Depending on your architecture, an API or rendering HTML is an implementation detail, rather than the meat of the application. Have your business logic elsewhere and have the API or controller endpoints call out to those and just do the business of rendering JSON or HTML in the controllers.

(I think this is inversion of control, someone can correct me if I'm wrong)

can't speak for the others, because they are still stateless, but LiveView is stateful and this is the point, you're not constantly rocketing perfectly good content into the trash can; it's essentially a "content-aware cache layer":


This is a specific example of a class of problems I refer to as "Where the indirection go?" In this case, the indirection is in the form of a function that takes structured data and produces html. That function can execute in the server, or on the client. Which we can term moving a function "closer" or "further" from the client. This is easier to imagine with isomorphic javascript code, but it applies to anything (with an extra translation step as you cross the network boundary).

What you've discovered is a general property of systems that you want to keep your entropy low for as long as possible, and defer the final boost in entropy until the last possible minute. This keeps your options open. It also means publishing low entropy APIs and boosting in the client. In your case, you've correctly noted that it allows you to support different clients more easily.

There are 3 reasons to pay the price to boost entropy on the server: to intentionally make it harder to consume your API, to protect a proprietary boosting function, and because you didn't realize it was bad design.

Interesting way to frame the idea as entropy. Starting every project with a backend and SPA client seems like adding unnecessary entropy.

I'm glad we have middle-way options where we can progressively add dynamic functionality to rich-server, lower entropy applications and make an intentional decision whether to add a rich-client if the need arises.

Surely the lowest-entropy solution is plain hypermedia, no?

>you have to start from the beginning and write an API now.

You could refactor your code to share mostly everything . I have no idea how this templates system work though so maybe you need a different one.

so the API returns an array of users object as a json the html side of things would have a render function that has as input the same array of user objects. What you will probably lose versus complex frameworks is magic stuff like 2 way data bindings but from my experience those always caused issue, I prefered the old react way of doing things, it was more "functional style" and less magic under the hood and I could(and used) JS and not a special template language with special keywords to render stuff.

Amway my point is yu can reuse the API code for the html render stuff too.

> Whether or not you love or hate this model of development is irrelevant, because if at some point you need to render on a client other than a browser -- you have to start from the beginning and write an API now.

That's not true generally since many mobile apps are simply wrappers around a WebView. Rails Turbolinks basically has mobile shims that allows you to deploy to all platforms. Sure, if you can't use a Web View on your mobile app (idk, for performance reasons) that's something else. But that's not the rule.

It's funny because it's strikingly similar to .NET web forms' original (and utterly trash) implementation of AJAX.

Everything that is old is new again...

This is a baffling and backwards mentality to me. I don't want an "app" most of the time, I want a damn webpage with hypermedia.

I presume you can keep the logic code separated and being consumed by the UI generation on the server.

This way, if you only need the logic for another platform, you can just consume it directly. Perhaps you'll have an additional job to write the HTTP endpoints around it directly, but that's a quite simple job.

Honestly because sometimes it's all you need.

Take Rails Hotwire for example. It's easy to write, easy to add JS when you need it, easy to make mobile apps since they've already done the work. It's live on Basecamp and Hey.com apps, it works very nicely. Hey is easily the snappiest email client I've ever used.

because you still want server rendered html? because you focus on building websites?

Being platform specific makes it easier to have a higher quality and integration on that specific platform.

I build exclusively web apps and I still use the backend as an API.

You fetch data from the API and render it with JavaScript.

React, Vue, Svelte, Angular, Alpine, pick whatever.

Not sending HTML over server endpoints, or using templated views like ERB/EJS/EEX/etc doesn't mean you inherently have a worse quality app.

If you do this in something like Next.js with "getServerSideProps()", it's functionally equivalent.

React even went so far as to make "Server Components":


If you render everything with JavaScript then HTMX is not really going to be useful at all. It's useful for developing apps that simply don't need the sort of complex client-side interaction that those frameworks enable. The article points this out- if you want to write a SPA in React, HTMX is not going to help at all. But if you want a mostly-server-rendered page with some AJAXy interactivity, HTMX is much much easier than trying to shoehorn in some client-side rendering just for the AJAXy bits.

The main thing is is that there is still a substantial demand for tools that let you mostly avoid writing lots of JavaScript, whether on the server or the client.

This is a super valid answer, thanks.

Psychological bubble from the sorts of apps I build I suppose, I hadn't considered:

  > It's useful for developing apps that simply don't need the sort of complex client-side interaction that those frameworks enable.

I feel like I gotta call out the LiveView is a bit in a class of its own. HEEx is rendered on the server, but the diff sent over the wire is the absolute minimal diff it can be with a map of where it in the DOM. Often this is just text. LiveView also gives you a running process on the server (a virtual machine process, not a linux process) that holds the user's state (like if they're logged in, for example) and makes things like "who's online?" and other concurrent features almost trivial.

You build web apps. The parent talks about web sites.

Not everything on the web is a full featured interactive application, this is exactly what they're talking about.

People need to learn to make their own mistakes. It's an anti-pattern. Just ignore it.

After reading some HTMX criticism, there's one point people seem to miss. Making HTML your application interface does *not* prevent you from having a JSON (or something else) API. If anything, it probably forces you to split your functions better. e.g:

  def get_user_data(user_id: int) -> Dict (JSON-looking data):

  def render_user_view(user_id: int) -> HTML:
      user_data = get_user_data(user_id)
      render_template("user_detail_view.html", context=user_data)
If you need the user data in a JSON API, nothing prevents you from exposing `get_user_data` as a JSON endpoint. You can also use WebViews in a mobile app.

People tend to overestimate the "interactivity" needs of their apps and underestimate what they can achieve by just swapping HTML. HTMX also lets you swap "Out of Band" [0]. This makes it easy to model more complex interactions (like "reactions"), for example, updating a counter somewhere else in the app when a form is submitted. Reactive frameworks can also become a Rube Goldberg machine if an app is not properly designed from the beginning. Then you start fighting rendering loops, build dependencies, components' side effects, etc.

Personally speaking, HTML-driven apps are not just about easy vs. hard development, it's also about your users [1]. Maybe a big React app runs fine on 8 CPU cores and 32 GB of RAM, but very often, your users just want to read some content, maybe submit a few forms, and leave. They may not want to download 2 MB of JS so that the page can render boxes with text, even more if your browser can already do that if you give it some HTML.

[0] https://htmx.org/attributes/hx-swap-oob/ [1] https://shkspr.mobi/blog/2021/01/the-unreasonable-effectiven...

Big +1 on all of this. I have terrible rural internet, and just recently tried developing my first app with HTMX (wasn't aware of Alpine.js), and _man_ is it fast. For city slickers with their symmetrical gigabit connections it may be unnoticeable, unfortunately. Not saying SPAs have to be bloated, it just seems like most sites that turn into SPAs are bloated.

All that said, trying to push everything server-side if you've been working in a heavy client takes some getting used to. In my real life job I've seen feature flags shipped as an API so the client queries whether a feature flag is enabled - this is something that always struck me as silly, the server serving the front end already knows if the feature flag is enabled. While that might be justifiable in some cases, it is definitely not so much in the on-prem-only product I worked on.

Not only in rural areas. I was commuting for 3 years and i had to deal with the German network coverage every day. Every single time i though "great, i can start reading", the page went blank, because it couldn't load "click_my_fancy_newsletterbox.js" in time. That's why i try to make sure, that my crappy hobby project is also able to load without fancy js.

I've been in exactly the same boat, and I feel like anyone could be if they run out of mobile data. When I get to the point where even HN won't load, I know it's time to put the phone away. The other one that really gets my goat is the page looking like it has loaded, going to click on something, and some frontend nonsense loads the instant before clicking and now I'm off to an ad or a completely different page than intended.

Haven't used django for a year or 2 but used to use django-rest-framework's negotiation to get both json and html responses (was using inertiajs not htmx) and it worked pretty well. You can use decorators on the api methods to set things like templates. Here's an example:

  def get_users(request,  **kwargs):
    // ...
    return Response(data={"users": users})
This can return JSON or HTML with the data injected.

THIS! So well put, especially the functional separation aspect.

I agree that the way modern JS (need to?) download half the Internet for a semi-useful app is annoying, wasteful and dangerous.

However, I still remember the hell that was updating the state and keeping the rendering in sync with it when using jQuery. Native JS of course doesn't improve on that, and (judging by the description - no experience with it) Alpine.js doesn't either. For me, the paradigm of rendering from the internal state is what makes React (and Vue,...) useful. Too bad that npm ecosystem sucks and that everyone is inventing new shiny useless new things all the time... But React makes non-trivial single-page apps possible.

Of course, if you need server-side rendering (for example because of SEO) then the equation changes, and maybe using Alpine.js makes sense.

That internal state is a burden on small teams though - you're efectively implementing the persistence layer twice. I'm totally onboard for SPAs as an option when breaking down an organization, or for solving a particular kind of problem.

But they're just not worth the effort for a form-oriented website with a small-medium team behind it. Almost everything about a web-app is harder with an SPA.

> However, I still remember the hell that was updating the state and keeping the rendering in sync with it when using jQuery. Native JS of course doesn't improve on that

I think this varied a lot depending on how teams approached it and that a lot of the appeal to something like React, Vue, etc. was simply that there was one way to do it.

The approach I've been quite happy with is to use JavaScript classes (IE11 is less than 1% of global web usage) for internal state, where you follow standard practice for having ownership and updates between different parts of your app, and the HTML5 data APIs for the public elements which are managed by different codebases. Web Components has something like 95-97% availability these days but this can be as simple as using element.dataset.whatever as needed. React is definitely good for huge apps but a significant fraction of the web sites I use are solidly in the level of functionality which in 2021 doesn't need functionality outside of what a modern browser provides.

Keeping all state server side is a completely reasonable approach. The 37Signals guys have been advocating for it since 2006 or so and as long as you have a low latency connection to the server then it usually works great.

What doesn't work well (and what tended to happen with jQuery apps) is having state both on the client and on the server and having to coordinate between the two. My personal preference is for client side state with a lighter setup than React (i.e. Svelte). I like the predictable latency and I feel you don't run into a complexity barrier (e.g. multi-step forms) as the app grows.

> Alpine.js doesn't either

While it won’t fit the needs of a complex app with dozens of components, it does drive the UI from the state. In fact, it uses Vue’s reactivity engine.

I was very good at making reliable apps and managing states manually in the jquery days. Having a scaffolding helps overall and in many ways, but now in the dense jungle that is the JS "ecosystem", there is sometimes a productivity loss searching for "the right way to do X in framework Y" or troubleshooting underlying tools instead of just writing the code to do the thing. Easier, maybe, but not at all satisfying.

I think the trick is, that state needs to be carefully considered at an overall systems level, not just client vs server. That's quite hard, so many (most?) devs don't do it. Certainly, I've seen state managed well using jquery, and even vanilla JS in the ES5 era, so frameworks don't make it possible, it's always been so.

So the question is, do frameworks make it easier? I don't think they really do. I think they make it easier to manipulate state, but not designing your system will still mean state being mismanaged.

I've been moving from Django to Elixir/Phoenix/LiveView and loving it so far. I hated the Angular/React era of the web and mostly moved to doing backend development, but the Live App era has reinvigorated my interest in web development as a whole. I'll miss Python a lot and hope they can come up with something that solves the concurrency issues, but Elixir is really pretty good in its own right.

Can you explain what you mean by “Live App” era vs React era? Can you point towards resources which might disambiguate the two?

Sure! Basically, the idea is that rather than requests in JavaScript going through an API, each user connection is maintained by a lightweight process on the server (which Elixir can scale easily), and then interactions with the page are pushed as a diff via WebSockets and then magically merged into the DOM. Interactive pages, no JavaScript required. I'm still pretty new to it, but after learning the ropes it's seeming very productive, and most importantly, fun.


So pretty much the Blazor Server approach in the Dot Net world then?

Blazor Server and Blazor WebAssembly needs to be marketed separately. Most people have heard about Blazor WebAssembly but are unaware that LiveView-style web development is possible in the .Net world using Blazor Server.

Yes, yes they do. I don't know why you'd name pretty much the exact opposite techs the exact same thing.

I don't use Blazor, however when I went to get basic info about it every article I have read were very clear that there's 2 ways to dev a Blazor app.

It's also not unlike JSF in the Java world, which went out of fashion 10 or so years ago, and for good reason: relying on a framework to paper over the distinction between client and server side turned out to be much more complicated and fragile than a cleanly defined separation. I haven't seen any authors or users of these recently developed server-side-first frameworks discuss this prior art, but I'd be interested to see if they've come up with a way around what seemed like a fundamental problem in the approach.

As former JSF framework tech lead built on top of Richfaces, the major problem with JSF was its lifecycle model and the fact that Sun et al only defined the basic infrastructure and lead to a forest of frameworks that could hardly interoperate among themselves.

To the point that on my follow up project I ended up recomending for JSP with tag libraries instead, regardless of Sun advising them as deprecated and replaced by JSF.

WebForms never felt as complex as JSF.

I feel JSP like models with component libraries, or MVC are more closer approaches to the whole Web programming model.

Yes, basically. Fun fact: Blazor and LiveView were developed independently, around the same time as each other! Just two similar implementations in disparate communities.

Guess that was due to browser-land innovations that made this a possibility.

So then this can never work offline like a single page React PWA could then I guess? For me after seeing offline-first PWAs it's hard to want to go back to building stuff that's only available online, but I guess everyone's use-case is different.

Not saying offline-first apps have no interest, they totally can have some.

But in more than ten years, offline mode have never been a requirement on any of the projects I worked on.

What I’m saying is that offline mode is just a feature like any other and it’s one with a huge cost in architecture complexity.

And, although it’s just my own opinion, I think if you want to be offline first, odds are that better stacks than the web exists for those needs.

what is your use case of offline ? I've never seen the point. For example what the purpose of a social offline app ? I can't refresh feed, I can't upload post or react to any thing in my feed.

The only use case I know is for map where you can download map so when you don't have 4G you will not be lost but that's the only use case I know.

We made a pwa for our sales team. Globally they visit millions of stores everyday and more than 80% stores are in poor or no internet area at all.

They need to make sales order, survey, etc while offline and sync with our server whenever they get internet connection.

Yes! I think it's hard for people in the 1st world to imagine that not everyone can get data whenever they want. In one country even YouTube added a feature for downloading and saving videos for offline viewing because they knew that internet was often slow or unreliable.

i'm currently thinking about building something similar - do you mind sharing more about what tech stack you used, esp. how you handled the offline data and syncing?

Was PouchDB / CouchDB used? I've found that it works marvelously for no-hassle syncing.

Sorry very late reply.

But yes we use pouch+couch to one way sync from our server to thousands device.

And from our sales force device to our server while we still save the data in pouch, we choose to use pwa's service worker to send directly to postgresql instead of pouchdb-couchdb sync.

A dictionary that works offline (and updates when online), book-like educational reference apps that people can access when they don't have data or if they want to turn their data off to study and not be distracted by notifications. There are tons of things that people want to download/install on their phone as traditional apps, the PWA just makes it cross-platform and available for all.

Working on stuff, not communicating. e.g. Google docs, Photopea.

OP probably talking about Phoenix's "LiveView" (https://www.phoenixframework.org/ check the video about it).

Phoenix LiveView is a framework where it keeps websocket open with the client and renders DOM changes server side and passes it to the client [0]. Thus with fully server side development without any JS you can have almost full SPA experience.

[0] Have no experience with it, and only read about it some time ago, so don't judge me on the details, but the gist of the "LiveView" idea should be like that.

But you still need JS to update the DOM and handle the websocket stuff, right? Or is this all somehow done with JS disabled?

It’s done with JS but that’s all written for you. But as a bonus, LiveView apps do work without JS (they just aren’t updated over web sockets anymore).

I assume you have to specifically design your site to work with both the request/response model and the LiveView model in order for this to actually work, as opposed to LiveView being able to plug that hole automatically for you.

To be a little more concrete, here's an example:

  <%= live_patch "about", to: Routes.about_path(@socket, :index) %>
which creates:

  <a href="/about" data-phx-link="patch" data-phx-link-state="push">about</a>
The data attributes are read by the LiveView HTML and are otherwise ignored by a JSless broswer.

Edited to fix glaring code sample error.

You can design singularly for LiveView and it handles everything. But if you want both request/response and LiveView (e.g. to mix the two or fallback to r/r when no js) you have to be more explicit in your design. It’s mostly trivial though. I have authentication pages that use the old controllers but pages that use only LiveView without any hassle.

Nope, all you need to do is make sure that everything has a route (which is very much encouraged in LiveView) and it works.

Right. It uses JS but you don’t write any. Or very little. Or in a custom DSL/framework.

Important distinction, as it wouldn't work when JS is disabled in browsers.

I don't see the point of going back to full server side, you have to scale more with more users.

The only benefit would be if it all worked without running JS in browsers, but it doesn't.

> as it wouldn't work when JS is disabled in browsers.

You can make it work when JS is disabled as well, you fall back to rendering regular HTML. It does require a little extra work, but it’s not insurmountable (e.g. using @conn instead of @socket).

>you have to scale more with more users

I might opt for additional optimizations once it gets bigger, but I’m not too worried about scaling Erlang processes.

Yup, this is my thinking as well. If I go back to fully server side, it's because I can somehow instruct the browser to update without JS involved.

Don't you have to scale your api too ?

I have (limited) experience scaling long-lived websocket connections and it _sucked_. It is _way_ easier to scale out little Node servers that are "stateless" than it is to ensure that Client A is connected to Socket A.

I would much rather scale out my REST/Graph/RPC API instead of having to scale out a WS API.

Yes, but now you have to scale both the data layer logic and view layer logic when doing server side rendering.

So now it's two times the hassle, two times the money spent

99% of the time no one runs into scaling issues and worrying about it is premature optimisation. I have to remind myself that all the time.

And no, same hassle, same money spent. Thought about from the start server-side rendered pages are almost as cacheable as API responses will be. If you can't cache you're in for a world of expense at scale whichever way you go.

The term “era” in this context has to be interpreted as a personal perspective to not be confusing. The mentioned paradigm is not new nor does it replace/evolve from React and similar.

There's a thriving ecosystem these days around the ASGI standard for building concurrent web applications in Python - it came out of the Django project originally but Starlette and FastAPI are the most widely used implementations of it.

ASGI is a bad solution to the concurrency issues as the ecosystem is heavily centralised around asyncio (which is ``java.lang.Thread`` for async/await, with all the pitfalls included).

Could you elaborate on what you mean by that? I can think of two interpretations, both wrong:

1. Asyncio is like java.lang.Thread in that it's very low level; which isn't accurate: it's totally acceptable and easy to write async concurrency using asyncio (the python async stdlib) directly. While frameworks like Trio are nice they're nowhere near as necessary when working async as thread/pool managers are in Java. 2. Asyncio is like java.lang.Thread in that it has similar pitfalls to threaded programming in general. This is also untrue: in async programming in general, race conditions are much harder to write, and concurrent modification issues aren't a risk at all in Python async code the way they are in threaded Python or Java.

Having recently looked into Blazor Server (similar to LiveView but in C#), one of the cons Microsoft listed was the scalability (compared to a typical SPA) as each client now needs an active websocket connection to the server, which will require more server resources.

Do you have any experience in that? I'd love to know where server resource requirements sit between SPA, this and a typical SSR site like Django.

The thing that elevates Phoenix in this regard is since it’s built on Elixir (and consequently the BEAM), adding a new “process” to hold the web socket in memory is trivial and basically comes for free out of the box.

The Phoenix core team has demonstrated over a million (maybe it was even 5?) simultaneous connections running on one server all being updated at once. Granted, it was a proof of concept, but in the real world, you would load balance well before that in all likelihood. The point is the same though. BEAM languages can easily manage a hundred thousand nodes of state simultaneously without breaking a sweat. For most apps, this is plenty as demonstrated by discord, WhatsApp, and other BEAM focused tech companies.

I haven't done frontend work for a few years, but Angular was really a revelation to me when it first came out. For all the headaches, it made it practical to do stuff that was really cool.

Another one to consider is https://www.django-unicorn.com if you want that LiveView feeling for Django.

For my latest project[1], I've opted for https://unpoly.com instead of Alipine+htmx as Unpoly allows me to write 100% server-side code and sprinkle some progressive enhancement where desirable. As a result, I can offer a no-JS experience (minus the Stripe Checkout) for those who may want/need it. Additionally, it forces me to focus solely on the server-side, and I write more idiomatic Django code as a result.

[1]: https://heraldsms.com


Sending an AJAX request every time you enter characters just to count them. Ewwww.

Howdy, creator of django-unicorn here. Some of the UI-specific examples are a little contrived and wouldn't be what I consider a really great idea, but it's more to show the possibilities, i.e. instead of counting characters, you could do a heavy ML task to analyze the text.

Personally, I mostly use Unicorn as a component library to split up functionality from the view code, and as a way to persist data to the backend (which would already be an AJAX call) without the hassle of creating an API, dealing with serializers, etc.

I guess that's the extreme end of no-js. Wanting to use compute, but philosophically sticking-to-your-guns on where that compute happens.

Well it's still using JS to send the AJAX request, so in-fact something along the lines of 'on text change, set some element text to text.length' is a lot less JavaScript code than 'on text change, format and POST an AJAX request and then set the element text to a value within the response'.

The difference being that the code to send an Ajax request an update the DOM based on the response is already coded and already used in other places, whereas updating the text length client-side would have to be custom code. Not that I'm arguing one way or another, but the reason it's done that way is to reuse the same set of primitives for everything.

I've been looking at these libraries and Unpoly looks the most promising for my needs. HTMX looks good as well but Unpoly's infinite layers feature [1] rather grabbed my attention :)

Stimulus is the one I can't get myself to like. Separate JS controllers is clever but the DSL and general nature - it just doesn't click with me.

[1] https://unpoly.com/up.layer

Don't use a framework, use three!

This sounds like a lot of effort just to brag about how you avoided whatever piece of tech we're considering to be evil this month.

I did web development work in the early 2000s (PHP, MySQL) and briefly in 2013-2014. In the past few years, I explored a recent version of Angular, React, and some other framework.

The tooling seemed really nice at first, but I was actually shocked at the number of dependencies and just the overall complexity with all the layers of abstractions.

Fundamentally, these are still websites. I honestly see this as people getting bored, inventing new things to deal with their boredom, and the rest of the industry follows.

Meanwhile, the services I was writing in Java like 10 years ago haven’t really changed. We agreed Spring sucked and moved on. At a FANG where I work, we use Java and Kotlin. A lot of my data crunching jobs are in Scala. We did start using Rust for shared libraries deployed on mobile operating systems… because the entire team preferred it over C++.

But I come back to web development and there’s always a new flavor of solving the same problem. /shrug

I do not understand it too.

The cost due to the complexity is at least 10 times more.

It feels strange to me that nobody in the hierarchy consider it.

I do understand that developer like to play with new technology and don't care about the cost as long as they can play with it and get paid but the hierarchy must have seen the millions dollars lost. Why did they do not say something about it ?

I met a guy at a bar in San Jose who worked at a web startup in the early 2010's. He said they easily spent over one million in UI/UX resources just to animate form validation because the default browser validation wasn't cool enough for their brand.

This is unfair. HTMX and Alpine could hardly be considered frameworks.

React, by comparison, is absolutely massive. It's complicated. It requires compilation, and often a complex build configuration. You still need a backend server, so it won't obviate Django.

Your comment speaks to a larger debate, but it's a rather disappointing contribution.

> [React] requires compilation

technically, but not in the same way e.g. Svelte does. the syntax used by React, JSX, is simple local syntactic sugar:

  <Foo x={a} y={b}>...</Foo>
  // ↓
  createElement(Foo, { x: a, y: b }, ...)
i.e. basically a macro, no real "compiling" going on

If you need to run it through a "transform" step, in this context, it's compiling. What it ultimately gets transformed to, and the implications of that, is a different discussion

alpine has one dependency and htmx is dependency-free, and both are very small[1][2]

so it is a very lightweight combination on top of plain-ol' HTML

[1] - https://bundlephobia.com/package/alpinejs@3.5.2 [2] - https://bundlephobia.com/package/htmx.org@1.6.1

One major downside to HTMX is that as soon as there is any state you have to keep track of, you're teleported back to the late 90s or early 2000s where you have to keep track of all your state in hidden form fields, and you're extremely constrained in how you update pages.

IMO, it's annoying enough that it's probably not worth it unless you're doing something trivial. If you want to render html on the server you can still do that, but in many cases it is easier to just use custom javascript and maybe even receive the rendered html as json to simply updating pages rather than use htmx.

Yeah, those damned early 2000s when back/forward buttons worked perfectly and we didn't lose the entire rich client state upon page refresh or wireless internet hiccup. Thank god we have a lot of JavaScript libraries and ambitious programmers to save us from those trivial things :)

But those were actual problems back then, and they were solved only through significant engineering work to hide them, each of which had their own problems.

Frameworks like .Net WebForms were created to abstract and hide a lot of this from developers, with things like long encoded strings of "view state" that would be re-posted on each request, by serializing things to the URL, or by saving them to Cookie-backed sessions.

But each of these has issues, from privacy concerns using the URL, to security concerns with the "view state", to concurrency and integrity concerns with sessions.

IMO, the biggest reasons these didn't stand out as huge problems in the early 2000's was because:

(a) we didn't use the web like we do today, and our expectations were very different in how things would work if we, for example, added an item to a shopping cart

(b) most heavily interactive / app-like things that more closely work "the way we expect" today, were either still done as desktop apps (like business software) or were implemented as Java applets, Flash, or something else of the sort

(c) in the rare cases that neither (a) nor (b) applied, it was because some very good engineers put in a lot of work to make it work that way.

You mean when a page refresh asked us if it wanted to resubmit a form?

We did this standardized simple trick to prevent this: https://en.m.wikipedia.org/wiki/Post/Redirect/Get

I'm new to htmx and alpine, but isn't alpine's global store what you'd want in this case? https://alpinejs.dev/magics/store

(I'm not very familiar with Alpine and htmx but I'm comfortable with Vue and React.)

Htmx pulls html content from the server and replaces inner|outerHTML with it. A JSON object (of the type React/Vue and presumably Alpine use) isn't a good fit to store html blobs and state.

If I use alpine.js why do I need htmx still? I would use either of them but not both. Using both seems making a supposed-to-be-simple approach immediately back to complicated-again-now-you-need-two-components.

alpine increases client side interactivity, it can also do ajax to talk with server, why do I still need htmx then?

On the other hand if I use htmx I will probably use its hyperscript for interactivity on the client side, to be 'consistent'.

Note both projects are trying to make frontend simpler, mixing them seems not to help that goal to me.

The pattern is that you use htmx for anything it can do, then use Alpine for things that shouldn't/can't require a server round-trip (toggling sidebar, animations etc).

You can reimplement htmx functionality with Alpine, but htmx is a lot better than `@click="fetch('/partial').then(res => res.text()).then(frag => $el.innerHTML = frag)"` all over the place (with a slight variation in that one component that you miss because of the noise).

Don't use _hyperscript just to be "'`consistent`'", use it because you want to. Being agnostic and playing well with other tools is a big thing for htmx. _hyperscript is lots of fun and lets you write very readable code, but you also need to stomach a 26mb bundle just to toggle some classes :/

(if anyone reading this can help us make it smaller, LMK)

> 26mb

ackshully, 21.2 kB:


Which compares pretty well w/ alpine, which comes in at 21.7kB with its dependency:

https://bundlephobia.com/package/alpinejs@3.5.2 - 17kB https://bundlephobia.com/package/@vue/reactivity@3.2.22 - 4.7kB


htmx is for syncing client side actions with the server effectively within the hypermedia model

AlpineJS (and hyperscript) are better for purely front end needs.

htmx and Alpine pair well together, as does htmx/jQuery, htmx/VanillaJS or, if you are brave, htmx/hyperscript

I don't think Alpine has any similar AJAX directives as HTMX.

that's the only piece messing indeed, alpine.js is following vue.js, both can use axios for ajax needs.

I honestly still don't see the point of htmx, to me alpine.js + axios can cover all the front needs, assuming backend returns json instead of html.

htmx is useful for django etc when it's returning html though, but new frameworks are more likely to return json instead.

'django saas startup kit' concept of the author is really smart

there's a hole in the market for people wanting to use frameworks to make saas sites -- frameworks provide great primitives that are subtly unusable for some modern web apps. Login but not saml, admin but not multitenant, forms but not multiple forms on a page

feels like this scratches an increasingly common itch of 'the web as a platform isn't shaped like what people use the web for'

100% agree. Frameworks are an incredible starting place and allow everyone to get a head start. But there's still a big gap between what a framework will cover versus what a SaaS app needs.

Also boilerplates occupy a very important world between no-code and code-everything-yourself. In a no-code world you're fully at the mercy of whatever options someone has provided you with. But starting from a boilerplate allows you to dive in and completely customize things how you need to, without having to worry about the provided abstractions. As a developer the interface I want to use is code, but few products provide that.

'progressive enhancement of boilerplate'

An alternative that requires even less server-side work is to use PJAX https://www.npmjs.com/package/pjax which is the spiritual successor to Turbolinks from the early Rails days. It's really a two-step process:

- create a quick JS file that loads and configures PJAX to automatically intercept links and - wherever you would have $(function() {}) ensure that it's also triggered on the PJAX load event, and is idempotent (e.g. check if you initialized a component before initializing it again)

Then you just render pages server-side like it's 2000, without any pjax-specific code needed in your templates themselves.

If you're building a data-focused product either solo or with a small team, it allows you to focus on your Python code, and anything frontend is as easy as accessing the variable in a Django template. Of course, it's a massive accumulation of technical debt - when you're successful you'll need to rewrite in a modern framework for maintainability and agility. But when you need to "punch above your weight class" and just get a product out into the world, it can be an amazing technique.

pjax (first released 8 years ago) preceeded turbolinks (6 years old), and was the inspiration for it

htmx is an attempt to drive HTML forward as a hypermedia, so it exposes explicit, client-side attributes, rather than hiding them as with pjax/turbolinks, although there is an attribute, hx-boost, that acts similar to them both

worth noting that intercooler.js, which was the predecessor for htmx, is 8 years old as well

I stand corrected on the timeline!

Since when is Django not a modern framework?

I read that as a modern JavaScript framework. I think there's definitely a good debate about what “modern” means in that case because I think it's less about “modern” and more along the lines of “suitable for a large team working on complex client-side functionality”. Reaching for that prematurely can add a massive toolchain to your critical path, which adds a lot of frictional cost you might not see sufficient benefit from.

One of the challenges we have is that people tend to hear about practices from very large tech companies because they have enough people to have things like developer evangelists and time to write lengthy blog series or produce videos. That often leaves people without the nuance that some of the tradeoffs make more sense when you have 50 developers including a dedicated support group but smaller teams might not make back the cost of entry. I like posts like this for reminding people that there are other options which might be appropriate for different projects, especially since even at large organizations it's usually easy to find small projects.

I figured they meant js frameworks. This is definitely my slanted point of view but I rarely consider anything JavaScript modern. Django 4 seems much more so

A suggestion for folks writing articles like this - sell me the advanced but typical example. I want to see an image input which is then previewed and then uploaded asynchronously with a loading bar and error states.

Here you go. Disclaimer: I've only implemented part of this personally because my use case didn't need previews.


Ah Phoenix, how is the package ecosystem these days? I've always hesitated because the Rails ecosystem of gems saves a lot of time.

I'm pretty inexperienced, my general feeling is that it's been good enough for me so far. For example, I found good Excel writing and job scheduling libraries. Definitely not as good as gems though

For people who want to see more advanced examples/tutorials built using Django and htmx- you can see a bunch here: https://htmx-django.com/

Anything that can be done with React/Vue can be done with htmx in a more “Django-way”- it’s an amazing library that allows for complete web applications without the complexity of the JS ecosystem

All I see there is someone using <span>s as fake <a>s.

cool, right? You don't have to do this with htmx, but you can if you want too and I did because I wanted too :)

Keep an open mind and continue reading on

Fake links break normal link behavior. Accessibility issues, opening links in new tabs, etc.

So cool! I love being able to break accessibility and the fundamental building block of the web :)

Requiring 28 kB of javascript in order to be able to click on navigation links might not be the alternative to the sins of the JS ecosystem I expected when following this thread.

Of course, that fact alone doesn't undermine the merits of the subject matter. But I'd seriously consider it a presentation issue when doing evangelism.

>because I wanted too

Why though? I see this sometimes even on mainstream sites and I always wonder what the idea is.

Not sure about the snippet:

    // when the DOM is loaded
    document.addEventListener('DOMContentLoaded', () => {
      // find every element with the class "close"
      (document.querySelectorAll('.close') || []).forEach((closeButton) => {
        const parent = closeButton.parentNode;
        // and add a "click" event listener
        closeButton.addEventListener('click', () => {
          // that removes the button's parent from the DOM 
It'd be clearer without useless comments and useless extra code:

    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('.close').forEach((closeButton) => {
            closeButton.addEventListener('click', (event) => {

It's worth noting the security tradeoffs of these micro-frameworks.

HTMLX uses innerHTML/outerHTML extensively, meaning that XSS is a real concern. Any sanitation of user-generated content must happen server-side. This how traditional server-side frameworks generally work, but it's the opposite of how sanitation tends to be handled in large JS frameworks such as Angular.


Alpine.js requires an alternative syntax to avoid running afoul of unsafe-eval Content-Security Policy. With this more verbose syntax, there no inline expressions in templates; everything is bound to attributes in Alpine.data instead.


> HTMLX uses innerHTML/outerHTML extensively, meaning that XSS is a real concern.

It's the same kind of a concern as having a user input inside HTML served from a server in the first place. And typical backend frameworks people would use with HTMX are already good at mitigating XSS inside served HTML.

> Any sanitation of user-generated content must happen server-side.

Um, yes? You should treat every request to hit the backend as a potential attack, otherwise you're going to have a _really_ bad time at some point.

> it's the opposite of how sanitation tends to be handled in large JS frameworks such as Angular.

But they still do server side input validation, right? Otherwise, that would be a security concern.

The thing about server side input validation is that if you miss something, that exploit is stored in your database. Mitigating issues you missed thus requires a backfill, which may not be scalable.

So instead you sanitize any user input just before every place it’s processed. Escaped in SQL, escaped on the frontend, escaped in email mailers, etc. This philosophy means that that you can’t use things like innerHTML on the frontend at all because the server output might contain XSS, and frontend frameworks don’t escape content added to the page via direct DOM manipulation.

> Mitigating issues you missed thus requires a backfill, which may not be scalable.

My understanding of this is that you leave data that is effectively an exploit in your database, and rely on the rigor of everyone using that data to validate it correctly? Have you had to do this in real life - can you provide an example?

I guess I've seen something like "<b>This is bold</b>" rendered after someone has fixed a bug, but I've also seen that rendered as bold text because it wasn't fixed everywhere (which is terrifying to me, knowingly not fixing malicious data).

Can anyone describe the use cases between HTMX/Alpine and Unpoly JS? I see the 3 routinely mentioned, but I am unsure what coverage Unpoly has vs HTMX/Alpine.


From my point of view, Unpoly is just more "full stack" than the others. You have the functionality of several of the others in one single package, plus awesome things such as automatic loading indicator (turbolinks style), very good error handling, modals, layers, popups, sidebars, slow network detection, caching, eventbus, server provided instructions via custom http headers, polling, keeping elements across page transitions, compilers (kind of "stimulus" for creating custom behavior based on html attributes), etc. And all of it in an awesome and very clear API: It's all custom and data attributes on standard elements.

HTMX, in my opinion, has a smaller scope. Of course you can do everything, but you'll have to do more yourself.

Alpine seems to be even more low level, kind of a lightweight Vue. I don't like it, it really turns me off adding code in html attributes.

I wrote about my experience evaluating the same stack here: https://www.joseferben.com/posts/hoarddit_a_website_to_disco...

would it possible to have a browser ship with htmx so that it is possible to have truly "javascript optional"? (in principle this applies to any full js framework but I suppose it is natural in the htmx context)

are there any unsurmountable issues around security and the sandbox etc (not terribly familiar with browser internals)

In general there are good reasons not to do that. It's much easier to ship a tiny dependency-free JS library than it is to ship a browser.

Most of what htmx does is a direct extension of the browser's capabilities. Some issues I can think of are

- maintaining the "accessible by default" nature of HTML

- swapping. htmx takes html from the server and uses one of innerHTML, outerHTML and insertAdjacentHTML to put it into the document, but it also does a whole lot more to avoid unexpected behavior. This would all need to be specified and implemented natively

I think it's great people who passionately dislike JavaScript have such powerful options.

Personally, I am not buying the whole "you can do anything this way" because it seems to me the main driver, implied or plainly stated, is always the "and no js!" part.

I get it, though. We are all capable of going to great lengths to prove a point. Having more viable options is great.

It's not necessarily about disliking JavaScript. I can see Node people using this as well, why wouldn't they? Not everyone wants or needs to double their app code by building both a server app and a SPA. I might be exaggerating by "double" but it's for sure a major increase of code. Github is doing fine without a SPA last I heard.

It reminds me a bit of [MooTools Behavior](https://mootools.net/blog/2011/12/20/mootools-behavior) (from 2011!) where interactivity was created by adding tags to HTML, similar to the article!

And dojo/dijit. I cut my web teeth on dojo back in the day and still feel like it was better than the things that ultimately supplanted it.

Sorry if this is off topic, I remember reading a proposal of including something similar to Alpine.js / HTMX within the HTML5 spec.

But I lost the page. And the proposal itself doesn't mention Alpine.js or HTMX so no keyword bring it up in Google. I am wondering if anyone have any idea.

Downside of most HTMX examples (such as the form in TFA) seems to be lack of graceful degradation with non-JS-enabled user agents.

But then, the rest of the page is server-generated (taking care of SEO), and handling interactivity without JS may not be a priority for most sites nowadays.

progressive enhancement is possible with htmx (e.g. the hx-boost attribute is p/e friendly) but often requires thought by the programmer

for example, the active search example can be made p/e'd by wrapping the input with a form that submits to the same page:


but it requires work by the end developer

the idea here is to be pragmatic: obviously some people care deeply about p/e and are unwilling to sacrifice it for more advanced U/X, but others are willing to do so, so let's make it possible for both camps to improve their sites with the tool

What makes it 'modern'?

It's required to call something 'modern' to get on HN.

I always have the fear that something I write will be too complicated for someone else. This has become particularly irksome when integrating OIDC into a single page app. It’s gut wrenching to double check every random header for every domain it needs to serve, to set up SSL for localhost, to include the right headers on the fetch request, and maybe edit the /etc/hosts file to make it work. That process is a joy killer. The tools mentioned here can help stop that madness and bring joy back into development. So thank you! Please evangelize these ideas to browser standards committees and whoever will listen.

This is a pretty cool idea. The only catch is retaining consistent state across the page when there are multiple components that depend on same data. For instance, if there’s a form that submits to increment or decrement some dataset, but there is a stat based on that data in the sidebar, then it’s nearly impossible to keep the stat relevant. That said, its a planning problem than a stack problem.

I’ve been trying this in all my hobby projects and am absolutely hooked. It feels simpler than even django-sockpuppet, say.

I’m not sure I understand the motivation to avoid JavaScript. I completely understand not wanting 400mb of SPA/NPM nonsense, but otherwise what problem does avoiding JS solve in a webpage?

* Is it because JavaScript is too hard to learn or too unpleasant to write?

* Is it because training people to write JavaScript is too much effort?

* Is it due to a lack of trust in your fellow developers?

* Is it due to challenges in planning or forming architecture?

* Something else?

I find that relying on JS in all browsers, on all platforms, over a long period of time is guaranteed to cause you pain.

I have ~16 years of experience. My old PHP websites have advanced seriously, and are more reliable that my Node based ones. The tooling changes in node, the platform changes, the capabilities of the user agent (the iPhone's per tab memory !== that of a virtual browser on AWS), the restrictions of the system, the assumed tools and tool rot.

Similarly, as Angular was replaced by React by Vue by Svelte in developer mindset, and the skill set of those coming in changed, as the versions of each of those platforms became less and less compatible; I have pages in Mootools that are just dead weight. But even if I had PHP3 pages, they would be humming along with minor changes....

Not to mention the security issues the JS community faces relative to eg. PHP, and you can bet that HTMX definitely appeals to me for anything that I don't need to do in the browser.

Can this stack easily handle 2-way data bindings on the frontend? For example, when you update a text input, the title of the text input changes?

Yes. You give ids to elements and the html indicates which id to replace with which content. You can even replace ids in other sections (might get complex quickly, but it works).


Source: I used this to implement my https://fromzerotofullstack.com website.

purely front end use cases like this would be handled by alpine

syncing state with the back end would be handled by htmx

Alpine should be able to do this by itself.

Smells like Turbo (and related frameworks) to me, which isn't a bad thing. From my brief use of CRA, I can see why many people are attracted to it, but the fact that a bunch of JS has to download before anything happens is never far from my mind.

In the meantime, I'll keep waiting until we get native HTML imports/includes - hopefully before the heat death of the Universe.

There is a cycle that has existed for a while:

- Home computer.

- Mainframe. Much more powerful and reliable.

- More powerful PC. Mainframes have latency et all.

- More powerful Server, browser cannot handle that much computing.

- More powerful browser. A whole OS really. Servers have latency.

- More powerful server.. All that complexity is hard to rebuild on the client side, and besides phones are too slow...

Party on!


This is a very promising architecture! We are actually building a template for Kit55 (http://stack55.com) that illustrates the use of Alpine,js. I think we should look into HTMX as well. Do you know if there is a comparison of Alpine.js vs HTMX somewhere?

From a sibling comment:

htmx is for syncing client side actions with the server effectively within the hypermedia model

AlpineJS (and hyperscript) are better for purely front end needs.

htmx and Alpine pair well together, as does htmx/jQuery, htmx/VanillaJS or, if you are brave, htmx/hyperscript

I'm not sure! As I say in the article, I kind of think of HTMX as "alpine for AJAX"

I'm a big fan of these setups but has anybody ever successfully used a minimal-JS setup for anything useful?

I mean, there's apps like Pinboard but React & Redux do handle a lot of complexity and most modern apps demand that complexity.

People have done plenty of useful things with htmx, just like people did (and some continue to do) useful things with links & forms.

Here's the example that's most convenient for me to pull up: https://www.youtube.com/watch?v=zHoAwVcSLdE (via https://www.commspace.co.za/)

> most modern apps demand that complexity.

Citation needed.

Basecamp and Hey.com for starters.

Whatever you can do with React you can do with pure JS.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact