# Turbo > title="Set up Biome in a Turborepo" --- # Source: https://turbo.build/guides/tools/biome.md # Biome [Biome](https://biomejs.dev/) is a fast formatter for JavaScript, TypeScript, JSX, and JSON that saves CI and developer time. ## Using Biome with Turborepo Biome is a rare exception to most tools that are used with Turborepo because it is **so extraordinarily fast**. For this reason, we recommend using a [Root Task](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks) rather than creating separate scripts in each of your packages. Using Biome at the root of the project will result in cache misses for all tasks when you upgrade Biome versions or change configuration. If you prefer the tradeoff of higher cache hit ratios in these situations over less configuration, you can still use Biome in separate scripts like the other recommendations in our guides. ### Initialize Biome First, [follow the installation documentation to set up Biome](https://biomejs.dev/guides/getting-started/) in your repository. You'll then be able to create a script to use Biome in the root of your repository: ```json title="./package.json" { "scripts": { "format-and-lint": "biome check .", "format-and-lint:fix": "biome check . --write" } } ``` ### Create a root task In practice, Biome is unlikely to be a bottleneck in the iteration speed of your repository. For this reason, we can have less configuration to manage in our repository by using Biome in a [Root Task](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks). If you believe Biome may be faster in your repository split up into tasks in packages, you are free to do so. We encourage you to experiment with what's best for your use case. To create a [Root Task](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks), register the scripts to Turborepo: ```json title="./turbo.json" { "tasks": { "//#format-and-lint": {}, "//#format-and-lint:fix": { "cache": false } } } ``` You'll now be able to run these scripts using `turbo run format-and-lint` and `turbo run format-and-lint:fix`. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/buildkite.md # Buildkite The following example shows how to use Turborepo with [Buildkite](https://buildkite.com/). For a given root `package.json`: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build", "test": "turbo run test" }, "devDependencies": { "turbo": "latest" } } ``` And a `turbo.json`: ```json title="./turbo.json" { "$schema": "https://turborepo.dev/schema.json", "tasks": { "build": { "outputs": [".next/**", "!.next/cache/**"], "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` Create a file called `.buildkite/pipeline.yml` in your repository with the following contents: ```yaml title=".buildkite/pipeline.yml" steps: - label: ":test_tube: Test" command: | pnpm install pnpm test - label: ":hammer: Build" command: | pnpm install pnpm build ``` ```yaml title=".buildkite/pipeline.yml" steps: - label: ":test_tube: Test" command: | yarn yarn test - label: ":hammer: Build" command: | yarn yarn build ``` ```yaml title=".buildkite/pipeline.yml" steps: - label: ":test_tube: Test" command: | npm install npm test - label: ":hammer: Build" command: | npm install npm run build ``` ```yaml title=".buildkite/pipeline.yml" steps: - label: ":test_tube: Test" command: | bun install bun run test - label: ":hammer: Build" command: | bun install bun run build ``` ## Create a Pipeline To create your pipeline in the Buildkite dashboard, you'll need to first upload the pipeline definition from your repository. 1. Select **Pipelines** to navigate to the Buildkite dashboard. 2. Select **New pipeline**. 3. Enter your pipeline's details in the respective **Name** and **Description** fields. 4. In the **Steps** editor, ensure there's a step to upload the definition from your repository: ```yaml title=".buildkite/pipeline.yml" steps: - label: ":pipeline:" command: buildkite-agent pipeline upload ``` 5. Select **Create Pipeline**, then click **New Build**, then select **Create Build**. Run the pipeline whenever you make changes you want to verify. ## Remote Caching To use Remote Caching, retrieve the team and token for the Remote Cache for your provider. In this example, we'll use [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching): * `TURBO_TOKEN` - The Bearer token to access the Remote Cache * `TURBO_TEAM` - The account to which the monorepo belongs To use Vercel Remote Caching, you can get the value of these variables in a few steps: 1. Create a Scoped Access Token to your account in the [Vercel Dashboard](https://vercel.com/account/tokens). Copy the value to a safe place. You'll need it in a moment. Vercel Access Tokens 2. Obtain [your Team URL](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings\&title=Find+Team+URL) and copy its value as well. Both values will be used in the next step. 3. In the Buildkite dashboard, create two new [Buildkite secrets](https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets), one for each value. Name them `TURBO_TOKEN` and `TURBO_TEAM`. 4. Update `pipeline.yml` to fetch and apply `TURBO_TOKEN` and `TURBO_TEAM` as environment variables with the [Buildkite Secrets](https://github.com/buildkite-plugins/secrets-buildkite-plugin) plugin as shown. (For additional secret-management options, read [Managing pipeline secrets](https://buildkite.com/docs/pipelines/security/secrets/managing) in the Buildkite documentation.) ```yaml title=".buildkite/pipeline.yml" steps: - label: ":test_tube: Test" command: | npm install npm test plugins: - secrets: variables: TURBO_TOKEN: TURBO_TOKEN TURBO_TEAM: TURBO_TEAM - label: ":hammer: Build" command: | npm install npm run build plugins: - secrets: variables: TURBO_TOKEN: TURBO_TOKEN TURBO_TEAM: TURBO_TEAM ``` Commit and push these changes to your repository, and on the next pipeline run, the secrets will be applied and Vercel Remote Caching will be active. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/circleci.md # CircleCI The following example shows how to use Turborepo with [CircleCI](https://circleci.com/). CircleCI [uses interactive terminals (TTY)](https://discuss.circleci.com/t/reprise-allow-for-using-circle-ci-tooling-without-a-tty/23309) that crash Turborepo's terminal UI. To workaround this, set the `TURBO_UI=false` environment variable in your CircleCI configuration. For a given root `package.json`: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build", "test": "turbo run test" }, "devDependencies": { "turbo": "latest" } } ``` And a `turbo.json`: ```json title="./turbo.json" { "$schema": "https://turborepo.dev/schema.json", "tasks": { "build": { "outputs": [".next/**", "!.next/cache/**"], "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` Create a file called `.circleci/config.yml` in your repository with the following contents: ```yaml title=".circleci/config.yml" version: 2.1 orbs: node: circleci/node@5.0.2 workflows: test: jobs: - test jobs: test: docker: - image: cimg/node:lts steps: - checkout - node/install-packages - run: command: npm i -g pnpm environment: TURBO_UI: "false" - run: command: pnpm build environment: TURBO_UI: "false" - run: command: pnpm test environment: TURBO_UI: "false" ``` ```yaml title=".circleci/config.yml" version: 2.1 orbs: node: circleci/node@5.0.2 workflows: test: jobs: - test jobs: test: docker: - image: cimg/node:lts steps: - checkout - node/install-packages: pkg-manager: yarn - run: command: yarn build environment: TURBO_UI: "false" - run: command: yarn test environment: TURBO_UI: "false" ``` ```yaml title=".circleci/config.yml" version: 2.1 orbs: node: circleci/node@5.0.2 workflows: test: jobs: - test jobs: test: docker: - image: cimg/node:lts steps: - checkout - node/install-packages - run: command: npm run build environment: TURBO_UI: "false" - run: command: npm run test environment: TURBO_UI: "false" ``` ```yaml title=".circleci/config.yml" version: 2.1 orbs: node: circleci/node@5.0.2 workflows: test: jobs: - test jobs: test: docker: - image: cimg/node:lts steps: - checkout - node/install-packages - run: command: npm i -g bun environment: TURBO_UI: "false" - run: command: bun run build environment: TURBO_UI: "false" - run: command: bun run test environment: TURBO_UI: "false" ``` ## Remote Caching To use Remote Caching, retrieve the team and token for the Remote Cache for your provider. In this example, we'll use [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching): * `TURBO_TOKEN` - The Bearer token to access the Remote Cache * `TURBO_TEAM` - The slug of the Vercel team to share the artifacts with To use Vercel Remote Caching, you can get the value of these variables in a few steps: 1. Create a Scoped Access Token to your account in the [Vercel Dashboard](https://vercel.com/account/tokens) Vercel Access Tokens Copy the value to a safe place. You'll need it in a moment. 2. Go to your CircleCI project settings and click on the **Environment Variables** tab. Create a new secret called `TURBO_TOKEN` and enter the value of your Scoped Access Token. CircleCI Environment Variables CircleCI Create Environment Variables 3. Make a second secret called `TURBO_TEAM` and set it to your team slug - the part after `vercel.com/` in [your Team URL](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings\&title=Find+Team+URL). For example, the slug for `vercel.com/acme` is `acme`. 4. CircleCI automatically loads environment variables stored in project settings into the CI environment. No modifications are necessary for the CI file. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/docker.md # Docker Building a [Docker](https://www.docker.com/) image is a common way to deploy all sorts of applications. However, doing so from a monorepo has several challenges. ## The problem **In a monorepo, unrelated changes can make Docker do unnecessary work when deploying your app.** Let's imagine you have a monorepo that looks like this: You want to deploy `apps/api` using Docker, so you create a Dockerfile: ```docker title="./apps/api/Dockerfile" FROM node:16 WORKDIR /usr/src/app # Copy root package.json and lockfile COPY package.json ./ COPY package-lock.json ./ # Copy the api package.json COPY apps/api/package.json ./apps/api/package.json RUN npm install # Copy app source COPY . . EXPOSE 8080 CMD [ "node", "apps/api/server.js" ] ``` This will copy the root `package.json` and the root lockfile to the Docker image. Then, it'll install dependencies, copy the app source and start the app. You should also create a `.dockerignore` file to prevent node\_modules from being copied in with the app's source. ```txt title=".dockerignore" node_modules npm-debug.log ``` ### The lockfile changes too often Docker is pretty smart about how it deploys your apps. Just like Turborepo, it tries to do as [little work as possible](https://bitjudo.com/blog/2014/03/13/building-efficient-dockerfiles-node-dot-js/). In our Dockerfile's case, it will only run `npm install` if the files it has in its image are *different* from the previous run. If not, it'll restore the `node_modules` directory it had before. This means that whenever `package.json`, `apps/api/package.json` or `package-lock.json` change, the Docker image will run `npm install`. This sounds great - until we realize something. The `package-lock.json` is *global* for the monorepo. That means that **if we install a new package inside `apps/web`, we'll cause `apps/api` to redeploy**. In a large monorepo, this can result in a huge amount of lost time, as any change to a monorepo's lockfile cascades into tens or hundreds of deploys. ## The solution The solution is to prune the inputs to the Dockerfile to only what is strictly necessary. Turborepo provides a simple solution - `turbo prune`. ```bash title="Terminal" turbo prune api --docker ``` Running this command creates a **pruned version of your monorepo** inside an `./out` directory. It only includes workspaces which `api` depends on. It also **prunes the lockfile** so that only the relevant `node_modules` will be downloaded. ### The `--docker` flag By default, `turbo prune` puts all relevant files inside `./out`. But to optimize caching with Docker, we ideally want to copy the files over in two stages. First, we want to copy over only what we need to install the packages. When running `--docker`, you'll find this inside `./out/json`. Afterwards, you can copy the files in `./out/full` to add the source files. Splitting up **dependencies** and **source files** in this way lets us **only run `npm install` when dependencies change** - giving us a much larger speedup. Without `--docker`, all pruned files are placed inside `./out`. ### Example Our detailed [`with-docker` example](https://github.com/vercel/turborepo/tree/main/examples/with-docker) goes into depth on how to use `prune` to its full potential. Here's the Dockerfile, copied over for convenience. Build the Dockerfile from the root of your monorepo: ```bash title="Terminal" docker build -f apps/web/Dockerfile . ``` This Dockerfile is written for a [Next.js](https://nextjs.org/) app that is using the `standalone` [output mode](https://nextjs.org/docs/pages/api-reference/next-config-js/output). ```docker title="./apps/web/Dockerfile" FROM node:18-alpine AS base RUN apk update RUN apk add --no-cache libc6-compat # Set working directory WORKDIR /app # --- FROM base AS prepare # Replace with the major version installed in your repository. For example: # RUN yarn global add turbo@^2 RUN yarn global add turbo@^ COPY . . # Add lockfile and package.json's of isolated subworkspace # Generate a partial monorepo with a pruned lockfile for a target workspace. # Assuming "web" is the name entered in the project's package.json: { name: "web" } RUN turbo prune web --docker # --- FROM base AS builder # First install the dependencies (as they change less often) COPY --from=prepare /app/out/json/ . RUN yarn install # Build the project COPY --from=prepare /app/out/full/ . # Uncomment and use build args to enable remote caching # ARG TURBO_TEAM # ENV TURBO_TEAM=$TURBO_TEAM # ARG TURBO_TOKEN # ENV TURBO_TOKEN=$TURBO_TOKEN RUN yarn turbo build # --- FROM base AS runner # Don't run production as root for security reasons RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public CMD node apps/web/server.js ``` ## Remote Caching To take advantage of remote caches during Docker builds, you will need to make sure your build container has credentials to access your [Remote Cache](/docs/core-concepts/remote-caching). There are many ways to take care of secrets in a Docker image. We will use a simple strategy here with multi-stage builds using secrets as build arguments that will get hidden for the final image. Assuming you are using a Dockerfile similar to the one above, we will bring in some environment variables from build arguments right before `turbo build`: ```docker title="./apps/api/Dockerfile" ARG TURBO_TEAM ENV TURBO_TEAM=$TURBO_TEAM ARG TURBO_TOKEN ENV TURBO_TOKEN=$TURBO_TOKEN RUN yarn turbo run build ``` `turbo` will now be able to hit your Remote Cache. To see a Turborepo cache hit for a non-cached Docker build image, run a command like this one from your project root: ```bash title="Terminal" docker build -f apps/web/Dockerfile . --build-arg TURBO_TEAM="your-team-name" --build-arg TURBO_TOKEN="your-token" --no-cache ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/eslint.md # ESLint ESLint is a static analysis tool for quickly finding and fixing problems in your JavaScript code. In this guide, we'll cover: * [ESLint v9 with Flat Configuration](#eslint-v9-flat-configs) * [ESLint v8 with legacy configuration](#eslint-v8-legacy) * [How to set up a `lint` task (applies to both versions)](#setting-up-a-lint-task) We will share configurations across the monorepo's Workspace, ensuring configuration is consistent across packages and composable to maintain high cache hit ratios. ## ESLint v9 (Flat Configs) Using ESLint v9's Flat Configs, we will end up with a file structure like this: This structure includes: * A package called `@repo/eslint-config` in `./packages/eslint-config` that holds all ESLint configuration * Two applications, each with their own `eslint.config.js` * A `ui` package that also has its own `eslint.config.js` ### About the configuration package The `@repo/eslint-config` package has three configuration files, `base.js`, `next.js`, and `react-internal.js`. They are [exported from `package.json`](https://github.com/vercel/turborepo/blob/main/examples/basic/packages/eslint-config/package.json#L6) so that they can be used by other packages, according to needs. Examples of the configurations can be found [in the Turborepo GitHub repository](https://github.com/vercel/turborepo/tree/main/examples/basic/packages/eslint-config) and are available in `npx create-turbo@latest`. Notably, the `next.js` and `react-internal.js` configurations use the `base.js` configuration for consistency, extending it with more configuration for their respective requirements. Additionally, notice that [the `package.json` for `eslint-config`](https://github.com/vercel/turborepo/blob/main/examples/basic/packages/eslint-config/package.json) has all of the ESLint dependencies for the repository. This is useful, since it means we don't need to re-specify the dependencies in the packages that import `@repo/eslint-config`. ### Using the configuration package In our `web` app, we first need to add `@repo/eslint-config` as a dependency. ```jsonc title="./apps/web/package.json" { "devDependencies": { "@repo/eslint-config": "workspace:*" } } ``` ```jsonc title="./apps/web/package.json" { "devDependencies": { "@repo/eslint-config": "*" } } ``` ```jsonc title="./apps/web/package.json" { "devDependencies": { "@repo/eslint-config": "*" } } ``` ```jsonc title="./apps/web/package.json" { "devDependencies": { "@repo/eslint-config": "workspace:*" } } ``` We can then import the configuration like this: ```js title="./apps/web/eslint.config.js" /** @type {import("eslint").Linter.Config} */ export default nextJsConfig; ``` Additionally, you can add configuration specific to the package like this: ```js title="./apps/web/eslint.config.js" /** @type {import("eslint").Linter.Config} */ export default [ ...nextJsConfig, // Other configurations ]; ``` ## ESLint v8 (Legacy) ESLint v8 is end-of-life as of October 5, 2024. We encourage you to upgrade to ESLint v9 or later. This documentation is here to help with existing projects that have not yet upgraded. Using legacy configuration from ESLint v8 and lower, we will end up with a file structure like this: There's a package called `@repo/eslint-config`, and two applications, each with their own `.eslintrc.js`. ### The `@repo/eslint-config` package The `@repo/eslint-config` file contains two files, `next.js`, and `library.js`. These are two different ESLint configurations, which we can use in different packages, depending on our needs. A configuration for Next.js may look like this: ```js title="./packages/eslint-config/next.js" /* Custom ESLint configuration for use with Next.js apps. */ module.exports = { extends: [ "eslint-config-turbo", "eslint-config-next", // ...your other ESLint configurations ].map(require.resolve), // ...your other configuration }; ``` The `package.json` looks like this: ```json title="./packages/eslint-config/package.json" { "name": "@repo/eslint-config", "version": "0.0.0", "private": true, "devDependencies": { "eslint": "^8", "eslint-config-turbo": "latest", "eslint-config-next": "latest" } } ``` Note that the ESLint dependencies are all listed here. This is useful, since it means we don't need to re-specify the dependencies inside the apps which import `@repo/eslint-config`. ### How to use the `@repo/eslint-config` package In our `web` app, we first need to add `@repo/eslint-config` as a dependency. ```jsonc title="./apps/web/package.json" { "dependencies": { "@repo/eslint-config": "workspace:*" } } ``` ```jsonc title="./apps/web/package.json" { "dependencies": { "@repo/eslint-config": "*" } } ``` ```jsonc title="./apps/web/package.json" { "dependencies": { "@repo/eslint-config": "*" } } ``` ```jsonc title="./apps/web/package.json" { "dependencies": { "@repo/eslint-config": "workspace:*" } } ``` We can then import the config like this: ```js title="./apps/web/.eslintrc.js" module.exports = { root: true, extends: ["@repo/eslint-config/next.js"], }; ``` By adding `@repo/eslint-config/next.js` to our `extends` array, we're telling ESLint to look for a package called `@repo/eslint-config`, and reference the file `next.js`. ## Setting up a `lint` task The `package.json` for each package where you'd like to run ESLint should look like this: ```json title="./packages/*/package.json" { "scripts": { "lint": "eslint ." } } ``` With your scripts prepared, you can then create your Turborepo task: ```json title="./turbo.json" { "tasks": { "lint": { "dependsOn": ["^lint"] } } } ``` Using `dependsOn` with `^lint` ensures that changes to dependencies like `@repo/eslint-config` will invalidate the cache for your `lint` task, even though the configuration package doesn't have a `lint` script itself. You can now run `turbo lint` with [global `turbo`](/docs/getting-started/installation#global-installation) or create a script in your root `package.json`: ```json title="./package.json" { "scripts": { "lint": "turbo run lint" } } ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/github-actions.md # GitHub Actions The following example shows how to use Turborepo with [GitHub Actions](https://github.com/features/actions). For a given root `package.json`: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build", "test": "turbo run test" }, "devDependencies": { "turbo": "latest" } } ``` And a `turbo.json`: ```json title="./turbo.json" { "$schema": "https://turborepo.dev/schema.json", "tasks": { "build": { "outputs": [".next/**", "!.next/cache/**", "other-output-dirs/**"], "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` Create a file called `.github/workflows/ci.yml` in your repository with the following contents: ```yaml title=".github/workflows/ci.yml" name: CI on: push: branches: ["main"] pull_request: types: [opened, synchronize] jobs: build: name: Build and Test timeout-minutes: 15 runs-on: ubuntu-latest # To use Remote Caching, uncomment the next lines and follow the steps below. # env: # TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} # TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 2 - uses: pnpm/action-setup@v3 with: version: 8 - name: Setup Node.js environment uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Test run: pnpm test ``` ```yaml title=".github/workflows/ci.yml" name: CI on: push: branches: ["main"] pull_request: types: [opened, synchronize] jobs: build: name: Build and Test timeout-minutes: 15 runs-on: ubuntu-latest # To use Remote Caching, uncomment the next lines and follow the steps below. # env: # TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} # TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup Node.js environment uses: actions/setup-node@v4 with: node-version: 20 cache: 'yarn' - name: Install dependencies run: yarn - name: Build run: yarn build - name: Test run: yarn test ``` ```yaml title=".github/workflows/ci.yml" name: CI on: push: branches: ["main"] pull_request: types: [opened, synchronize] jobs: build: name: Build and Test timeout-minutes: 15 runs-on: ubuntu-latest # To use Remote Caching, uncomment the next lines and follow the steps below. # env: # TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} # TURBO_TEAM: ${{ vars.TURBO_TEAM }} # TURBO_REMOTE_ONLY: true steps: - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 2 - name: Setup Node.js environment uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' - name: Install dependencies run: npm install - name: Build run: npm run build - name: Test run: npm run test ``` ```yaml title=".github/workflows/ci.yml" name: CI on: push: branches: ["main"] pull_request: types: [opened, synchronize] jobs: build: name: Build and Test timeout-minutes: 15 runs-on: ubuntu-latest # To use Remote Caching, uncomment the next lines and follow the steps below. # env: # TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} # TURBO_TEAM: ${{ vars.TURBO_TEAM }} steps: - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 2 - uses: oven-sh/setup-bun@v2 - name: Setup Node.js environment uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: bun install - name: Build run: bun run build - name: Test run: bun run test ``` ## Remote Caching with Vercel Remote Cache To use Remote Caching with GitHub Actions, add the following environment variables to your GitHub Actions workflow to make them available to your `turbo` commands. * `TURBO_TOKEN` - The Bearer token to access the Remote Cache * `TURBO_TEAM` - The slug of the Vercel team to share the artifacts with To use Remote Caching, retrieve the team and token for the Remote Cache for your provider. In this example, we'll use [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching). Create a Scoped Access Token to your account in the [Vercel Dashboard](https://vercel.com/account/tokens) Vercel Access Tokens Copy the value to a safe place. You'll need it in a moment. Go to your GitHub repository settings and click on the **Secrets** and then **Actions** tab. Create a new secret called `TURBO_TOKEN` and enter the value of your Scoped Access Token. GitHub Secrets GitHub Secrets Create Create a new repository variable (click the **Variables** tab) called `TURBO_TEAM` and set it to your team slug - the part after `vercel.com/` in [your Team URL](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings\&title=Find+Team+URL). For example, the slug for `vercel.com/acme` is `acme`. Using a repository variable rather than a secret will keep GitHub Actions from censoring your team name in log output. GitHub Repository Variables At the top of your GitHub Actions workflow, provide the following environment variables to jobs that use `turbo`: ```yaml title=".github/workflows/ci.yml" # ... jobs: build: name: Build and Test timeout-minutes: 15 runs-on: ubuntu-latest # To use Turborepo Remote Caching, set the following environment variables for the job. env: # [!code highlight] TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} # [!code highlight] TURBO_TEAM: ${{ vars.TURBO_TEAM }} # [!code highlight] steps: - name: Check out code uses: actions/checkout@v4 with: fetch-depth: 2 # ... ``` ## Using Remote Caching with Reusable Workflows For information on passing secrets to reusable workflows, see [GitHub's documentation on passing secrets to nested workflows](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows#passing-secrets-to-nested-workflows). ## Remote Caching with GitHub actions/cache The following steps show how you could use [actions/cache](https://github.com/actions/cache) to cache your monorepo artifacts on GitHub. Supply a package.json script that will run tasks using Turborepo. Example `package.json` with a `build` script: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build" }, "devDependencies": { "turbo": "1.2.5" } } ``` Configure your GitHub pipeline with a step which uses the `actions/cache@v4` action before the build steps of your CI file. * Make sure that the `path` attribute set within the `actions/cache` action matches the output location above. In the example below, `path` was set to `.turbo`. * State the cache key for the current run under the `key` attribute. In the example below, we used a combination of the runner os and GitHub sha as the cache key. * State the desired cache prefix pattern under the `restore-keys` attribute. Make sure this pattern will remain valid for future ci runs. In the example below, we used the `${{ runner.os }}-turbo-` as the cache key prefix pattern to search against. This allows us to hit the cache on any subsequent ci runs despite `github.sha` changing. Example `ci` yaml with `.turbo` as chosen cache folder: ```yaml title=".github/workflows/ci.yml" jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 - name: Cache turbo build setup # [!code highlight] uses: actions/cache@v4 # [!code highlight] with: # [!code highlight] path: .turbo # [!code highlight] key: ${{ runner.os }}-turbo-${{ github.sha }} # [!code highlight] restore-keys: | # [!code highlight] ${{ runner.os }}-turbo- - name: Setup Node.js environment uses: actions/setup-node@v4 with: node-version: 20 cache: "npm" - name: Install dependencies run: npm install - name: Build run: npm run build ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/gitlab-ci.md # GitLab CI The following example shows how to use Turborepo with [GitLab CI](https://docs.gitlab.com/ee/ci/). For a given root `package.json`: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build", "test": "turbo run test" }, "devDependencies": { "turbo": "latest" } } ``` And a `turbo.json`: ```json title="./turbo.json" { "$schema": "https://turborepo.dev/schema.json", "tasks": { "build": { "outputs": [".svelte-kit/**"], "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` Create a file called `.gitlab-ci.yml` in your repository with the following contents: ```yaml title=".gitlab-ci.yml" image: node:latest stages: - build build: stage: build before_script: - curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6.32.2 - pnpm config set store-dir .pnpm-store script: - pnpm install - pnpm build - pnpm test cache: key: files: - pnpm-lock.yaml paths: - .pnpm-store ``` > For more information visit the pnpm documentation section on GitLab CI integration, view it [here](https://pnpm.io/continuous-integration#gitlab) ```yaml title=".gitlab-ci.yml" image: node:latest stages: - build build: stage: build script: - yarn install - yarn build - yarn test cache: paths: - node_modules/ - .yarn ``` ```yaml title=".gitlab-ci.yml" image: node:latest stages: - build build: stage: build script: - npm install - npm run build - npm run test ``` ```yaml title=".gitlab-ci.yml" default: image: oven/bun:1.2 cache: key: files: - bun.lock paths: - node_modules/ before_script: - bun install build: script: - bun run build test: script: - bun run test ``` ## Remote Caching To use Remote Caching, retrieve the team and token for the Remote Cache for your provider. In this example, we'll use [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching): * `TURBO_TOKEN` - The Bearer token to access the Remote Cache * `TURBO_TEAM` - The slug of the Vercel team to share the artifacts with To use Vercel Remote Caching, you can get the value of these variables in a few steps: 1. Create a Scoped Access Token to your account in the [Vercel Dashboard](https://vercel.com/account/tokens) Vercel Access Tokens Copy the value to a safe place. You'll need it in a moment. 2. Go to your GitLab repository settings and click on the **Settings** and then **CI/CD** tab. Create a new variable called `TURBO_TOKEN` and enter the value of your Scoped Access Token. GitLab CI Variables GitLab CI Create Variable 3. Make a second secret called `TURBO_TEAM` and set it to your team slug - the part after `vercel.com/` in [your Team URL](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings\&title=Find+Team+URL). For example, the slug for `vercel.com/acme` is `acme`. Remote Caching will now be operational in your GitLab workflows. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/messages/invalid-env-prefix.md # Invalid environment variable prefix ## Why this error occurred When declaring environment variables in your `turbo.json`, you cannot prefix them with `$`. This was an old syntax for declaring a dependency on an environment variable that was deprecated in Turborepo 1.5. ```json title="./turbo.json" { "globalEnv": ["$MY_ENV_VAR"] } ``` The environment variable declared above has the `$` prefix. ## Solution Remove the `$` prefix from your environment variable declaration. ```json title="./turbo.json" { "globalEnv": ["MY_ENV_VAR"] } ``` You can migrate to the `env` and `globalEnv` keys using `npx @turbo/codemod migrate-env-var-dependencies`. Check out [the codemod's documentation for more details](/docs/reference/turbo-codemod#turborepo-1x). --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/jest.md # Jest [Jest](https://jestjs.io/) is a common test runner with a vast ecosystem. Integrating with Turborepo will lead to enormous speed-ups. ## Setting up Let's say we have a monorepo that looks like this: Install `jest` into the packages where you plan on having test suites. For this example, we will have tests in `web` and `@repo/ui`: ```bash title="Terminal" pnpm add jest --save-dev --filter=@repo/ui --filter=web ``` ```bash title="Terminal" yarn workspace web add jest --dev yarn workspace @repo/ui add jest --dev ``` ```bash title="Terminal" npm install jest --workspace=web --workspace=@repo/ui --save-dev ``` ```bash title="Terminal" cd apps/web && bun install jest --dev cd packages/ui && bun install jest --dev ``` Both the `apps/web` and `packages/ui` have their own test suites, so we'll add a `test` script to their `package.json`: ```json title="./apps/web/package.json" { "name": "web", "scripts": { "test": "jest" }, "devDependencies": { "jest": "latest" } } ``` ```json title="./packages/ui/package.json" { "name": "@repo/ui", "scripts": { "test": "jest" }, "devDependencies": { "jest": "latest" } } ``` Inside the root `turbo.json`, create a `test` task: ```json title="./turbo.json" { "tasks": { "test": {} } } ``` Now, `turbo test` can parallelize and cache all of the test suites from each package, only testing code that has changed. ## Running tests in watch mode When you run your test suite normally, it completes and outputs to `stdout`. This means you can [cache it](/docs/crafting-your-repository/caching) with Turborepo. But when you run your tests in a watched mode, the process never exits. This makes a watch task more like a [development task](/docs/crafting-your-repository/developing-applications). Because of this difference, we recommend specifying **two separate Turborepo tasks**: one for running your tests, and one for running them in Jest's watch mode. Inside your each `package.json` file for each workspace: ```json title="./apps/web/package.json" { "name": "web", "scripts": { "test": "jest", "test:watch": "jest --watch" // [!code highlight] }, "devDependencies": { "jest": "latest" } } ``` ```json title="./packages/ui/package.json" { "name": "@repo/ui", "scripts": { "test": "jest", "test:watch": "jest --watch" // [!code highlight] }, "devDependencies": { "jest": "latest" } } ``` Inside the root `turbo.json`: ```json title="./turbo.json" { "tasks": { "test": {}, "test:watch": { "cache": false, // [!code highlight] "persistent": true // [!code highlight] } } } ``` You can now either run this task using [global `turbo`](/docs/getting-started/installation#global-installation) as `turbo test:watch` or from a script in your root `package.json`: ```bash title="Terminal" turbo test ``` ```bash title="Terminal" turbo test:watch ``` ```json title="./package.json" { "scripts": { "test": "turbo run test", "test:watch": "turbo run test:watch" } } ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/messages/missing-root-task-in-turbo-json.md # Missing root task in turbo.json ## Why this error occurred Root tasks are the scripts defined in the monorepo's root `package.json`. These tasks often call `turbo`. For example: ```json title="./package.json" { "scripts": { "build": "turbo run build" } } ``` This creates a problem when we declare [topological dependencies](/docs/reference/configuration#dependson). Topological dependencies specify that your package's dependencies should execute their tasks before your package executes its own task. ```json title="./turbo.json" { "tasks": { "build": { "dependsOn": ["^build"] } } } ``` Because the root package is a dependency for all packages inside your workspace, its task would get executed first. But since its task calls `turbo`, this would cause an infinite loop. ## Solution As long as the root task does *not* call `turbo`, you can add it to the `tasks` field in `turbo.json`: ```json title="./turbo.json" { "tasks": { "//#build": {} } } ``` This will permit tasks to depend on `//#build`. However, if the root task does call `turbo`, this can cause infinite recursion. In this case, we don't recommend depending on the root task. Instead, you can determine the tasks that this root task depends on, and depend on those directly. For instance, if `//#build` depends on `app#lint` and `docs#lint`, then you can declare those as dependencies. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/oxc.md # Oxc (oxlint and oxfmt) [Oxc](https://oxc.rs/) is a collection of high-performance JavaScript and TypeScript tools written in Rust, including [oxlint](https://oxc.rs/docs/guide/usage/linter) (a fast linter) and [oxfmt](https://oxc.rs/docs/guide/usage/formatter) (a fast formatter). ## Using Oxc tools with Turborepo Similar to [Biome](/docs/guides/tools/biome), oxlint and oxfmt are **extraordinarily fast** tools. For this reason, we recommend using [Root Tasks](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks) rather than creating separate scripts in each of your packages. Using oxlint or oxfmt at the root of the project will result in cache misses for all tasks when you upgrade versions or change configuration. If you prefer the tradeoff of higher cache hit ratios in these situations over less configuration, you can still run these tools in separate scripts like the other recommendations in our guides. ## Setting up oxlint ### Install oxlint First, install oxlint in your repository: ```bash title="Terminal" pnpm add --save-dev oxlint ``` ```bash title="Terminal" yarn add --dev oxlint ``` ```bash title="Terminal" npm install --save-dev oxlint ``` ```bash title="Terminal" bun add --dev oxlint ``` ### Create scripts Add scripts to the root `package.json` of your repository: ```json title="./package.json" { "scripts": { "lint": "oxlint .", "lint:fix": "oxlint --fix ." } } ``` ### Create root tasks Register the scripts to Turborepo as [Root Tasks](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks): ```json title="./turbo.json" { "tasks": { "//#lint": {}, "//#lint:fix": { "cache": false } } } ``` You can now run `turbo run lint` to lint your entire repository. ## Setting up oxfmt oxfmt is a fast code formatter for JavaScript and TypeScript, designed to be a drop-in replacement for Prettier. oxfmt is currently in alpha and may not have full feature parity with Prettier. Check the [oxfmt documentation](https://oxc.rs/docs/guide/usage/formatter) for the latest status and supported options. ### Install oxfmt Install oxfmt as a dev dependency: ```bash title="Terminal" pnpm add --save-dev oxfmt ``` ```bash title="Terminal" yarn add --dev oxfmt ``` ```bash title="Terminal" npm install --save-dev oxfmt ``` ```bash title="Terminal" bun add --dev oxfmt ``` ### Create scripts Add formatting scripts to the root `package.json`: ```json title="./package.json" { "scripts": { "format": "oxfmt --check", "format:fix": "oxfmt ." } } ``` ### Create root tasks Register the scripts to Turborepo: ```json title="./turbo.json" { "tasks": { "//#format": {}, "//#format:fix": { "cache": false } } } ``` You can now run `turbo run format` to check formatting and `turbo run format:fix` to format your code. ## Using oxlint and oxfmt together For repositories using both tools, you can orchestrate them with a unified quality task: ```json title="./package.json" { "scripts": { "lint": "oxlint .", "lint:fix": "oxlint --fix .", "format": "oxfmt --check", "format:fix": "oxfmt ." } } ``` ```json title="./turbo.json" { "tasks": { "//#quality": { "dependsOn": ["//#lint", "//#format"] }, "//#quality:fix": { "dependsOn": ["//#lint:fix", "//#format:fix"] }, "//#lint": {}, "//#lint:fix": { "cache": false }, "//#format": {}, "//#format:fix": { "cache": false } } } ``` With this configuration: * Run `turbo run quality` to check both linting and formatting * Run `turbo run quality:fix` to fix both linting and formatting issues --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/messages/package-task-in-single-package-workspace.md # Package task in single-package workspace error ## Why this error occurred In single package mode, there cannot be multiple packages in your repository. Therefore, declaring a task in the `turbo.json` with a specified package name is not permitted. ```json title="./turbo.json" { "tasks": { "app#build": { "cache": true } } } ``` ## Solution Remove the package name from the task declaration. ```json title="./turbo.json" { "tasks": { "build": { "cache": true } } } ``` Alternatively, if you would like to have multiple packages, you can [specify the workspaces in your repository](/docs/getting-started/add-to-existing-repository). --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/playwright.md # Playwright [Playwright](https://playwright.dev/) enables reliable end-to-end testing for modern web apps. We recommend creating a Playwright package for each test suite that you'd like to run in your monorepo. This may mean suites broken up per-application, per-domain, or some other scheme, according to your needs. If you are unsure, start with creating a Playwright package per-application. ## Handling Playwright's environment variables Playwright requires several environment variables to run correctly. To ensure that these variables are available within your tasks in [Strict Mode](/docs/crafting-your-repository/using-environment-variables#strict-mode), you'll want to add them to your pass through variables using [`passThroughEnv`](/docs/reference/configuration#passthroughenv) in your end-to-end task or [`globalPassThroughEnv`](/docs/reference/configuration#globalpassthroughenv) globally, depending on your scoping preferences. The configuration below using `passThroughEnv` will allow environment variables that start with `PLAYWRIGHT_` into **the `e2e` task** and **will not affect hashing**. ```json title="./turbo.json" { "tasks": { "e2e": { "passThroughEnv": ["PLAYWRIGHT_*"] } } } ``` The configuration below using `globalPassThroughEnv` will allow environment variables that start with `PLAYWRIGHT_` into **all tasks** and **will not affect hashing**. ```json title="./turbo.json" { "globalPassThroughEnv": ["PLAYWRIGHT_*"] } ``` Note that pass through variables are used since we don't want to miss cache in situations where these Playwright-internal variables change. For example,`PLAYWRIGHT_BROWSERS_PATH` is used to locate browser binaries used by Playwright, and we don't want to miss cache if this location changes. ## Designing the task graph We want to ensure the proper caching behavior for our end-to-end suites. Specifically, we want to make sure of cache misses in a few key situations: * If test suites change, cache gets missed. * If code tested by the suites changes, cache gets missed. The first requirement will be met naturally since the hash for the task will change when test code is changed. However, the second requirement means you need to ensure end-to-end tests depend on changes in application source code. This relationship can be expressed in `turbo.json` and the end-to-end suite's `package.json`. ```json title="./turbo.json" { "tasks": { "build": { "dependsOn": ["^build"] }, "e2e": { "dependsOn": ["^build"] // [!code highlight] } } } ``` ```json title="./tooling/playwright-web/package.json" { "name": "@repo/playwright-web", "dependencies": { "web": "workspace:*" // [!code highlight] } } ``` Later on, when you want to run your end-to-end tests, use [the `--only` flag](/docs/reference/run#--only) to run end-to-end tests without running the application's build first. As an example, your command may look like `turbo run e2e --filter=@repo/playwright-myapp --only`. ## Sharing Playwright utilities You can also create a common package for shared utilities that you need in your end-to-end test suites. We recommend using `peerDependencies` in this shared package so that you can get access to Playwright used in consumers without having to install Playwright into the shared package itself. ```json title="./packages/playwright-utilities/package.json" { "name": "@repo/playwright-utilities", "peerDependencies": { "playwright": "workspace:*" } } ``` ```json title="./packages/playwright-utilities/package.json" { "name": "@repo/playwright-utilities", "peerDependencies": { "playwright": "*" } } ``` ```json title="./packages/playwright-utilities/package.json" { "name": "@repo/playwright-utilities", "peerDependencies": { "playwright": "*" } } ``` ```json title="./packages/playwright-utilities/package.json" { "name": "@repo/playwright-utilities", "peerDependencies": { "playwright": "workspace:*" } } ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/prisma.md # Prisma [Prisma](https://www.prisma.io/) unlocks a new level of developer experience when working with databases thanks to its intuitive data model, automated migrations, type-safety & auto-completion. [Their official guide](https://www.prisma.io/docs/guides/turborepo) describes how to integrate Prisma into a Turborepo, including: * Prisma client initialization * Packaging the client as an [Internal Package](/docs/core-concepts/internal-packages) * Performing migrations * Working on your applications locally * Deploying ## Example To get started with our community-supported Prisma example, run: ```bash title="Terminal" npx create-turbo@latest -e with-prisma ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/messages/recursive-turbo-invocations.md # Recursive `turbo` invocations ## Why this error occurred When a cycle of `turbo` invocations is detected in a [single-package workspace](https://turborepo.dev/docs/guides/single-package-workspaces), Turborepo will error to prevent the recursive calls to itself. Typically, this situation occurs for one of two reasons: ### Recursion in scripts and tasks In a single-package workspace, a script in `package.json` that calls a Turborepo task with the same name causes a loop. ```json title="./package.json" { "scripts": { "build": "turbo run build" } } ``` Calling the `build` script calls `turbo run build`. `turbo run build` then calls the `build` script, initiating the loop of recursive calls. To resolve this, ensure that the name of the script in `package.json` is not the same as the Turborepo task. For example, to fix the snippet above, renaming the script would break the cycle: ```json title="./package.json" { "scripts": { "build:app": "turbo run build" } } ``` ### Package manager Workspace misconfiguration A misconfigured workspace can make it appear that a [multi-package workspace](https://vercel.com/docs/vercel-platform/glossary#multi-package-workspace) is a single-package workspace. This causes Turborepo to infer that the repository is of the wrong type, causing it to see the script in `package.json` to be recursive. Your repo can end up in this state in a few ways, with the most common being that the [packages are not defined according to your package manager](https://turborepo.dev/docs/crafting-your-repository/structuring-a-repository#specifying-packages-in-a-monorepo). An npm workspace that is missing the `workspaces` field in `package.json` or a pnpm workspace that is missing a `pnpm-workspace.yaml` file can result in this error message. Check that your repository is complying with standards for multi-package workspaces and correct any issues. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/shadcn-ui.md # shadcn/ui [shadcn/ui](https://ui.shadcn.com/docs/monorepo) is an open-source set of beautifully designed components made with Tailwind CSS that you can copy and paste into your apps. To get started with shadcn/ui in a new monorepo, run: ```bash title="Terminal" pnpm dlx shadcn@canary init ``` ```bash title="Terminal" npx shadcn@canary init ``` ```bash title="Terminal" npx shadcn@canary init ``` ```bash title="Terminal" bunx shadcn@canary init ``` When prompted, select the option for monorepos. To add a component, run: ```bash title="Terminal" pnpm dlx shadcn@canary add [COMPONENT] ``` ```bash title="Terminal" npx shadcn@canary add [COMPONENT] ``` ```bash title="Terminal" npx shadcn@canary add [COMPONENT] ``` ```bash title="Terminal" bunx shadcn@canary add [COMPONENT] ``` ## More information To learn more about using shadcn/ui in Turborepo, [visit the docs for shadcn/ui](https://ui.shadcn.com/docs/monorepo). --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/storybook.md # Storybook [Storybook](https://storybook.js.org/) is a popular way to build UI components in an isolated environment. By putting Storybook into your Turborepo, you can easily develop your design system right alongside your applications. ## Quickstart If you'd rather use a template, this guide is walking through how to build [this Storybook/Turborepo template](https://vercel.com/templates/react/turborepo-design-system) on Vercel. ```bash title="Terminal" pnpm dlx create-turbo@latest -e design-system ``` ```bash title="Terminal" yarn dlx create-turbo@latest -e design-system ``` ```bash title="Terminal" npx create-turbo@latest -e design-system ``` ```bash title="Terminal" bunx create-turbo@latest -e design-system ``` ## Guide ### Create a monorepo If you don't have an existing project, use [create-turbo](/docs/getting-started/installation) to create a new monorepo: ```bash title="Terminal" pnpm dlx create-turbo@latest ``` ```bash title="Terminal" yarn dlx create-turbo@latest ``` ```bash title="Terminal" npx create-turbo@latest ``` ```bash title="Terminal" bunx create-turbo@latest ``` ### Create a directory for the app You'll need a directory for the Storybook application: ```bash title="Terminal" mkdir apps/storybook cd apps/storybook ``` ### Add the Storybook application In the `apps/storybook` directory, initialize a new Storybook application: ```bash title="Terminal" pnpm create storybook@latest ``` ```bash title="Terminal" yarn create storybook@latest ``` ```bash title="Terminal" npm create storybook@latest ``` ```bash title="Terminal" bun create storybook@latest ``` Follow the prompts to create an application. For the rest of this guide, we'll assume React and TypeScript. After going through Storybook's onboarding, you can [uninstall the onboarding addon](https://github.com/storybookjs/addon-onboarding/blob/main/README.md). ### Add your UI kit to Storybook Now, install your UI package into Storybook. ```bash title="Terminal" pnpm add @repo/ui --filter=storybook ``` ```bash title="Terminal" yarn workspace storybook add @repo/ui ``` ```bash title="Terminal" npm install @repo/ui --workspace=storybook ``` ```bash title="Terminal" cd apps/storybook && bun install @repo/ui ``` ### Set up a story for your Button component Delete the stories and components found in `src/stories` that were created by the Storybook scaffolding tool. You will be making your own. As an example, here is a story for the `Button` component from `@repo/ui/button`. ```tsx title="./apps/storybook/src/stories/Button.stories.tsx" import type { Meta, StoryObj } from "@storybook/react"; import { Button } from "@repo/ui/button"; const meta = { title: "Example/Button", component: Button, tags: ["autodocs"], } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { appName: "Button", children: "I am a primary button.", }, }; ``` ### Align scripts to your tasks Last, integrate the new Storybook application into your Turborepo: ```json title="apps/storybook/package.json" { "scripts": { "dev": "storybook dev -p 6006", // [!code highlight] "build": "storybook build" // [!code highlight] } } ``` These scripts will now run with the `turbo dev` and `turbo build` tasks in your `turbo.json`. To ensure file outputs are cached when you run `build`, add `storybook-static` to the outputs of your `turbo.json` build task: ```diff title="turbo.json" { "tasks": { "build": { "outputs": [ ".next/**", "!.next/cache/**" + "storybook-static/**" ] } } } ``` ### Add Storybook build outputs to `.gitignore` Ensure that the build outputs for Storybook are not committed to source control ```diff title=".gitignore" + storybook-static ``` ### Verify your configuration Run `turbo build` to build the Storybook application alongside the rest of your applications. You can also run `turbo build` again to see cache hits for your builds. ## More tips ### Co-locating stories If you'd prefer to co-locate your stories to their source code (rather than having them in the Storybook application), you'll need some extra configuration. #### Re-configure Storybook sources In `.storybook/main.ts`, change the `stories` paths in `config` to the directories you'd like to capture. For instance, if you'd like to write stories in the UI package: ```diff title="./apps/storybook/.storybook/main.ts" const config = { stories: [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + "../../../packages/ui/src/**/*.stories.@(js|jsx|mjs|ts|tsx)", }; ``` #### Move story files to the UI package Following along with [the guide above](/docs/guides/tools/storybook#set-up-a-story-for-your-button-component), move the `./apps/storybook/src/stories/Button.stories.tsx` file to `./packages/ui/src/Button.stories.tsx`. Update components imports so that they reference the now co-located modules. For instance, in the story's imports: ```diff title="./packages/ui/src/Button.stories.tsx" - import { Button } from "@repo/ui/button"; + import { Button } from "./button"; ``` You may also need to update [absolute imports](/docs/guides/tools/typescript#use-nodejs-subpath-imports-instead-of-typescript-compiler-paths) according to your changes and usage. You'll also need to install any Storybook packages required for writing stories. For example, moving the story from above would require that you install `@storybook/react` into your `@repo/ui` package. ```bash title="Terminal" pnpm add @storybook/react --filter=@repo/ui --save-dev ``` ```bash title="Terminal" yarn workspace @repo/ui add @storybook/react --dev ``` ```bash title="Terminal" npm install @storybook/react --workspace=@repo/ui --save-dev ``` ```bash title="Terminal" cd packages/ui && bun install @storybook/react --dev ``` #### Configure caching Because stories are now in the UI package, changes to those stories can cause cache misses for any builds that depend on your UI package. However, changing a story doesn't mean your production applications should miss cache. To prevent this, exclude stories from the inputs to your `build` task in your root `turbo.json`. You'll also need to create a `build:storybook` task, which you'll need in a moment: ```json title="./turbo.json" { "tasks": { "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", "!**/*.stories.{tsx,jsx,mdx}"], // [!code highlight] "outputs": [".next/**", "!.next/cache/**"] }, "build:storybook": {} // [!code highlight] } } ``` Additionally, create a [Package Configuration](/docs/reference/package-configurations) in the `storybook` application so stories are **accounted for in building the Storybook application, specifically**: ```json title="./apps/storybook/turbo.json" { "extends": ["//"], "tasks": { "build:storybook": { "dependsOn": ["^build:storybook"], "outputs": ["storybook-static/**"] } } } ``` If you are using the [Compiled Package pattern](/docs/core-concepts/internal-packages#compiled-packages), you may also need to add `^build` to your `dependsOn`. #### Rename the build script Last, make sure your script to build Storybook uses the configuration we just wrote by renaming it to the name of the task: ```json title="apps/storybook/package.json" { "scripts": { "dev": "storybook dev -p 6006", "build:storybook": "storybook build" // [!code highlight] } } ``` The script that was once `"build"` is now `"build:storybook"` to ensure the stories are included in hashes for caching. #### Verify your configuration To ensure your setup is correct: 1. Run `turbo build:storybook build`. You should see cache misses. 2. Run `turbo build:storybook build` again. You should see all cache hits. 3. Make a code change **to a story** in your `@repo/ui` package. 4. Run `turbo build:storybook build` again. You should **only** see a cache miss for the Storybook application. All others should hit cache. ### Adding CSS If your UI package exports its own CSS, you'll need to add it to the renders in the Storybook app, similar to how you would add it to your applications. [The Storybook documentation](https://storybook.js.org/docs/configure/styling-and-css#css) recommends you add it to the `.storybook/preview.ts` file. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/tailwind.md # Tailwind CSS [Tailwind CSS](https://tailwindcss.com/) is a CSS framework that allows you to rapidly build modern websites without ever leaving your HTML. ## Quickstart If you'd rather use a template, this guide is walking through how to build [this Tailwind CSS + Turborepo template](https://github.com/vercel/turborepo/tree/main/examples/with-tailwind). ```bash title="Terminal" pnpm dlx create-turbo@latest -e with-tailwind ``` ```bash title="Terminal" yarn dlx create-turbo@latest -e with-tailwind ``` ```bash title="Terminal" npx create-turbo@latest -e with-tailwind ``` ```bash title="Terminal" bunx create-turbo@latest -e with-tailwind ``` ## Guide This guide is for Tailwind CSS v4. ### Create a monorepo If you don't have an existing project, use [create-turbo](/docs/getting-started/installation) to create a new monorepo: ```bash title="Terminal" pnpm dlx create-turbo@latest ``` ```bash title="Terminal" yarn dlx create-turbo@latest ``` ```bash title="Terminal" npx create-turbo@latest ``` ```bash title="Terminal" bunx create-turbo@latest ``` ### Add Tailwind CSS to your application [Follow Tailwind CSS's guides](https://tailwindcss.com/docs/installation/using-vite) to set up Tailwind CSS for your frontend framework. Once completed, you can start working on bringing your UI package into the applications. ### Create a shared Tailwind CSS configuration package First, build an [Internal Package](https://turborepo.dev/docs/core-concepts/internal-packages) with four files: This `package.json` installs Tailwind CSS so we can create the file shared styles and export for the rest of the repository. ```json title="./packages/tailwind-config/package.json" { "name": "@repo/tailwind-config", "version": "0.0.0", "type": "module", "private": true, "exports": { ".": "./shared-styles.css", "./postcss": "./postcss.config.js" }, "devDependencies": { "postcss": "^8.5.3", "tailwindcss": "^4.1.5" } } ``` This `shared-styles.css` file will be shared to the libraries and applications in the repository. The variables shown will be available anywhere that the file is included. ```css title="./packages/tailwind-config/shared-styles.css" @import "tailwindcss"; @theme { --blue-1000: #2a8af6; --purple-1000: #a853ba; --red-1000: #e92a67; } ``` If your frontends need a PostCSS configuration file, you can create one to share. ```js title="./packages/tailwind-config/postcss.config.js" export const postcssConfig = { plugins: { "@tailwindcss/postcss": {}, }, }; ``` ### Create the UI package You can now build the components to share to your applications. For a full example, [visit the source code for `@repo/ui` package in the Tailwind CSS example](https://github.com/vercel/turborepo/tree/main/examples/with-tailwind/packages/ui). The files required for your Tailwind CSS setup are below. The `package.json` installs the dependencies for the package, sets up scripts for development and build environments, and marks the exports for the package. ```json title="./packages/ui/package.json" { "exports": { "./styles.css": "./dist/index.css", "./*": "./dist/*.js" }, "scripts": { "build:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css", "build:components": "tsc", "dev:styles": "tailwindcss -i ./src/styles.css -o ./dist/index.css --watch", "dev:components": "tsc --watch" }, "devDependencies": { "@repo/tailwind-config": "workspace:*", "@tailwindcss/cli": "^4.1.5", "@tailwindcss/postcss": "^4.1.5", "autoprefixer": "^10.4.20", "tailwindcss": "^4.1.5" } } ``` Above, we've only included the code related to setting up Tailwind. The full package.json is [here](https://github.com/vercel/turborepo/tree/main/examples/with-tailwind/packages/ui/package.json). Create a `build` and `dev` task that runs the scripts for building of components and style sheets in parallel. ```json title="./packages/ui/turbo.json" { "extends": ["//"], "tasks": { "build": { "dependsOn": ["build:styles", "build:components"] }, "build:styles": { "outputs": ["dist/**"] }, "build:components": { "outputs": ["dist/**"] }, "dev": { "with": ["dev:styles", "dev:components"] }, "dev:styles": { "cache": false, "persistent": true }, "dev:components": { "cache": false, "persistent": true } } } ``` This `styles.css` contains component-level styles for the shared UI library. ```css title="./packages/ui/src/styles.css" /* Component-level styles for the UI package */ @import "tailwindcss" prefix(ui); ``` Tailwind classes used in this package should be prefixed with `ui:` to avoid style specificity issues. ### Use the UI package in an application Install the packages you've created into your application. ```bash title="Terminal" pnpm add @repo/ui @repo/tailwind-config --save-dev --filter=@repo/ui --filter=web ``` ```bash title="Terminal" yarn workspace web add @repo/ui @repo/tailwind-config --dev yarn workspace @repo/ui add @repo/ui @repo/tailwind-config --dev ``` ```bash title="Terminal" npm install @repo/ui @repo/tailwind-config --workspace=web --workspace=@repo/ui --save-dev ``` ```bash title="Terminal" cd apps/web && bun install @repo/ui @repo/tailwind-config --dev cd packages/ui && bun install @repo/ui @repo/tailwind-config --dev ``` Then, configure the files in your application so the styles from the UI package are reflected in the application. ```css title="./apps/web/app/globals.css" @import "tailwindcss"; @import "@repo/tailwind-config"; ``` ```tsx title="./apps/web/app/layout.tsx" import "@repo/ui/styles.css"; import "./globals.css"; ``` ```js title="./apps/web/postcss.config.js" import { postcssConfig } from "@repo/tailwind-config/postcss"; export default postcssConfig; ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/travis-ci.md # Travis CI The following example shows how to use Turborepo with [Travis CI](https://www.travis-ci.com/). For a given root `package.json`: ```json title="./package.json" { "name": "my-turborepo", "scripts": { "build": "turbo run build", "test": "turbo run test" }, "devDependencies": { "turbo": "latest" } } ``` And a `turbo.json`: ```json title="./turbo.json" { "$schema": "https://turborepo.dev/schema.json", "tasks": { "build": { "outputs": [".svelte-kit/**"], "dependsOn": ["^build"] }, "test": { "dependsOn": ["^build"] } } } ``` Create a file called `.travis.yml` in your repository with the following contents: ```yaml title=".travis.yml" language: node_js node_js: - lts/* cache: npm: false directories: - "~/.pnpm-store" before_install: - curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6.32.2 - pnpm config set store-dir ~/.pnpm-store install: - pnpm install script: - pnpm build script: - pnpm test ``` > For more information visit the pnpm documentation section on Travis CI integration, view it [here](https://pnpm.io/continuous-integration#travis) Travis CI detects the use of Yarn by the presence of `yarn.lock`. It will automatically ensure it is installed. ```yaml title=".travis.yml" language: node_js node_js: - lts/* install: - yarn script: - yarn build script: - yarn test ``` ```yaml title=".travis.yml" language: node_js node_js: - lts/* install: - npm install script: - npm run build script: - npm run test ``` ```yaml title=".travis.yml" language: node_js node_js: - lts/* cache: npm: false directories: - "~/.pnpm-store" before_install: - curl -fsSL https://bun.sh/install | bash install: - bun install script: - bun run build script: - bun run test ``` ## Remote Caching To use Remote Caching, retrieve the team and token for the Remote Cache for your provider. In this example, we'll use [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching): * `TURBO_TOKEN` - The Bearer token to access the Remote Cache * `TURBO_TEAM` - The slug of the Vercel team to share the artifacts with To use Vercel Remote Caching, you can get the value of these variables in a few steps: 1. Create a Scoped Access Token to your account in the [Vercel Dashboard](https://vercel.com/account/tokens) Vercel Access Tokens Copy the value to a safe place. You'll need it in a moment. 2. Go to your Travis repository settings and scroll down to the *Environment Variables* section. Create a new variable called `TURBO_TOKEN` and enter the value of your Scoped Access Token. Travis CI Variables 3. Make a second secret called `TURBO_TEAM` and set it to your team slug - the part after `vercel.com/` in [your Team URL](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fsettings\&title=Find+Team+URL). For example, the slug for `vercel.com/acme` is `acme`. 4. Travis CI automatically loads environment variables stored in project settings into the CI environment. No modifications are necessary for the CI file. --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/typescript.md # TypeScript TypeScript is an excellent tool in monorepos, allowing teams to safely add types to their JavaScript code. While there is some complexity to getting set up, this guide will walk you through the important parts of a TypeScript setup for most use cases. * [Sharing TypeScript configuration](#sharing-tsconfigjson) * [Building a TypeScript package](#building-a-typescript-package) * [Making type checking faster across your workspace](/docs/guides/tools/typescript#linting-your-codebase) This guide assumes you are using a recent version of TypeScript and uses some features that are only available in those versions. You may need to adjust the guidance on this page if you are unable to features from those versions. ## Sharing `tsconfig.json` You want to build consistency into your TypeScript configurations so that your entire repo can use great defaults and your fellow developers can know what to expect when writing code in the Workspace. TypeScript's `tsconfig.json` sets the configuration for the TypeScript compiler and features an [`extends` key](https://www.typescriptlang.org/tsconfig#extends) that you'll use to share configuration across your workspace. This guide will use [`create-turbo`](/docs/reference/create-turbo) as an example. ```bash title="Terminal" pnpm dlx create-turbo@latest ``` ```bash title="Terminal" yarn dlx create-turbo@latest ``` ```bash title="Terminal" npx create-turbo@latest ``` ```bash title="Terminal" bunx create-turbo@latest ``` ### Use a base `tsconfig` file Inside `packages/typescript-config`, you have a few `json` files which represent different ways you might want to configure TypeScript in various packages. The `base.json` file is extended by every other `tsconfig.json` in the workspace and looks like this: ```json title="./packages/typescript-config/base.json" { "compilerOptions": { "esModuleInterop": true, "skipLibCheck": true, "target": "es2022", "allowJs": true, "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, "strict": true, "noUncheckedIndexedAccess": true, "module": "NodeNext" } } ``` ### Creating the rest of the package The other `tsconfig` files in this package use the `extends` key to start with the base configuration and customize for specific types of projects, like for Next.js (`nextjs.json`) and a React library (`react-library.json`). Inside `package.json`, name the package so it can be referenced in the rest of the Workspace: ```json title="packages/typescript-config/package.json" { "name": "@repo/typescript-config" } ``` ## Building a TypeScript package ### Using the configuration package First, install the `@repo/typescript-config` package into your package: ```json title="./apps/web/package.json" { "devDependencies": { "@repo/typescript-config": "workspace:*", "typescript": "latest" } } ``` ```json title="./apps/web/package.json" { "devDependencies": { "@repo/typescript-config": "*", "typescript": "latest" } } ``` ```json title="./apps/web/package.json" { "devDependencies": { "@repo/typescript-config": "*", "typescript": "latest" } } ``` ```json title="./apps/web/package.json" { "devDependencies": { "@repo/typescript-config": "workspace:*", "typescript": "latest" } } ``` Then, extend the `tsconfig.json` for the package from the `@repo/typescript-config` package. In this example, the `web` package is a Next.js application: ```json title="./apps/web/tsconfig.json" { "extends": "@repo/typescript-config/nextjs.json", "compilerOptions": { "outDir": "dist" }, "include": ["src"], "exclude": ["node_modules"] } ``` ### Creating entrypoints to the package First, make sure your code gets compiled with `tsc` so there will be a `dist` directory. You'll need a `build` script as well as a `dev` script: ```json title="./packages/ui/package.json" { "scripts": { "dev": "tsc --watch", "build": "tsc" } } ``` Then, set up the entrypoints for your package in `package.json` so that other packages can use the compiled code: ```json title="./packages/ui/package.json" { "exports": { "./*": { "types": "./src/*.ts", "default": "./dist/*.js" } } } ``` Setting up `exports` this way has several advantages: * Using the `types` field allows `tsserver` to use the code in `src` as the source of truth for your code's types. Your editor will always be up-to-date with the latest interfaces from your code. * You can quickly add new entrypoints to your package without creating [dangerous barrel files](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js#what's-the-problem-with-barrel-files). * You'll receive auto-importing suggestions for your imports across package boundaries in your editor. If you're publishing the package, you cannot use references to source code in `types` since only the compiled code will be published to npm. You'll need to generate and reference declaration files and source maps. ## Linting your codebase To use TypeScript as a linter, you can check the types across your workspace **fast** using Turborepo's caching and parallelization. First, add a `check-types` script to any package that you want to check the types for: ```json title="./apps/web/package.json" { "scripts": { "check-types": "tsc --noEmit" } } ``` Then, create a `check-types` task in `turbo.json`. From the [Configuring tasks guide](/docs/crafting-your-repository/configuring-tasks#dependent-tasks-that-can-be-run-in-parallel), we can make the task run in parallel while respecting source code changes from other packages using a [Transit Node](/docs/core-concepts/package-and-task-graph#transit-nodes): ```json title="./turbo.json" { "tasks": { "topo": { "dependsOn": ["^topo"] }, "check-types": { "dependsOn": ["topo"] } } } ``` Then, run your task using `turbo check-types`. ## Best practices ### Use `tsc` to compile your packages For [Internal Packages](/docs/core-concepts/internal-packages), we recommend that you use `tsc` to compile your TypeScript libraries whenever possible. While you can use a bundler, it's not necessary and adds extra complexity to your build process. Additionally, bundling a library can mangle the code before it makes it to your applications' bundlers, causing hard to debug issues. ### Enable go-to-definition across package boundaries "Go-to-definition" is an editor feature for quickly navigating to the original declaration or definition of a symbol (like a variable or function) with a click or hotkey. Once TypeScript is configured correctly, you can navigate across [Internal Packages](/docs/core-concepts/internal-packages) with ease. #### Just-in-Time Packages Exports from [Just-in-Time Packages](/docs/core-concepts/internal-packages#just-in-time-packages) will automatically bring you to the original TypeScript source code. Go-to-definition will work as expected. #### Compiled Packages Exports from [Compiled Packages](/docs/core-concepts/internal-packages#compiled-packages) require the use of [`declaration`](https://www.typescriptlang.org/tsconfig/#declaration) and [`declarationMap`](https://www.typescriptlang.org/tsconfig/#declarationMap) configurations for go-to-definition to work. After you've enabled these two configurations for the package, compile the package with `tsc`, and open the output directory to find declaration files and source maps. With these two files in place, your editor will now navigate to the original source code. ### Use Node.js subpath imports instead of TypeScript compiler `paths` It's possible to create absolute imports in your packages using [the TypeScript compiler's `paths` option](https://www.typescriptlang.org/tsconfig#paths), but these paths can cause failed compilation when using [Just-in-Time Packages](https://turborepo.dev/docs/core-concepts/internal-packages#just-in-time-packages). [As of TypeScript 5.4](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#auto-import-support-for-subpath-imports), you can use [Node.js subpath imports](https://nodejs.org/api/packages.html#imports) instead for a more robust solution. #### Just-in-Time Packages In [Just-in-Time packages](https://turborepo.dev/docs/core-concepts/internal-packages#just-in-time-packages), `imports` must target the source code in the package, since build outputs like `dist` won't be created. ```json title="./packages/ui/package.json" { "imports": { "#*": "./src/*" } } ``` ```tsx title="./packages/ui/button.tsx" import { MY_STRING } from "#utils.ts" // Uses .ts extension // [!code highlight] export const Button = () => { return ( ) } ``` #### Compiled Packages In [Compiled packages](https://turborepo.dev/docs/core-concepts/internal-packages#compiled-packages), `imports` target the built outputs for the package. ```json title="./packages/ui/package.json" { "imports": { "#*": "./dist/*" } } ``` ```tsx title="./packages/ui/button.tsx" import { MY_STRING } from "#utils.js"; // Uses .js extension // [!code highlight] export const Button = () => { return ; }; ``` ### You likely don't need a `tsconfig.json` file in the root of your project As mentioned in the [Structuring your repository guide](/docs/crafting-your-repository/structuring-a-repository#anatomy-of-a-package), you want to treat each package in your tooling as its own unit. This means each package should have its own `tsconfig.json` to use instead of referencing a `tsconfig.json` in the root of your project. Following this practice will make it easier for Turborepo to cache your type checking tasks, simplifying your configuration. The only case in which you may want to have a `tsconfig.json` in the Workspace root is to set configuration for TypeScript files that are not in packages. For example, if you have a script written with TypeScript that you need to run from the root, you may need a `tsconfig.json` for that file. However, this practice is also discouraged since any changes in the Workspace root will cause all tasks to miss cache. Instead, move those scripts to a different directory in the repository. ### You likely don't need TypeScript Project References We don't recommend using TypeScript Project References as they introduce both another point of configuration as well as another caching layer to your workspace. Both of these can cause problems in your repository with little benefit, so we suggest avoiding them when using Turborepo. ## Limitations ### Your editor won't use a package's TypeScript version `tsserver` is not able to use different TypeScript versions for different packages in your code editor. Instead, it will discover a specific version and use that everywhere. This can result in differences between the linting errors that show in your editor and when you run `tsc` scripts to check types. If this is an issue for you, consider [keeping the TypeScript dependency on the same version](/docs/crafting-your-repository/managing-dependencies#keeping-dependencies-on-the-same-version). --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/messages/unnecessary-package-task-syntax.md # Unnecessary package task syntax error ## Why this error occurred Turborepo supports adding additional `turbo.json` files in a package directory to override the `turbo.json` file declared at the repository root, a feature called [Workspace Configurations](/docs/crafting-your-repository/structuring-a-repository#specifying-packages-in-a-monorepo). In those additional `turbo.json` files, you can only configure tasks for that specific package. Therefore, only the task name should be included in the task, not the package and task name (`package#task`). `turbo.json` file in `apps/web` directory: ```json title="./turbo.json" { "tasks": { "web#build": { "dependsOn": ["lint"] } } } ``` Since this `turbo.json` file is inside a package directory, the `web` prefix is unnecessary. ## Solution Remove the package prefix from the task name: ```json title="./turbo.json" { "tasks": { "build": { "dependsOn": ["lint"] } } } ``` --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/ci-vendors/vercel.md # Vercel Vercel's zero-config integration with Turborepo automatically understands your monorepo. To deploy your Turborepo on Vercel, [create a new project](https://vercel.com/new) and import your code. Your projects will be pre-configured with the correct settings to use the [Vercel Remote Cache](https://vercel.com/docs/monorepos/remote-caching). For more information about deploying your Turborepo to Vercel, [visit the Vercel documentation](https://vercel.com/docs/concepts/monorepos/turborepo). --- [View full sitemap](/sitemap.md) --- # Source: https://turbo.build/guides/tools/vitest.md # Vitest [Vitest](https://vitest.dev/) is a test runner from the Vite ecosystem. Integrating it with Turborepo will lead to enormous speed-ups. [The Vitest documentation](https://vitest.dev/guide/workspace) shows how to create a "Vitest Projects" configuration that runs all tests in the monorepo from one root command, enabling behavior like merged coverage reports out-of-the-box. This feature doesn't follow modern best practices for monorepos, since its designed for compatibility with Jest (whose Workspace feature was built before [package manager Workspaces](/docs/crafting-your-repository/structuring-a-repository)). Vitest has deprecated workspaces in favor of projects. When using projects, individual project vitest configs can't extend the root config anymore since they would inherit the projects configuration. Instead, a separate shared file like `vitest.shared.ts` is needed. Because of this you have two options, each with their own tradeoffs: * [Leveraging Turborepo for caching](#leveraging-turborepo-for-caching) * [Using Vitest's Projects feature](#using-vitests-projects-feature) ### Leveraging Turborepo for caching To improve on cache hit rates and only run tests with changes, you can choose to configure tasks per-package, splitting up the Vitest command into separate, cacheable scripts in each package. This speed comes with the tradeoff that you'll need to create merged coverage reports yourself. For a complete example, run `npx create-turbo@latest --example with-vitest` or [visit the example's source code](https://github.com/vercel/turborepo/tree/main/examples/with-vitest). #### Setting up Let's say we have a simple [package manager Workspace](/docs/crafting-your-repository/structuring-a-repository) that looks like this: Both `apps/web` and `packages/ui` have their own test suites, with `vitest` [installed into the packages that use them](/docs/crafting-your-repository/managing-dependencies#install-dependencies-where-theyre-used). Their `package.json` files include a `test` script that runs Vitest: ```json title="./apps/web/package.json" { "scripts": { "test": "vitest run" }, "devDependencies": { "vitest": "latest" } } ``` Inside the root `turbo.json`, create a `test` task: ```json title="./turbo.json" { "tasks": { "test": { "dependsOn": ["transit"] }, "transit": { "dependsOn": ["^transit"] } } } ``` Now, `turbo run test` can parallelize and cache all of the test suites from each package, only testing code that has changed. #### Running tests in watch mode When you run your test suite in CI, it logs results and eventually exits upon completion. This means you can [cache it with Turborepo](/docs/crafting-your-repository/caching). But when you run your tests using Vitest's watch mode during development, the process never exits. This makes a watch task more like a [long-running, development task](/docs/crafting-your-repository/developing-applications). Because of this difference, we recommend specifying **two separate Turborepo tasks**: one for running your tests, and one for running them in watch mode. This strategy below creates two tasks, one for local development and one for CI. You could choose to make the `test` task for local development and create some `test:ci` task instead. For example, inside the `package.json` file for each workspace: ```json title="./apps/web/package.json" { "scripts": { "test": "vitest run", "test:watch": "vitest --watch" } } ``` And, inside the root `turbo.json`: ```json title="./turbo.json" { "tasks": { "test": { "dependsOn": ["^test"] }, "test:watch": { "cache": false, "persistent": true } } } ``` You can now run your tasks using [global `turbo`](/docs/getting-started/installation#global-installation) as `turbo run test:watch` or from a script in your root `package.json`: ```bash title="Terminal" turbo run test turbo run test:watch ``` ```json title="./package.json" { "scripts": { "test": "turbo run test", "test:watch": "turbo run test:watch" } } ``` #### Creating merged coverage reports [Vitest's Projects feature](#using-vitests-projects-feature) creates an out-of-the-box coverage report that merges all of your packages' tests coverage reports. Following the Turborepo strategy, though, you'll have to merge the coverage reports yourself. The [`with-vitest` example](https://github.com/vercel/turborepo/tree/main/examples/with-vitest) shows a complete example that you may adapt for your needs. You can get started with it quickly using `npx create-turbo@latest --example with-vitest`. To do this, you'll follow a few general steps: 1. Run `turbo run test` to create the coverage reports. 2. Merge the coverage reports with [`nyc merge`](https://github.com/istanbuljs/nyc?tab=readme-ov-file#what-about-nyc-merge). 3. Create a report using `nyc report`. Turborepo tasks to accomplish will look like: ```json title="./turbo.json" { "tasks": { "test": { "dependsOn": ["^test", "@repo/vitest-config#build"], "outputs": ["coverage.json"] }, "merge-json-reports": { "inputs": ["coverage/raw/**"], "outputs": ["coverage/merged/**"] }, "report": { "dependsOn": ["merge-json-reports"], "inputs": ["coverage/merge"], "outputs": ["coverage/report/**"] } } } ``` With this in place, run `turbo test && turbo report` to create a merged coverage report. The [`with-vitest` example](https://github.com/vercel/turborepo/tree/main/examples/with-vitest) shows a complete example that you may adapt for your needs. You can get started with it quickly using `npx create-turbo@latest --example with-vitest`. ### Using Vitest's Projects feature The Vitest Projects feature doesn't follow the same model as a [package manager Workspace](/docs/crafting-your-repository/structuring-a-repository). Instead, it uses a root script that then reaches out into each package in the repository to handle the tests in that respective package. In this model, there aren't package boundaries, from a modern JavaScript ecosystem perspective. This means you can't rely on Turborepo's caching, since Turborepo leans on those package boundaries. Because of this, you'll need to use [Root Tasks](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks) if you want to run the tests using Turborepo. Once you've configured [a Vitest Projects setup](https://vitest.dev/guide/workspace), create the Root Tasks for Turborepo: ```json title="./turbo.json" { "tasks": { "//#test": { "outputs": ["coverage/**"] }, "//#test:watch": { "cache": false, "persistent": true } } } ``` **Notably, the file inputs for a Root Task include all packages by default, so any change in any package will result in a cache miss.** While this does make for a simplified configuration to create merged coverage reports, you'll be missing out on opportunities to cache repeated work. ### Using a hybrid approach You can combine the benefits of both approaches by implementing a hybrid solution. This approach unifies local development using Vitest's Projects feature while preserving Turborepo's caching in CI. This comes at the tradeoff of slightly more configuration and a mixed task running model in the repository. First, create a shared configuration package since individual projects can't extend the root config when using projects. Create a new package for your shared Vitest configuration: ```json title="./packages/vitest-config/package.json" { "name": "@repo/vitest-config", "version": "0.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "dev": "tsc --watch" }, "dependencies": { "vitest": "latest" }, "devDependencies": { "@repo/typescript-config": "workspace:*", "typescript": "latest" } } ``` ```json title="./packages/vitest-config/tsconfig.json" { "extends": "@repo/typescript-config/base.json", "compilerOptions": { "outDir": "dist", "rootDir": "src" }, "include": ["src"], "exclude": ["dist", "node_modules"] } ``` ```ts title="./packages/vitest-config/src/index.ts" export const sharedConfig = { test: { globals: true, environment: "jsdom", setupFiles: ["./src/test/setup.ts"], // Other shared configuration }, }; ``` Then, create your root Vitest configuration using projects: ```ts title="./vitest.config.ts" export default defineConfig({ ...sharedConfig, projects: [ { name: "packages", root: "./packages/*", test: { ...sharedConfig.test, // Project-specific configuration }, }, ], }); ``` In this setup, your packages maintain their individual Vitest configurations that import the shared config. First, install the shared config package: ```json title="./packages/ui/package.json" { "scripts": { "test": "vitest run", "test:watch": "vitest --watch" }, "devDependencies": { "@repo/vitest-config": "workspace:*", "vitest": "latest" } } ``` Then create the Vitest configuration: ```ts title="./packages/ui/vitest.config.ts" export default defineConfig({ ...sharedConfig, test: { ...sharedConfig.test, // Package-specific overrides if needed }, }); ``` Make sure to update your `turbo.json` to include the new configuration package in the dependency graph: ```json title="./turbo.json" { "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "test": { "dependsOn": ["^test", "@repo/vitest-config#build"] }, "test:watch": { "cache": false, "persistent": true } } } ``` While your root `package.json` includes scripts for running tests globally: ```json title="./package.json" { "scripts": { "test:projects": "vitest run", "test:projects:watch": "vitest --watch" } } ``` This configuration allows developers to run `pnpm test:projects` or `pnpm test:projects:watch` at the root for a seamless local development experience using Vitest projects, while CI continues to use `turbo run test` to leverage package-level caching. **You'll still need to handle merged coverage reports manually as described in the previous section**. --- [View full sitemap](/sitemap.md)