Skip to main content

I Migrated to a Monorepo in 2 Days. Things Broke.

·6 min read
engineeringmonorepoturborepodx

I migrated my side project, Inner Anchor, from two separate repos to a monorepo. It took two days, things broke in production, and my users saw a white screen. But I'd do it again because I don't have to worry about accidentally causing a bug from mismatched contracts anymore.

TLDR: Multi-repo meant I had to define the API contract in two places. I missed it once and shipped a bug to production. So I migrated to a monorepo with Turborepo and pnpm. It was more work than I expected, things broke, but now I define a contract once and it works everywhere. That alone was worth it.


Some context on the project

Inner Anchor is a daily management app where people manage tasks with their purpose. It helps you start your day with the right task and adhere your tasks to a purpose. It has task management, focus mode, daily purpose, rollover, settings, user management. I started listing the features and realized, oh, that's a lot. But it's still relatively small. I was managing two repos: one for the API, one for the web app.

Why I did this

Let me go to the past a little bit. In my company, we have a tradition to do multi-repo. We just create multiple repos and develop them separately, navigating through VS Code workspaces. At most I manage four workspaces at the same time. And I felt some of its shortcomings: I have to copy the contract of the API to two places. I have to redefine it. Sometimes I miss it, and that causes a bug in production.

I did cause a bug when I mismatched the contract between the front-end and the back-end. That was the breaking point. I wanted to try a new approach in my personal project, so I tried monorepo.

Picking the tools

I did a little bit of research on Turborepo and NX, and I asked Claude what the best tool for it was. It said Turborepo is simple with pnpm. So I just went with it, because tooling picking is not important. Just go there, use one of the two most popular, and figure out what you need later. That's my philosophy.

And honestly, the tooling was easier than expected. It took me a few good minutes to get familiar with turbo.json. I had to Google to pull up the terminal UI for the monorepo, and that's it. The tool is very pleasant.

The actual migration process

Here's the real sequence:

First, enable pnpm. Create an apps folder. Move the API folder inside the apps folder, then move all the source code of the API into it. That's one.

Second, copy the whole git tree of the front-end to a web folder inside apps. That's basically the restructure done.

Then the tweaking: get both of them to build and dev first. Copy the env files to each. Build them to make sure it works.

After that, install a universal git lint-staged at the root, and each repo gets a separate lint-staged config so each of them stages their own code. That adheres to the best practice of the lint-staged official doc.

Clean up, prune down, and it should be done.

It should be.

What broke

After the restructure was "done," the real work started.

First, I had to switch Cloudflare from the old front-end repo to the new monorepo. Second, I had to SSH into the VM to manually pull the new code, manually build and run it to see if it broke or not. Third, I had to rewrite the GitHub workflow and GitHub Actions to point to the new location of the deploy script. And fix the deploy script too.

There's no way to do a zero-downtime migration on this, I think.

And then the second thing that broke: after I migrated, I went to the web, and it's white. Something didn't build. I had to go back to the repo, turn it up, build, preview it to find the source of the bug. It was a DayJS extend call where the import wasn't right. I had to add .js to the file path of the duration plugin to make it work again.

That was real panic, because the project has users now. If someone was using it right then and it just went poof, white screen. I'm sorry. If you experienced this, sorry.

I expected the migration to be smooth, just redirect and restructure files. I was wrong. There were multiple points of pain: restructuring, redirecting Cloudflare to the new repo, rewriting the CI to make sure it worked again. That was too much work.

What it looks like now

Now I have a shared package called contract. It's a shared contract between the front-end and back-end. I use Zod schema for schema parsing for both the UI forms and the back-end, so they share the same validation. That's it. We don't over-engineer it. It's just a simple contract package.

Right now I'm implementing the payment workflow and I see it. It's kind of pleasant where I define once and it works in both repos and I don't have to worry if I break something. "I don't have to worry about breaking something" is the most worthy moment of the monorepo that I think made it worth it.

The tradeoffs

The sidebar of the folder structure is longer now. I have a hard time scrolling. And I have to filter the API and the web in my terminal commands now, so that makes my commands longer. Mild inconvenience.

I haven't experienced the monorepo long enough to feel the real downsides. I could pull up Google and tell you 10 points of downside of monorepo, but I won't. I don't experience them right now. Maybe I'll revise this blog in the future, but not now.

Who this is for

This is for solo devs with side projects. Small teams can do this too, and honestly small teams need this most because the source of bugs is often the mismatched contract. The bigger the team, the more you need the monorepo.

If you're on the fence

It will be a lot of work. But I'm happily enjoying it now because I don't have to worry about accidentally causing a bug. I don't have to worry about that anymore.