How we run multiple frontend apps in one Turborepo monorepo
One repo, several apps, a shared design system, and Turborepo keeping builds fast. Here is how it fits together and why it is worth it.

We ship several frontend apps: a client-facing booking widget, a marketing site, and an admin dashboard. They share a design system, shared types, and the same tooling. Kept in separate repositories, that sharing turns into copy-paste and drift. One monorepo with Turborepo fixed that.
Why one repo
The shared design system and types live in one place, so every app always uses the latest version with no publish-and-bump dance during development. A change to a shared component is a single pull request, tested against every app that consumes it. And the whole workspace runs on one consistent toolchain (lint, tsconfig, test setup) instead of a handful of configs that quietly diverge.
The layout: PNPM workspaces
The structure is simple: an apps folder (booking, marketing, admin) and a
packages folder (the design system, shared config and types). PNPM workspaces
link them together, so an app imports a package by name and resolves straight to
its local source. No versioning gymnastics while I am building.
What Turborepo actually does
Turborepo is the task runner that sits on top of the workspace. It understands
the dependency graph between packages and apps, so turbo run build builds a
package before the apps that depend on it, and runs unrelated tasks in parallel.
The real win is caching: if a package has not changed, its build, test, and lint
results are restored from cache instead of recomputed. That turns “rebuild
everything” into “rebuild only what changed,” both locally and in CI.
One task pipeline, defined once
I define the tasks (build, lint, test, type-check) and their dependencies once in
the Turborepo config. build depends on the build of upstream packages, while
test and lint fan out in parallel across the repo. One command at the root
runs everything in the right order, so I do not have to remember it.
CI that only does the work that changed
The same graph-aware, cached tasks run in GitHub Actions. A pull request that only touches the marketing site does not rebuild the booking widget. With remote caching, CI reuses artifacts across runs and machines, so the pipeline stays fast as the repo grows instead of getting slower with every app we add.
Deploys stay independent
Each app builds to its own output and deploys on its own cadence to AWS, using S3 and CloudFront for static delivery and EC2 where we need it. One repository, but no app is forced to ship just because another one did.
The payoff
A monorepo is not about cramming everything into one folder. It is about a single source of truth for shared code, one consistent toolchain, and a build system that only does the work that actually changed. For a small team running several apps, that is the difference between shipping quickly and drowning in coordination.