Building a CMS in Rust: Lessons from 250,000 Lines of Code
I have been building LuperIQ, a content management system written entirely in Rust, for the better part of two years. It started as an experiment — could a single-developer project build a CMS that competed with WordPress using a systems programming language? The answer, it turns out, is yes. But the journey taught me things I did not expect.
Why Rust Over Everything Else
I evaluated Go, Python (Django/Flask), Node.js, and Rust before starting. Go was the closest competitor — fast compilation, good concurrency, simple deployment. But Go's error handling is verbose without being expressive, and its type system lacks the precision I wanted for a complex domain model.
Python with Django is the traditional CMS choice, and for good reason — Django's ORM, admin panel, and ecosystem are mature. But Python's performance ceiling worried me. A CMS serving hundreds of pages per second while running AI workflows needs raw speed, not interpreted overhead.
Node.js had the ecosystem advantage (NPM has everything) but single-threaded JavaScript felt wrong for a system that would handle concurrent database operations, template rendering, file I/O, and AI inference. Yes, async helps. But real parallelism matters when you are doing CPU-intensive work.
Rust gave me everything: C-level performance, memory safety, real multi-threading, an excellent type system, and the serde ecosystem for serialization. The tradeoff was development speed — Rust takes longer to write than Python or JavaScript. But for infrastructure software that runs for years, that tradeoff is worth it.
The Architecture
LuperIQ is a workspace of 6 main crates: the CMS application server (axum-based), the core data layer (ForgeJournal), a bundle format for data transfer, a web crawler for AI verification, a demo GraphQL server, and a pure-Rust BERT embedding service.
The CMS itself has over 65 modules, each implementing a trait that defines routes, admin views, and assets. This trait-based module system is one of the best architectural decisions I made. Adding a new module means implementing a trait and registering it — the CMS automatically handles routing, admin navigation, and asset loading.
All state is stored in an event-sourced append-only log (ForgeJournal) using bincode serialization with blake3 checksums. No traditional database. This was controversial (everyone told me I was crazy) but it has proven to be simple, fast, and remarkably debuggable.
Lessons Learned
Lesson 1: Rust's compile times are real. A clean build of the full workspace takes about 6 minutes. Incremental builds are fast (under 10 seconds for small changes), but the first build after a dependency update is painful. I learned to use dev-release profiles (optimized but with faster linking) for iteration and save full release builds for deployment.
Lesson 2: Serde is magic. The serde ecosystem for serialization and deserialization is arguably Rust's killer feature for application development. Defining a struct with #[derive(Serialize, Deserialize)] gives you JSON, bincode, TOML, and dozens of other formats for free. My entire event-sourcing system is built on serde + bincode.
Lesson 3: Axum is the right web framework. I started with actix-web, switched to axum, and never looked back. Axum's tower middleware compatibility, typed extractors, and clean error handling make building REST APIs pleasant. The async ecosystem (tokio runtime) handles thousands of concurrent connections without breaking a sweat.
Lesson 4: The borrow checker teaches you better architecture. When the borrow checker rejects your code, it is usually because your data flow is tangled. Wrestling with lifetimes forced me to design cleaner APIs with explicit ownership boundaries. Code that satisfies the borrow checker tends to have fewer concurrency bugs and clearer data flow.
Lesson 5: Traits are Rust's most underappreciated feature. The module system in LuperIQ is built entirely on traits. Each CMS module implements the CmsModule trait. Each data manager (ForgeContentManager, ForgeMenuManager, ForgeMediaManager) follows a consistent pattern. Traits give you polymorphism without inheritance, and it results in cleaner, more composable code than class hierarchies.
Performance Results
LuperIQ serves page requests in under 2 milliseconds on average. Template rendering (Tera, a Jinja2 implementation for Rust) is fast enough that I have never needed to add a caching layer. The event-sourced journal handles thousands of writes per second on commodity hardware.
Memory usage is remarkably low. A fully loaded CMS instance with 65+ modules, template engine, and in-memory state projections uses about 50-80 MB of RAM. Compare that to a WordPress installation with a comparable feature set, which typically uses 256-512 MB.
Binary size is about 25 MB for the complete CMS. One binary, no runtime dependencies, no interpreter, no VM. Copy it to a server and it runs. Deployment does not get simpler than that.
The Hard Parts
The ecosystem gap is real. WordPress has 60,000+ plugins. LuperIQ has 65 modules I built myself. For every feature that would be a 'just install this plugin' in WordPress, I had to build it from scratch: email marketing, booking system, commerce, customer portal, A/B testing, theme studio, SEO tools.
Frontend tooling in Rust is immature. I use server-side rendered HTML with Tera templates and vanilla JavaScript for interactivity. There is no mature Rust equivalent of Next.js or Django's template system. This is fine for a CMS (server rendering is correct for SEO), but it limits interactive features.
Finding contributors is hard. Rust developers are in high demand, and most are working on well-funded infrastructure projects. Finding someone who wants to contribute to a CMS in Rust is a much smaller pool than finding PHP or JavaScript contributors.
Was It Worth It?
Absolutely. LuperIQ deploys as a single binary that starts in under a second, handles thousands of requests per second, uses minimal memory, and has never had a memory safety vulnerability. The event-sourced architecture means I can replay the entire history of any CMS instance for debugging.
The development experience improved dramatically over the two years. Early Rust code was a fight with the borrow checker. Current code reads almost as cleanly as Python, with the performance of C. The language and my skill in using it both improved.
Would I recommend building a CMS in Rust? If you are a solo developer or small team building infrastructure software that needs to run reliably for years, yes. If you need to ship a product next month and iterate fast, probably not. Rust rewards patience and punishes haste. For a CMS that will serve thousands of businesses, that tradeoff makes sense.