Building Your First Turborepo
In the previous article, we explored what Monorepos and Turborepos are, why companies use them, and how Turborepo acts as a…
Building Your First Turborepo
In the previous article, we explored what Monorepos and Turborepos are, why companies use them, and how Turborepo acts as a high-performance build system and task orchestrator for modern JavaScript and TypeScript projects.
Reference Link for Monorepos and Turborepos
In this article, we’ll move away from theory and build a Turborepo from scratch.
By the end of this guide, you’ll understand:
- How to create a Turborepo project.
- The command used to initialize it.
- The default folder structure.
- The purpose of the
appsandpackagesdirectories. - How multiple applications can coexist inside a single repository.
- How shared packages are organized and reused
Prerequisites
Before creating a Turborepo, make sure you have:
- Node.js installed.
- A package manager such as npm, pnpm, or Yarn.
- Basic knowledge of Git and JavaScript/TypeScript projects.
Although Turborepo supports multiple package managers, this guide will use the default setup generated by the official CLI.
Creating a New Turborepo
The easiest way to get started is by using the official scaffolding command:
npx create-turbo@latest
After running the command, the CLI will ask you a few simple questions, such as the project name and the package manager you want to use.
Once the setup is complete, a fully configured Turborepo project will be generated automatically.
At first glance, the generated structure may seem larger than a typical frontend or backend project, but each folder has a specific purpose.
In the next section, we’ll break down the generated folder structure and understand why Turborepo organizes projects this way.
Understanding the Default Project Structure
When you create a new Turborepo using the official starter template, you’ll typically see two major directories:
apps/
The apps directory contains the actual applications that can be built and deployed independently.
By default, the starter template generates:
**apps/web** — A Next.js application that usually serves as the primary user-facing web application.**apps/docs** — Another Next.js application, commonly used to host project documentation or component documentation.
Although both are independent Next.js projects, they live inside the same repository, which is one of the core ideas behind a monorepo.
packages/
The packages directory contains code and configurations that can be shared across multiple applications.
The default template usually includes:
**packages/ui** — A shared UI component library. Components created here can be imported and reused by both thewebanddocsapplications.**packages/typescript-config** — A centralized TypeScript configuration package. Instead of maintaining separatetsconfig.jsonfiles for every application, common settings are shared from here.**packages/eslint-config** — A centralized ESLint configuration package that helps maintain consistent coding standards across the entire monorepo.
The main purpose of the packages directory is to avoid duplication and encourage code sharing between applications.
Running the Turborepo
Now that we understand the folder structure, let’s actually run the project.
From the root directory, execute:
npm run dev
Note: Depending on the version of the Turborepo starter template, you may need a relatively recent version of Node.js. If you encounter compatibility errors, simply upgrade your Node.js installation and try again.
After the command executes successfully, you should notice two development servers starting automatically.
Typically, they will be available at:
[http://localhost:3000](http://localhost:3000)[http://localhost:3001](http://localhost:3001)
The exact ports may vary depending on your local environment, but by default one serves the web application and the other serves the docs application.
What Is Actually Happening?
At first glance, it may look like both Next.js applications are being started magically.
However, this is where Turborepo demonstrates its real purpose.
When you execute:
npm run dev
from the root of the repository, the root package.json delegates the task to Turborepo.
Turborepo then scans the entire monorepo and looks for projects that define a dev script.
For example:
apps/
├── web/
│ └── package.json
└── docs/
└── package.json
Both of these applications contain their own development command:
{
"scripts": {
"dev": "next dev"
}
}
Turborepo does not start Next.js itself.
Instead, it orchestrates these tasks by discovering the available dev scripts and executing them together.
In simple words:
- Next.js knows how to run the application.
- Turborepo knows which applications should run and coordinates them.
This is exactly why Turborepo is called a task orchestrator rather than a build tool.
A Practical Observation
By running a single command from the root directory, we are simultaneously running multiple independent applications that live inside one repository.
This is one of the fundamental characteristics of a monorepo:
A single repository containing multiple independent projects that can be developed and managed together.
In our case:
apps/web→ Main web applicationapps/docs→ Documentation application
Both are separate Next.js projects, but they coexist inside the same repository and are coordinated by Turborepo.
Adding Your Own Applications
The applications generated by the Turborepo starter template are only examples.
The web and docs applications are created using Next.js because they provide a good starting point, but a Turborepo is not limited to Next.js.
You can add any kind of application to your monorepo:
- Express.js backend
- React application created with Vite
- NestJS server
- Node.js microservice
- CLI applications
- Shared internal tools
As long as the application exposes standard scripts such as dev and build, Turborepo can orchestrate them.
Adding a Backend Server
Suppose we want to add an Express.js backend to our existing monorepo.
First, navigate to the apps directory:
cd apps
Now create a new folder for the backend:
mkdir server
cd server
Initialize a new Node.js project:
npm init -y
Install Express:
npm install express
For a TypeScript setup, you may also install:
npm install -D typescript tsx @types/node @types/express
Your folder structure may now look like:
apps/
├── web/
├── docs/
└── server/
At this point, the server is simply another application inside the monorepo.
Defining Development and Build Scripts
Inside the server’s package.json, define the scripts that Turborepo should orchestrate.
For example:
{
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc"
}
}
Notice that Turborepo itself is not building the server.
The server knows how to run itself.
Turborepo simply discovers these scripts and coordinates them.
Running the New Application
Once the server has been added, you do not need to manually start it every time.
Running:
npm run dev
from the root of the repository allows Turborepo to discover the new dev script and execute it alongside the other applications.
Conceptually, Turborepo now sees something like:
apps/
├── web → next dev
├── docs → next dev
└── server → tsx src/index.ts
A single command can now start multiple independent applications that belong to the same repository.
The Real Power of Turborepo: Intelligent Caching
At this point, you might wonder:
“If Turborepo simply runs the build scripts defined by each application, why do so many companies use it?”
The answer is intelligent caching.
The First Build
Suppose we execute:
npm run build
for the very first time.
Internally, the root package.json delegates this command to:
turbo build
Since no previous build information exists, Turborepo has no cached results available.
It discovers every project that exposes a build script and executes them.
For example:
apps/
├── web
└── docs
packages/
├── ui
├── eslint-config
└── typescript-config
During this first execution, all required build tasks run normally.
Once they complete successfully, Turborepo stores information about these builds inside its local cache.
What Happens If Nothing Changes?
Now suppose you immediately run:
npm run build
again without modifying any files.
This time, Turborepo compares the current state of the repository with the previous build.
Since nothing has changed, it recognizes that the previous build outputs are still valid.
Instead of rebuilding everything again, Turborepo simply restores the results from its cache.
In other words:
First Build:
❌ Cache Miss
✅ Build Everything
Second Build:
✅ Cache Hit
❌ Rebuild Nothing
This is one of the main reasons why Turborepo can significantly reduce build times.
What Happens If We Change One Application?
Now imagine we make a small change inside:
apps/web
The docs application does not depend on web.
Therefore, rebuilding docs would be unnecessary.
Turborepo analyzes the dependency graph and determines that only the web application has been affected.
The next build behaves like this:
apps/web → Rebuild
apps/docs → Use Cache
Only the modified project is rebuilt, while the unaffected project reuses its previously cached result.
What Happens If We Change a Shared Package?
Now consider a different situation.
Suppose we modify a shared component inside:
packages/ui
Both the web and docs applications import components from this package.
Because these applications depend on packages/ui, Turborepo knows that their previous build outputs are no longer valid.
The next build will look something like:
packages/ui → Rebuild
apps/web → Rebuild
apps/docs → Rebuild
This happens because a shared dependency has changed.
Rather than blindly rebuilding every project in the repository, Turborepo rebuilds only the projects that are actually affected.
Why This Matters
Imagine a large monorepo containing:
- 10 frontend applications
- 5 backend services
- 8 shared packages
Without intelligent caching, every small change could potentially trigger dozens of unnecessary builds.
Turborepo avoids this by understanding the relationships between projects and only rebuilding what is required.
This dependency-aware caching mechanism is one of the primary reasons why Turborepo is considered a high-performance build system and task orchestrator for modern monorepos.
It doesn’t simply execute tasks — it remembers previous work and intelligently decides when that work can be safely reused.
Final Thoughts
In this article, we moved beyond the theory of Monorepos and Turborepos and explored how a Turborepo actually works in practice.
We started by creating a new Turborepo project and understanding the default folder structure. We saw how the repository is divided into two major parts:
apps/— which contains independent applications such aswebanddocs.packages/— which contains shared code and configurations that can be reused across multiple applications.
We then explored how Turborepo uses the root package.json as an entry point. Instead of building or running applications itself, Turborepo discovers the scripts defined inside each individual project's package.json and orchestrates their execution.
We also learned that adding a new application is straightforward. Whether it is an Express.js backend, a React application, or another service, as long as it exposes standard scripts like dev and build, Turborepo can automatically include it in the development and build workflow.
Finally, we looked at one of Turborepo’s most powerful features: intelligent caching. Rather than rebuilding every application after every small change, Turborepo understands the dependency graph of the monorepo and rebuilds only the projects that are actually affected, while reusing cached results for everything else.
My Biggest Takeaway
The biggest realization for me while learning Turborepo was that it is not another frontend framework or build tool. It is a task orchestrator that sits on top of existing tools and coordinates them intelligently.
A monorepo gives us a way to organize multiple related projects inside a single repository, while Turborepo provides the infrastructure to manage those projects efficiently through orchestration, dependency awareness, and caching.
As projects grow larger and more complex, these concepts become increasingly valuable, which is why many modern startups and product companies adopt this architecture for their applications.
메타데이터
- post_id
- d77cd631b56a
- slug
- building-your-first-turborepo-d77cd631b56a
- url
- https://medium.com/@muskanqed/building-your-first-turborepo-d77cd631b56a
- canonical_url
- https://medium.com/@muskanqed/building-your-first-turborepo-d77cd631b56a
- author_url
- https://medium.com/@muskanqed
- status
- ok
- fetched_at
- 2026-06-10 08:17:25