---
title: Getting Started with Turborepo
description: Create your first monorepo or add Turborepo to an existing project.
---

import { Callout } from "../../../../components/Callout";
import { Tabs, Tab } from "../../../../components/Tabs";

# Creating a new monorepo

<Callout type="info">
  This guide uses a global installation of `turbo`. Follow the [installation guide](../installing)
  to get this setup. Alternatively, you can use your package manager to run a locally installed `turbo`
  in the commands below.
</Callout>

## Quickstart

To create a new monorepo, use our [`create-turbo`](https://www.npmjs.com/package/create-turbo) npm package:

<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager">
  <Tab>
    ```sh
    npx create-turbo@latest
    ```
  </Tab>
  <Tab>
    ```sh
    yarn dlx create-turbo@latest
    ```
  </Tab>
  <Tab>
    ```sh
    pnpm dlx create-turbo@latest
    ```
  </Tab>
</Tabs>

You can also clone a Turborepo starter repository to get a head start on your monorepo. To see Turborepo examples and starters, see the [Turborepo examples directory on GitHub](https://github.com/vercel/turbo/tree/main/examples).

## Full tutorial

This tutorial will walk you through setting up a basic example. By the end, you'll feel confident with using `turbo`, and know all the basic functionality.

<Callout type="info">

During this tutorial, some lines of code are omitted from the code samples. For instance, when showing a `package.json` we won't show _all_ of the keys - only the ones that matter.

</Callout>

### 1. Running `create-turbo`

First, run:

<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager">
  <Tab>
    ```sh
    npx create-turbo@latest
    ```
  </Tab>
  <Tab>
    ```sh
    yarn dlx create-turbo@latest
    ```
  </Tab>
  <Tab>
    ```sh
    pnpm dlx create-turbo@latest
    ```
  </Tab>
</Tabs>

This installs the [`create-turbo`](https://www.npmjs.com/package/create-turbo) CLI, and runs it. You'll be asked several questions:

#### Where would you like to create your turborepo?

Choose anywhere you like. The default is `./my-turborepo`.

#### Which package manager do you want to use?

Turborepo doesn't handle installing packages, so you'll need to choose one of:

- [bun](https://bun.sh/)
- [npm](https://www.npmjs.com/)
- [pnpm](https://pnpm.io/)
- [yarn](https://yarnpkg.com/)

`create-turbo` will detect which package managers you have available on your system. If you're unsure which to choose, Turborepo recommends `pnpm`.

#### Installation

Once you've picked a package manager, `create-turbo` will create a bunch of new files inside the folder name you picked. It'll also install all the dependencies that come with the `basic` example by default.

### 2. Exploring your new repo

You might have noticed something in the terminal. `create-turbo` gave you a description of all of the things it was adding.

```
>>> Creating a new turborepo with the following:

 - apps/web: Next.js with TypeScript
 - apps/docs: Next.js with TypeScript
 - packages/ui: Shared React component library
 - packages/eslint-config: Shared configuration (ESLint)
 - packages/typescript-config: Shared TypeScript `tsconfig.json`
```

Each of these is a _workspace_ - a folder containing a `package.json`. Each workspace can declare its own dependencies, run its own scripts, and export code for other workspaces to use.

Open the root folder - `./my-turborepo` - in your favourite code editor.

#### Understanding `packages/ui`

First, open `./packages/ui/package.json`. You'll notice that the package's name is `"name": "@repo/ui"` - right at the top of the file.

Next, open `./apps/web/package.json`. You'll notice that this package's name is `"name": "web"`. But also - take a look in its dependencies.

You'll see that `"web"` depends on a package called `"@repo/ui"`.

<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager">
  <Tab>
    ```json filename="apps/web/package.json"
    {
      "dependencies": {
        "@repo/ui": "*"
      }
    }
    ```
  </Tab>
  <Tab>
    ```json filename="apps/web/package.json"
    {
      "dependencies": {
        "@repo/ui": "*"
      }
    }
    ```
  </Tab>
  <Tab>
    ```json filename="apps/web/package.json"
    {
      "dependencies": {
        "@repo/ui": "workspace:*"
      }
    }
    ```
  </Tab>
</Tabs>

This means that our **web app depends on our local `@repo/ui` package**.

If you look inside `apps/docs/package.json`, you'll see the same thing. Both `web` and `docs` depend on `@repo/ui` - a shared component library.

This pattern of sharing code across applications is extremely common in monorepos - and means that multiple apps can share a single design system.

#### Understanding imports and exports

Take a look inside `./apps/docs/app/page.tsx`. Both `docs` and `web` are [Next.js](https://nextjs.org/) applications, and they both use the `@repo/ui` library in a similar way:

```tsx filename="apps/docs/app/page.tsx"
import { Button } from "@repo/ui/button";
//       ^^^^^^         ^^^^^^^^^^^^^^^

export default function Page() {
  return (
    <>
      <Button appName="web" className={styles.button}>
        Click me!
      </Button>
    <>
  );
}
```

They're importing `Button` directly from a dependency called `@repo/ui/button`! How does that work? Where is `Button` coming from?

Open `packages/ui/package.json`. You'll notice the `exports` field:

```json filename="packages/ui/package.json"
{
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx",
    "./code": "./src/code.tsx"
  },
}
```

When workspaces import from `@repo/ui/button`, `exports` tells them where to access the code they're importing.

So, let's look inside `packages/ui/src/button.tsx`:

```tsx filename="packages/ui/src/button.tsx"
"use client";

import { ReactNode } from "react";

interface ButtonProps {
  children: ReactNode;
  className?: string;
  appName: string;
}

export const Button = ({ children, className, appName }: ButtonProps) => {
  return (
    <button
      className={className}
      onClick={() => alert(`Hello from your ${appName} app!`)}
    >
      {children}
    </button>
  );
};
```

We've found our button!

Everything inside this file will be able to be used by workspaces that depend on `@repo/ui/button`.

Any changes we make in this file will be shared across `web` and `docs`. Pretty cool!

<Callout type="idea">

Try experimenting with exporting a different function from this file. Perhaps `add(a, b)` for adding two numbers together.

This can then be imported by `web` and `docs`.

</Callout>

#### Understanding `tsconfig`

We have two more workspaces to look at, `typescript-config` and `eslint-config`. Each of these allow for shared configuration across the monorepo. Let's look in `typescript-config`:

```json filename="packages/typescript-config/package.json"
{
  "name": "@repo/typescript-config",
}
```

Here we see the name of the package is `@repo/typescript-config`.

Now, let's take a look in the `tsconfig.json` file located in our `web` app.

```json filename="apps/web/tsconfig.json"
{
  "extends": "@repo/typescript-config/nextjs.json",
}
```

As you can see, we're importing `@repo/typescript-config/nextjs.json` directly into our `tsconfig.json` file.

This pattern allows for a monorepo to share a single `tsconfig.json` across all its workspaces, reducing code duplication.

#### Understanding `eslint-config`

Our final workspace is `eslint-config`.

Let's start with taking a look inside `packages/eslint-config/package.json`:

```json filename="packages/eslint-config/package.json"
{
  "name": "@repo/eslint-config",
  "files": [
    "library.js",
    "next.js",
    "react-internal.js"
  ],
}
```

As you can see, the package is named `@repo/eslint-config`, and it exposes three files: `library.js`, `next.js` and `react-internal.js`.

To understand how we can use custom ESLint configs, let's take a look inside `apps/docs/.eslintrc.js`:

```js filename="apps/docs/.eslintrc.js"
module.exports = {
  extends: ["@repo/eslint-config/next.js"],
};
```

Here you can see that we're importing `@repo/eslint-config/next.js` directly into our `.eslintrc.js` file.

Just like `typescript-config`, `eslint-config` lets us share ESLint configs across our entire monorepo, keeping things consistent no matter what project you're working on.

#### Summary

It's important to understand the dependencies between these workspaces. Let's map them out:

- `web` - depends on `ui`, `typescript-config` and `eslint-config`
- `docs` - depends on `ui`, `typescript-config` and `eslint-config`
- `ui` - depends on `typescript-config` and `eslint-config`
- `typescript-config` - no dependencies
- `eslint-config` - no dependencies

Note that **the Turborepo CLI is not responsible for managing these dependencies**. All of the things above are handled by the package manager you chose (`npm`, `pnpm` or `yarn`).

### 3. Understanding `turbo.json`

We now understand our repository and its dependencies. How does Turborepo help?

Turborepo helps by making running tasks simpler and _much_ more efficient.

Let's take a look inside `turbo.json`, at the root:

```json filename="turbo.json"
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
```

What we're seeing here is that we've _registered_ three tasks with `turbo`: `lint`, `dev` and `build`.
Every task registered inside `turbo.json` can be run with `turbo run <task>` (or `turbo <task>` for short).

<Callout type="error">
  Before we move on, let's try running a task called `hello` that _isn't_ registered in `turbo.json`:

```bash
turbo hello
```

You'll see an error in the terminal. Something resembling:

```
Could not find the following tasks in project: hello
```

That's worth remembering - **in order for `turbo` to run a task, it must be in `turbo.json`**.

</Callout>

Let's investigate the scripts we already have in place.

### 4. Linting with Turborepo

Try running our `lint` script:

```sh
turbo lint
```

You'll notice several things happen in the terminal.

1. Several scripts will be run at the same time, each prefixed with either `docs:lint`, `@repo/ui:lint` or `web:lint`.
2. They'll each succeed, and you'll see `3 successful` in the terminal.
3. You'll also see `0 cached, 3 total`. We'll cover what this means later.

The scripts that each run come from each workspace's `package.json`. Each workspace can optionally specify its own `lint` script:

```json filename="apps/web/package.json"
{
  "scripts": {
    "lint": "next lint"
  }
}
```

```json filename="apps/docs/package.json"
{
  "scripts": {
    "lint": "next lint"
  }
}
```

```json filename="packages/ui/package.json"
{
  "scripts": {
    "lint": "eslint \"**/*.ts*\""
  }
}
```

When we run `turbo lint`, Turborepo looks at each `lint` script in each workspace and runs it. For more details, see our [pipelines](/repo/docs/core-concepts/monorepos/running-tasks#defining-a-pipeline) docs.

#### Using the cache

Let's run our `lint` script one more time. You'll notice a few new things appear in the terminal:

1. `cache hit, replaying logs` appears for `docs:lint`, `web:lint` and `@repo/ui:lint`.
2. You'll see `3 cached, 3 total`.
3. The total runtime should be under `100ms`, and `>>> FULL TURBO` appears.

Something interesting just happened. Turborepo realised that **our code hadn't changed since the last time we ran the lint script**.

It had saved the logs from the previous run, so it just replayed them.

Let's try changing some code to see what happens. Make a change to a file inside `apps/docs`:

```diff filename="apps/docs/app/page.tsx"
import { Button } from "@repo/ui/button";
//       ^^^^^^         ^^^^^^^^^^^^^^^

export default function Page() {
  return (
    <>
      <Button appName="web" className={styles.button}>
-        Click me!
+        Click me now!
      </Button>
    <>
  );
}
```

Now, run the `lint` script again. You'll notice that:

1. `docs:lint` has a comment saying `cache miss, executing`. This means that `docs` is running its linting.
2. `2 cached, 3 total` appears at the bottom.

This means that **the results of our previous tasks were still cached**. Only the `lint` script inside `docs` actually ran - again, speeding things up. To learn more, check out our [caching docs](/repo/docs/core-concepts/caching).

### 5. Building with Turborepo

Let's try running our `build` script:

```bash
turbo build
```

You'll see similar outputs to when we ran our lint script. Only `apps/docs` and `apps/web` specify a `build` script in their `package.json`, so only those are run.

Take a look inside `build` in `turbo.json`. There's some interesting config there.

```json filename="turbo.json"
{
  "pipeline": {
    "build": {
      "outputs": [".next/**", "!.next/cache/**"]
    }
  }
}
```

You'll notice that some `outputs` have been specified. Declaring outputs will mean that when `turbo` finishes running your task, it'll save the output you specify in its cache.

Both `apps/docs` and `apps/web` are Next.js apps, and they output builds to the `./.next` folder.

Let's try something. Delete the `apps/docs/.next` build folder.

Run the `build` script again. You'll notice:

1. We hit `FULL TURBO` - the builds complete in under `100ms`.
2. The `.next` folder re-appears!

Turborepo cached the result of our previous build. When we ran the `build` command again, it restored the entire `.next/**` folder from the cache. To learn more, check out our docs on [cache outputs](/repo/docs/core-concepts/caching/what-to-cache).

### 6. Running dev scripts

Let's now try running `dev`.

```bash
turbo dev
```

You'll notice some information in the terminal:

1. Only two scripts will execute - `docs:dev` and `web:dev`. These are the only two workspaces which specify `dev`.
2. Both `dev` scripts are run simultaneously, starting your Next.js apps on ports `3000` and `3001`.
3. In the terminal, you'll see `cache bypass, force executing`.

Try quitting out of the script, and re-running it. You'll notice we don't go `FULL TURBO`. Why is that?

Take a look at `turbo.json`:

```json filename="turbo.json"
{
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
```

Inside `dev`, we've specified `"cache": false`. This means we're telling Turborepo _not_ to cache the
results of the `dev` script. `dev` runs a persistent dev server and produces no outputs, so there
is nothing to cache. Learn more about it in our docs on [turning off caching](/repo/docs/core-concepts/caching#turn-off-caching).

Additionally, we set `"persistent": true`, to let turbo know that this is a long-running dev server,
so that turbo can ensure that no other tasks depend on it. You can read more in the [docs for the
`persistent` option](/repo/docs/reference/configuration#persistent).

#### Running `dev` on only one workspace at a time

By default, `turbo dev` will run `dev` on all workspaces at once. But sometimes, we might only want to choose one workspace.

To handle this, we can add a `--filter` flag to our command.

```bash
turbo dev --filter docs
```

You'll notice that it now only runs `docs:dev`. Learn more about [filtering workspaces](/repo/docs/core-concepts/monorepos/filtering) from our docs.

### Summary

Well done! You've learned all about your new monorepo, and how Turborepo makes handling your tasks easier.

#### Next steps

- Need to add more tasks? Learn more about using [pipelines](/repo/docs/core-concepts/monorepos/running-tasks#defining-a-pipeline)
- Want to speed up your CI? Set up [remote caching](/repo/docs/core-concepts/remote-caching).
- Want some inspiration? Take a look at our directory of [examples](https://github.com/vercel/turbo/tree/main/examples)
