The component model of web development has won. But how do you bring that innovation back to traditional frameworks? I've been trying to figure that out.
I think I'm 75% towards reinventing HTMX by accident
— Swizec Teller (@Swizec) February 5, 2025
Flask?
Flask is a typical 2010's web framework like Rails, Django, Express and others. You have views, models, templates, controllers and all the rest.
Requests come in, get routed to a view, HTML "renders" on the server, flies back as a string. Sprinkle JavaScript to make smol things interactive.
This works great until your team starts to grow. It encourages blobby monolithic architectures with unclear separation of concerns. Big ball of mud style.
Why make things composable
Balls of mud are great for small applications. Every engineer knows how everything works, has the codebase memorized, and can move fast.
Then you add engineers and features. Code begins to break every time you sneeze.
The feature you built 3 months ago has changed 30 times since you looked. Others did that. You don't even know how it works anymore.
When I first encountered this a few years ago, it felt like gerbils running around my brain moving things around. Little gremlins messing up my beautiful mental model of the code.
The biggest mental shift for a programmer is going from projects that fit in 1 person’s head to projects that nobody understands fully. Your whole approach changes.
— Swizec Teller (@Swizec) April 19, 2023
The solution is to compose your code from small balls of mud. Keep features self-contained and independent so they're easy to move around.
Composable Flask (and others?)
I'm not inventing anything new here. Just a different way to hold existing tools. A different way to think about it.
How these apps tend to look
A typical Flask view looks something like this:
# views.py
@views.route("/cool_feature")
def cool_feature():
# bunch of data fetching
# bunch of logic
# instantiating forms
return render_template("cool_feature.html",
user=user,
user_form=user_form,
address_form=address_form,
hello_string=hello_string,
# ...
)
And then you have a template that renders this:
<!-- html stuff -->
{% if user %}
<p>{{ hello_string }}, {{ user.name }}!!!</p>
<p>So cool to see you! Look we say hello in different languages so you know we're quirky and fun</p>
{% endif %}
{% if not user.name %}
<h3>Please give us your name</h3>
<form action={{ url_for('views.update_name') }}>
{{ form.hidden_tag() }}
<input name="name" type="text" />
<button type="submit">Save name</button>
</form>
{% endif %}
<!-- ... -->
And so on like this. You end up with huge views and massive templates full of intertwined logic. Everything declared and loaded up-front.
Want to move your form somewhere else? Tough! You gotta move allll the stuff it comes with. Hope you didn't miss any.
Plus if any of those sub features on the page break, the whole page breaks. You get an error like Oh no! Line 1723 of blah.py
and groan. Hate those 🙃
Make it composable
You can rewrite that same thing as composable views. Even better if you organize them into blueprints or folders by business domain.
Small views:
# user/views.py
@user.route("/user/hello")
def hello():
# get user
return render_template("user/hello.html",
user=user,
hello_string=hello_string
)
@user.route("/user/name_form")
def name_form():
# init forms
return render_template("user/name_forms.html,
user_form=user_form
)
# views.py
import views from .user as user_views
@views.route("/cool_feature")
def cool_feature():
# almost nothing
return render_template("cool_feature.html",
user=user
user_views=user_views,
# ...
)
With small templates:
<!-- user/templates/hello.html -->
<p>{{ hello_string }}, {{ user.name }}!!!</p>
<p>So cool to see you! Look we say hello in different languages so you know we're quirky and fun</p>
<!-- user/templates/name_form.html -->
<h3>Please give us your name</h3>
<form action={{ url_for('views.update_name') }}>
{{ form.hidden_tag() }}
<input name="name" type="text" />
<button type="submit">Save name</button>
</form>
<!-- cool_feature.html -->
{% if user %}
{{ user_views.hello() | safe }}
{% endif %}
{% if not user.name %}
{{ user_views.name_form() | safe}}
{% endif %}
<!-- ... -->
What just happened
Those function calls are almost like React components. Function calls that return markup.
Each handles its own data access, styling, javascript, and all the rest. The view manages data, template manages the rest. You can use {% block %}
instructions to bring your own styling or javascript and let Flask put it all together in the base template.
That means you can move your code around without worrying about breaking anything or missing a dependency.
Now here's the best part: Every composable fragment of your UI comes with a URL. That means with a bit of JavaScript, you can update portions of your page without a page reload.
Submit the form, re-render just the form. And that pretty much gives you HTMX. Or any of the newfangled React meta frameworks.
But why?
I realized going full React Islands will be a jump too far. Too much rewrite.
We can use islands for new features and big bets. But we also need to keep working on our existing 65,000 lines of python and 46,000 lines of HTML. Many of those will never get the React treatment – not worth it.
Pretty happy with this composable UI pattern. ✌️
Cheers,
~Swizec
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️