Case for a monorepo

Chris Nager
Mar 20, 2018 · 4 min read
Image for post
Image for post
Code + design + file structure

🎈 Air

At , we’re building apps for capturing, storing, and managing videos. We currently offer an and a web app that share a good amount of code. This article walks through the steps we took to build a universal codebase and synced design system in a single repository.

Goal

My goal was to build a cross-platform codebase that synced with our design system and allowed for rapid code changes to increase our team’s output.

Image for post
Image for post

Universal code

I looked at our projects and found plenty of reusable code and shared folders. Our apps share utilities, constants, config settings, strings, linting rules, and components (thanks to ). With this information, I was able to split our code into reusable packages.

Image for post
Image for post
Initial plans to create Air’s universal system
Image for post
Image for post

Synced design system

We use to generate our design documentation. Our docs use Air’s shared components which keeps our design system and code in perfect sync. When a change is made to our components, our apps and docs are updated.

Image for post
Image for post
Air’s design system docs
Image for post
Image for post

Finding our optimal file structure

First implementation: Practice repos

Our initial code organization strategy was to keep our app repositories separate. We then built functional practice projects that contained essentially reusable code for us to copy-paste into our apps. As we moved quickly, this was an easy way to stay in sync for a small team, but was ultimately not a sustainable solution.

  • ✔ Simple
  • ✘ Hard to stay in sync
  • ✘ Duplicated code
  • ✘ Not sustainable
Image for post
Image for post
Oh, bother.

Second implementation: Polyrepo structure

I then built a polyrepo structure, abstracting all shared packages into their own repositories (constants, utilities, config, components, etc.).

Our apps consumed these packages as defined in their package.json file. This was clean, but led to dependency management chaos. Every time a change needed to be made to a dependency, we were forced to jump into a separate repo, update it, bump the package version (if need be), hop back into our original project, and reinstall the dependency. Another issue we ran into was, because we serve our web app and site on , we had to create workarounds for reusing our Git credentials to install our private repositories.

  • ✔ Clean
  • ✔ Reusable code
  • ✘ Slowed down development time
  • ✘ Dependency management is difficult
  • ✘ Issues with Netlify builds
Think, think, think!

Third implementation: Monorepo

Finally, I used to build a single repo that hosts all our projects and handles our dependencies in a more maintainable way.

  • ✔ Clean; all dependencies are in a single packages folder
  • ✔ Reusable code
  • ✔ Consistent linting and project configuration files across all packages (.eslintrc, .babelrc, .gitignore, prettierrc.json, etc.)
  • ✔ Cross-platform testing is easy to set up
  • ✔ Simple releases with
  • ✔ Allows for rapid development across dependencies
  • ✔ Simple dependency management
  • ✔ Contains our synced design system
  • ✔ Works perfectly with Netlify builds
“Nobody can be uncheered with a balloon.” ― A.A. Milne, Winnie-the-Pooh

🏆 Goal achieved

Thanks to our new code organization, we now have:

  • A universal codebase with , shared code between projects
  • Synced design docs that use our shared components
  • Rapid, cross-project code changes and releases in our new monorepo

Though a monorepo worked for our purposes (and for others [Facebook, Google, Twitter, Babel, React Native Web, React Router, Meteor]), be sure to review the before implementing one.

Takeaways

  • Consistency is underrated. Tools like and are spectacular for working with a team across multiple projects.
  • Using lerna import or git subtree carries over a repo’s Git history and combines its commits chronologically with the monorepo’s commit history. We’re still finding the best way to compose our commit messages for the monorepo as commits can span multiple packages. We may try namespacing our commits. (e.g. [web, components] Refactor gallery)
  • An exciting aspect of building a business from the ground up is the speed at which we’re able to adopt or reject new technologies. I’m proud of our team for their ability to move quickly and willingness to try new things.

$ >whoami

I’m Chris, a designer-developer that has worked in advertising, in big tech, on Wall Street, and am now building a startup in a Brooklyn warehouse. I care deeply about accessibility and performance in my apps.


Mash the clap (👏) button if you found this article helpful or want to show your support for our startup. Thanks!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store