Over the past few months, in my spare time, I've been working on my side-project, Zero Waste Tickets, where I make heavy use of HTMX, server-rendered HTML, and a few bits of vanilla JavaScript for interactions. I'm able to do more with less. Much more than you might expect for a single dev working in my spare time. And it's fast to load, and I can reason about the whole codebase in one place.

I keep reaching for simpler tools and getting better results. Not as a nostalgia trip, but because the defaults in web development have drifted towards complexity that most projects don't need. React had its moment, but it got used everywhere, including lots of places it shouldn't. And it created a whole ecosystem of state management, build tooling, and split-brain architecture along with it.

This post is about the stack I'm excited about right now. It's a mix of things I'm using in production, things I'm experimenting with in side projects, and things I'll be pushing for at work. It's opinionated and personal.

The Philosophy: Do More With Less

The best stack is the one where you've removed everything that isn't pulling its weight.

Splitting an application into a heavy frontend and a separate backend roughly doubles the surface area. Two codebases, two deployment pipelines, two sets of state to manage, and a contract between them that needs constant maintenance. Business logic gets scattered — some in the frontend for "responsiveness," some in the backend for "security." Testing becomes harder. Onboarding new developers takes longer. Why do we keep doing this to ourselves?

"Simplicity is a great virtue, but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better." — Edsger Dijkstra

That last line is the whole story of React's dominance. Complexity sold very well indeed.

The alternative is to co-locate business logic in the backend and treat HTML as a first-class output format. This isn't a new idea — it's how the web worked for most of its early history. But it deserves a serious look now that HTML and CSS have got genuinely good.

Modern HTML gives you dialogs, form validation, lazy loading, and semantic elements that screen readers understand out of the box. CSS handles layouts that used to require JavaScript. If you lean into the platform, you get performance and accessibility by default instead of fighting for them.

That doesn't mean zero JavaScript. It means JavaScript where it actually adds value, not as the default for everything.

The Rust Backend: Axum, SQLx, and Postgres

I'm writing web services in Rust using Axum. The type system catches entire categories of bugs at compile time, and the performance characteristics mean I don't need to think about scaling until much later than I would with other languages.

Axum itself is straightforward — it's a thin layer over the Tokio async runtime with good ergonomics for routing and middleware. Unlike the full-featured frameworks you get in other languages, Axum is deliberately a low-level building block. I appreciate that as a design goal, even if it means you're assembling more pieces yourself. I've ended up writing my own macro-based utilities on top of it to make common patterns more declarative, which is exactly the kind of flexibility this approach allows.

For database access, I'm using SQLx with Postgres. SQLx checks your SQL queries against your actual database schema at compile time. No ORM abstraction layer, no runtime query building — just raw SQL with the safety of knowing it will work before you deploy.

Postgres continues to be the right default for most applications. It handles JSON, full-text search, and geospatial data without needing separate systems. That said, it's not without its quirks. If you try to build queue systems on top of it, or rely heavily on locks and NOTIFY, you can run into global lock contention that's easy to miss until it becomes a problem. It's quite possible to shoot yourself in the foot.

I've been using Postgres long enough that I know where the rough edges are and how to work around them. Over the years I've watched people move to trendier databases only to spend time rebuilding features that a relational database gives you out of the box.

HTML-First with Maud and HTMX

For rendering HTML, I'm using Maud — a macro-based templating library for Rust. Templates are type-checked at compile time, and because they're just Rust code, you get all the refactoring and IDE support you're used to.

For styling, I'm using a small macro to collect and aggregate CSS snippets from across the codebase.

When I need interactivity beyond what HTML provides natively, I reach for HTMX. It lets you make AJAX requests and update parts of the page using HTML attributes. The mental model stays simple: the server returns HTML, and HTMX swaps it into the DOM.

The result is fast and accessible. The pages are small. The server does the work it's good at. And because the core content is server-rendered HTML, there's a graceful baseline even without JavaScript.

Infrastructure You Can Understand: Docker Compose on a VPS

I'm currently in the process of moving my side projects from cloud services to a self-hosted VPS. The primary motivation is cost — running something like Zero Waste Tickets on managed cloud infrastructure costs more than it needs to. But there's also value in having infrastructure I can fully understand and control.

For orchestration, Docker Compose is enough. I define my services in a single file, run docker compose up, and everything works. No service mesh, no ingress controllers, no YAML spread across dozens of files.

For object storage, I use whatever S3-compatible option the host provides, or RustFS if I need to run it myself. The S3 API is a reasonable standard, and there's no reason to couple to a specific cloud provider for blob storage.

We use Kubernetes heavily at work, and I understand why it exists. For large-scale systems with dedicated platform teams, the complexity is justified. But for most projects, it's overhead that doesn't pay for itself.

Durable Execution for the Agentic Future: Restate

The piece of this stack I'm most excited about is Restate.

Restate is a durable execution platform. It stores the progress of your workflows as they run, so if something crashes, execution resumes from where it left off rather than restarting from the beginning. Retries, idempotency, and state persistence are handled by the runtime rather than being your problem.

I've been using it in side projects for long-running workflows and agent orchestration. If you've ever built a multistep process — an agent that calls external APIs, waits for responses, and makes decisions over minutes or hours — you know the pain. You end up hand-rolling state machines, writing retry logic, building idempotency checks, tracking progress in a database. And then you do it all again for the next workflow, slightly differently each time.

Restate makes that entire category of problem go away. You write your workflow as straightforward code, and the runtime handles persistence, retries, and recovery. If something crashes, execution picks up where it left off.

As AI agents start doing more real work — spanning multiple steps, calling external services, running over extended time periods — this kind of infrastructure stops being nice-to-have. I'm planning to expand my use of it and introduce it at work where it makes sense.

Wrapping Up

None of these are particularly exotic choices. Postgres has been around for decades. HTML-first development is older than React. Docker Compose is deliberately simple. What's changed is that I'm choosing them deliberately rather than defaulting to complexity. Doing more with less.

I'd love to know what your stack looks like right now. What are you excited about this year? What have you dropped that you used to think was essential? Drop me a line — I'm always up for that conversation.