After playing for some hours with the new Turbo on a side project (with Django as backend), it mostly works fine.
One thing that is kind of broken out of the box is form submissions: so a common pattern with server side rendering is to re-render the form with validation errors, but otherwise redirect to wherever.
The workaround here I think is to use turbo-streams and just re-render the invalid form HTML snippet rather than the whole page. While this is probably ok for most cases, I have some forms (3rd party library stuff) where this is going to be more work than I'd like, and due to a bug in the beta release you can't override the default form submission behavior - Turbo throws an error if a form POST returns anything other than a redirect. So it's probably not quite ready for production yet.
With Django and flask and everything else I've ever used, there is a templating system at play. I use the request headers to determine if I should return a whole page or a portion of a page.
For example I have a form that's something like
form_partial.html: partial form template:
<form hx-post="{{ request.path }}" hx-swap="outerHTML" hx-encoding='multipart/form-data' hx-target="this">
{% csrftoken %}
... some other form stuff from django
</form>
form.html: A normal page that includes that form:
{% extends core.html %}
{% include partial_form.html %}
Then in the controller code I choose whether to render the entire page (form.html) or a partial page (partial_form.html) depending on the presence of the htmx headers.
So the full load might look something like:
1. Visit mysite/coolform
2. Server renders whole page
3. Browser submits form data
4. If the form is invalid, return only the partial form, swapping the forms html
and if the form IS valid, return a redirect
Another cool thing (checkout hx-boost ) about htmx is that even if you return a whole page you can actually still avoid a full page reload and only swap out the body component. So even though you're returning a few extra bytes the site isn't re-executing the entire JavaScript bundle every time you navigate to a new page.
I tried this approach and it's clearly attractive, but it also has some disadvantages. I decided to switch back to rendering static HTML in Django (not a Rails guy), with just a little JS on top for now. These are my main reasons:
- Debugging on every browser was quite painful. I tried to support IE7+ as well. If a server-side code works, it works for everyone.
- Frameworks on the client-side are not as developed as the server-side ones. Also I am reluctant to introduce more client-side libraries than necessarily, so I will end up coding a lot of stuff, that I can get for free in Django otherwise.
- The user will need to wait for the JS to load and execute until they see something on the first pageload (at least in my implementation).
- If there is a bug in the JS it's harder to get it logged.
I think the case for server-side HTML rendering has never been stronger, and of all Django projects I work on, the ones I enjoy most are those that never got on the SPA bandwagon. From both a developer and user point of view I find them much faster and less painful. There are many cases where you really, really don't need the massive amount of extra complexity involved in designing APIs, adding JS frameworks etc.
When you need a bit of extra UI goodness, htmx https://htmx.org/ is a fantastic solution, and you can still use SPA-type approaches for things that need them.
You can also benefit from massively faster (and more reliable) functional testing when you are mostly standard HTML - see django-functest https://github.com/django-functest/django-functest/ for an example of this.
Thanks, but this doesn't render the HTML, it just shows the plaintext. The HTML generated by Django for error messages is not very readable, which is why I have to copy what Chrome gives me in that XHR tab into a new file to view as rendered HTML.
I just end up using Firebug Lite instead when I need to do this, but it's really clunky.
So. Write your templates in your server-side templating language. That way you can serve full html for initial page loads and compiled-to-javascript versions to allow Ajax updates.
Yes. If people are using full MVC frameworks for the sole reason of avoiding page reloads then they really need to look at PJAX etc.
I can add PJAX to existing Django sites in about half an hour. It degrades gracefully, allows me to carry on generating my HTML on the server in a language I prefer and carries a very small cognitive overhead.
I understand TurboLinks in Rails is essentially the same concept?
This is cool and definitely something I'm going to look into!
But I have a question for the frontend people.
Is server-side rendering + ~Bootstrap + ~jQuery really getting you into that much trouble? Even with peppering in some HTMX [0]?
I do all my projects with Django + Bootstrap + jQuery + (maybe HTMX if needed) and I've never heard any kind of feedback from anyone that suggests I should be doing anything else.
I've used Turbolinks/Stimulus with Django. I can't speak to the new hotwire stuff, other than the relatviely minor Stimulus2 changes.
Honestly it's pretty straightforward and the API is tiny. It's great for that grey area between serving server-loaded static pages and full-blown SPA - for example CRUD apps (and lots of things are pretty much CRUD apps). Turbolinks gives you a "poor man's SPA" for little effort (albeit with some gotchas) and Stimulus is fine, although it gets tedious for when you have to work with a lot of DOM elements e.g. "when I click the Subscribe button, change it to an Unsubscribe button". Maybe a less imperative framework like AlpineJS might be simpler.
Actually, FastUI is repeating the same thing. It's cool but don't we get the same thing if we return HTML instead of JSON, combined with HTMX? We rely on the browser and battle-tested Django forms. No need to invent the wheel.
I recently used Turbolinks + Stimulus for a Django side project. While these projects originated in Rails, it was quite easy to add to Django with some middleware and webpack tweaking. Stimulus provides some good structure and constraints for adding JS "sprinkles" to the site and overall it's worked very well. It feels more like I'm building with the browser and HTML rather than against or around them. That said, I don't think this approach would fly at work and would probably be frowned upon as maybe inefficient or out of date compared to the pure SPA+API architecture.
For those screens that you want super snappy, you'll want them outside of Django. You'll want to get to where the page generation is as simple as possible on the server side and the endpoint you're POST'ing to is as performant as possible. SIMPLE is the name of the game when it comes to these kinds of things.
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.
I would have been satisfied if the forms use the django templating system, instead of trying to use string manipulation to create HTML somewhere in the bowls of a python file.
How great would this be?
name = BooleanField(template='switch_not_radio_boxes.html')
Your template file spits out HTML for flip switches instead of radio boxes (e.g. Https://proto.io/freebies/onoff/), and the template can add javascript/css to be bundled into a singular location with the rest of the Form fields static assets.
It'd be simple.
But nope, instead BooleanFields use a widget that creates HTML & sprinkles error messages upon it through some byzantine means.
Oh man, I've had to work on projects with serverside form generation (python people are really into this stuff for some reason), and it's a huge pain. Backend engineer thinks it's going to be way easier or something not having to write templates, however on the frontend, the code generated by these tools is always weird and different from the rest of the markup. No thanks.
I also think django templates is not up to par in 2017, and use typically Django as an REST API platform; A node/react server would call the API and render the html.
"I think one of the unsolved problems of client-side interactivity on websites is how difficult it is to add it just a little bit of extra client-side functionality to a traditional server rendered website. " I've started using django-unicorn lately and I'm hopeful that it may solve that problem for many use-cases.
One thing that is kind of broken out of the box is form submissions: so a common pattern with server side rendering is to re-render the form with validation errors, but otherwise redirect to wherever.
The workaround here I think is to use turbo-streams and just re-render the invalid form HTML snippet rather than the whole page. While this is probably ok for most cases, I have some forms (3rd party library stuff) where this is going to be more work than I'd like, and due to a bug in the beta release you can't override the default form submission behavior - Turbo throws an error if a form POST returns anything other than a redirect. So it's probably not quite ready for production yet.
reply