Back to blog
Marcin Ostrowski  · Jun 18, 2026

11 months inside a 13-year-old Rails codebase: the Domestika rescue

What a Rails rescue actually looks like from the inside: the audit, the kills, the rebuild, and the deploy pipeline that got 40% faster on the way out.

Domestika is an online learning platform for creative professionals, serving millions of users on thirteen years of Ruby on Rails. In late 2024 their CTO brought us in, and the brief was unusually honest: decide what stays and what dies. Not “build us features.” Decide.

This is what those eleven months actually looked like, because the internet is full of legacy-rescue advice written from thirty thousand feet and nearly empty of accounts from inside the repository. I’ve written separately about the decision framework that came out of this work. This post is the work, as done by the three of us who were in the codebase.

What thirteen years of Rails looks like

From the outside, one app. From the inside: react-on-rails rendering some views, classic Rails rendering others, separate React apps on the side, and a layer of internal libraries that had reinvented authorization and API frameworks Rails already provides. Somebody had started extracting engines years earlier, so a simple change could mean updating a gem, releasing it, and bumping it in the main app. Two API styles, GraphQL and REST, coexisting on different conventions. Dead code everywhere, including half-built features that never shipped, and you couldn’t tell which was which from the code alone.

None of this happened because anyone was incompetent. It happened the way it always happens: a decade of reasonable decisions, each made under that quarter’s constraints, by people who mostly weren’t there anymore. A codebase like this is a sediment record. Reading it is archaeology, and the first weeks were exactly that: the repository, the deploy pipeline, the test suite, and long calls with the people who lived there.

The mandate, in order

What we agreed to do, ranked: get react-on-rails out and convert those views to Stimulus, simplify deployment, choose a simpler path forward for the whole app, rebuild the abandoned Community module, and then ship new features on that foundation. The ordering matters more than the list. Every item makes the next one cheaper, which is the difference between a plan and a pile of tickets.

The kill: react-on-rails

The expensive problem wasn’t ugly code. React-on-rails charged rent on the whole system: its build step in every deploy, its tax on every test run, one more rendering model for every new developer to learn. Removing it was less a refactor than the demolition of a toll booth.

To be clear about what died and what didn’t: react-on-rails is glue that makes server-rendered views pretend to be React applications, and that glue is what we killed. Where a real application lived (the standalone frontend Wojtek owned), React stayed, got modernized into TypeScript, and earned its keep. Views went back to being views, in Stimulus.

The safety mechanism for the conversion was boring on purpose: slices small enough that a bad one was a small revert, not an incident, shipped alongside normal feature work rather than as a big-bang migration branch. Small things went wrong the way they always do in slice-by-slice work. Small is the point. None of it ever grew into the kind of story that needs a postmortem heading.

The tests had drifted

Thirteen years accumulates tests the way it accumulates everything else. Many were green, plentiful, and asserting nothing: they’d drifted from the behavior they were once written to protect, surviving refactor after refactor as pure ceremony. A suite like that is worse than missing coverage, because it sells confidence nobody should buy. We deleted the drifted ones and wrote tests that catch real bugs.

Two honest notes on that. Deleting tests is part of where the deploy speedup came from, and that part was correct: a test that asserts nothing spends CI minutes buying false confidence, and removing it is a quality improvement that happens to read as a performance one. And the principle outlived the engagement: it ended up shaping how we verify everything now, AI-written code included.

So: deploy time dropped 40%, measured on the pipeline, traceable mostly to react-on-rails leaving the build and the test suite getting honest. Nobody ran a “make deploys faster” project. The number is what falling complexity looks like on a clock.

The build: Community

Domestika’s Community module had been abandoned for years: no owner, no investment, drifting further from the rest of the product with every release. The business wanted it back because contests needed it. A platform where users publish projects and the best work gets rewarded engages exactly the people Domestika exists for.

Abandoned code gets a different treatment than living code. There was nothing worth preserving in the old project editor, so the old code wasn’t the spec. User research was. Katarzyna ran the full process, from discovery through production rollout, and the editor that came out of it belongs to this decade. I led the module rebuild around it, and on that foundation we shipped the contest platform, the feature the whole rebuild existed to enable.

That sequencing deserves a sentence of its own: the rescue paid for itself in a shipped product feature, not in an architecture diagram. Nobody outside engineering celebrates a simplified webpack config. People celebrate contests going live.

What we left standing

Thirteen years doesn’t get fixed in eleven months, and pretending otherwise is how rescues turn into rewrites. The Rails core that served millions kept serving them, untouched wherever touching it bought nothing. The dual GraphQL-and-REST situation outlived us, as did some of the extracted engines: real debts, deliberately left for a day when paying them buys something. Plenty of imperfect code stayed imperfect because it worked. A rescue’s discipline isn’t in what you fix. It’s in what you decline to fix.

What made it work

The mandate was decision-shaped. We were hired to choose, not just to produce code, so disagreements got resolved instead of accumulating in a backlog. The sequence compounded, with every removal making the next move cheaper, and the exit was designed in from the start: the team that inherited the codebase could read it, because simplification, not cleverness, was the standard for every change we made.

The codebase is still thirteen years old. It just stopped acting like it.

fryga.io
fryga — a product engineering consultancy in Kraków, Poland. We build with companies in Spain, Norway, the US, and beyond.
Marcin writes about Rails and AI at rubyonai.com
The Rails AI harness, in the open at github.com/fryga-io/superpowers-rails
We're four people — maybe five: careers
[email protected]
RubyPL sp. z o. o.; ul. Będzińska 5 /8, 31-403 Kraków, Poland; KRS: 0001044822; NIP: 6762646011; REGON: 52575800600000