by Joe Marshall on February 22nd, 2020
We just launched formcake (🎉!) with a marketing and application monorepo. That means all frontend and backend services, for both martech and application services, are represented in a single repo that looks like this.
With all individual services falling into one of those two buckets.
We thought we'd write about our reasoning - why use a monorepo (though admittedly a tiny one), and why not split our marketing off into Wordpress (OK) or a JAM-stack powered static site (better)?
We use Nuxt in Server-Side Rendering (SSR) mode for our frontend, then an Express-powered Node API server, for our backend, employing Postgres as our persistence layer. This is all hosted in AWS, where we also take advantage of some serverless patterns: Within
/back we have a
/functions directory for our Lambdas that feed off an SQS queue. While most of our interactions stop at the Postgres level, triggering form actions feeds an SQS queue consumed by Lambdas responsible for carrying out an action associated with a form submission (like sending an email, firing off a webhook, or zapping a Zapier zap).
As a general list of tech:
We tried to pick a mix of tech we either had close experience with (Nuxt) or is so battle-tested that it should pretty much be the default for everyone (Postgres). We also liked the isomorphic nature of an all-JS codebase, and felt like Node and something as lightweight as Express made for a good API base.
We also individual had experiencing building on these technologies together before. Developing a common stack has helped speed everything along.
For the unified marketing and app part - our blog is four functions in a section of the Express app; our main landing page shares the same frontend surface as our application; and our pricing page is powered by our application API. Everything lives side-by-side.
There were several factors playing into our current structure.
When we were thinking of how we wanted to do our docs, we considered a couple of different services like readthedocs.io and docsify, but were most inspired by Stripe's living docs - where, if you're logged in, code samples are populated by your account's actual test tokens.
Now our docs are thoroughly humble at the moment - bare even. But we've built everything to allow us to implement that sort of sandbox. You could call it a premature optimization, but the design allows us to be flexible.
There's no reason to avoid subdomains in particular - but the advice on them can be rather situational: Google says Googlebot treats subdirectories and subdomains the same (so
site.com/blog rank equally), but subdomains can suffer from their own issues like keyword dilution.
But there still seems like an organizational elegance to having everything under
formcake.com (well, except for
api.formcake.com, since we're cheating). It gives us a nice sitemap that covers a consistent app surface.
Because we're playing in an application where we can already access stuff like account info, pricing, and other data, we can integrate our martech so that marketing pages like
/pricing are all fed from a few Postgres tables. We can then have a unified marketing and application source of truth - we always price as advertised, because we have one pricing table feeding everything.
A classic advantage of monorepos is the capacity to perform cross-project changes where a single release can include alterations of multiple coordinating services. This blog, dear reader, was one PR: the frontend Nuxt infrastracture and styling, the backend Express routes, and the actual rich text markdown flat files, all bundled together in a single change. With our setup, we can also extend this idea across not just multiple application projects but our entire business unit as well - with one PR we can release a feature and the marketing apparatus for that feature at the same time.
We also benefit from some specific circumstances it's useful to note.
Our blog, as markdown files served by Express, is not very user-friendly. But we're not some larger outfit with outside freelancers and editors - or our own writing staff - we're it. And since we don't need a whole CMS for blog post CRUD or a separate application for its own access control, we can avoid a lot of overhead. If we had a dedicated writer, bizdev person or account manager, we'd have to change our infrastructure to accomodate them.
We're not landing a spacecraft on a floating drone barge - we're proud of our architecture, but the problem space is straightforward. Monorepos, though popular at large companies like Google and Facebook, also do well at smaller scales, when individual services (the blog, pricing info, form dashboard) are small enough to group together. With smaller applications, we don't risk a a weird directory organization or testing the limits of
We don't need to staff up - we plan on operating via our own talent, augmented by the occasional outside freelancer, for a long while as we bootstrap the company. That means we can design systems that only fit the four of us.
These are things we've either learned to live with or have accepted as trade-offs for what we've accomplished.
Our blog is in our principal application source code. To edit a post you would need access to all our source. This is fine for our four-person partnership - but would be unworkable for a conventional company.
Monorepos are supposed to reduce code duplication and allow for more isomorphic strategies (that work well with our chosen tech like Nuxt), but that can quickly curdle into tight couplings where code snippets that have no business referencing each other become deeply entangled.
If we want to associate a Github webhook or CircleCI job with an individual repo, we only have one - and it's got everything. It comes back to the security isolation issue - we've lost a certain granularity with the monorepo. Now, it's not terrible to have multiple apps simply tuning into their subdirectory of choice when they test/merge/deploy whatever code their concerned with, but it could get messy.
We chose a structure for our project that mirrored the needs and unique advantages of our business - one where every member was trusted (and expected) to contribute technically.
It's definitely not for everyone, but hopefully this breakdown of how it does (and sometimes doesn't) work for us will help inform decisions for your own projects, and maybe lead you to consider these patterns yourself.