Developing Applications

Purpose and Scope

This page explains how to use Turborepo while actively developing applications in a JavaScript or TypeScript workspace. In this context, development usually means running one or more long-lived scripts: app servers, package watch builds, test watchers, or local setup jobs that prepare generated files. Turborepo treats those scripts as tasks in the same task graph used for builds, lints, and tests, but the recommended configuration is different because an interactive development process does not produce a stable artifact and normally does not exit on its own.

The first-party guide positions application development as a natural next step after repository structure, internal packages, task configuration, and caching. That sequencing matters: Turborepo can run development processes in parallel because earlier steps taught it where packages live, how packages depend on each other, and which scripts correspond to named tasks. When you configure development correctly, a single command can start several apps and supporting packages while preserving enough graph awareness to avoid accidental dependency mistakes.

Sources: apps/docs/content/docs/crafting-your-repository/developing-applications.mdx, apps/docs/content/docs/crafting-your-repository/index.mdx

Relevant Source Files

  • apps/docs/content/docs/crafting-your-repository/developing-applications.mdx — Primary guide for long-lived development tasks, terminal UI interaction, watch mode, setup tasks, and filtering development to one application.
  • apps/docs/content/docs/crafting-your-repository/caching.mdx — Explains why deterministic cacheable work differs from development tasks and describes local and remote cache behavior that informs when to set caching off.
  • apps/docs/content/docs/crafting-your-repository/configuring-tasks.mdx — Defines tasks as package scripts registered through the root turbo.json, including dependsOn, outputs, and graph-aware task ordering.
  • apps/docs/content/docs/crafting-your-repository/constructing-ci.mdx — Shows that the same registered tasks and filters are used in CI, which helps teams keep local and pipeline workflows aligned.
  • apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx — Demonstrates internal packages with dev and build scripts, including a TypeScript watch script that fits the development-task model.
  • apps/docs/content/docs/crafting-your-repository/index.mdx — Places developing applications in the broader “Crafting your repository” learning path after caching and before environment variables and CI.

Core Development Task Model

A Turborepo task is a named script that can be run across packages. The task configuration guide states that each key in the tasks object is a task Turborepo can execute, and Turborepo searches packages for scripts with matching names. For application development, the conventional task name is often dev, but the important part is the contract: the root configuration describes the behavior, and package package.json files provide the actual script commands that start servers or watchers.

The development guide recommends two properties for a long-lived dev task: disable caching and mark it persistent. Disabling caching tells Turborepo not to attempt to cache the result of a task whose value is interaction rather than a reproducible output directory. Marking the task persistent tells Turborepo that it should keep running until stopped and gives the terminal UI a signal that the task is long-running and interactive. It also protects the graph by preventing other tasks from depending on work that will never complete.

Sources: apps/docs/content/docs/crafting-your-repository/developing-applications.mdx, apps/docs/content/docs/crafting-your-repository/configuring-tasks.mdx, apps/docs/content/docs/crafting-your-repository/caching.mdx

./turbo.json
{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

With that root configuration in place, running the development workflow is intentionally simple. The guide shows turbo dev, which starts the matching development scripts in parallel across packages. This is different from manually opening separate terminals and running individual package scripts because Turborepo still knows the package graph, task names, and configured behavior. The result is a single coordination point for local work, while each package remains responsible for defining what its own development script actually does.

Terminal
turbo dev

Setup Tasks Before Long-Running Processes

Some development environments require preparation before servers or watchers are useful. The guide calls out setup scripts for work such as generating code or pre-building supporting packages. Turborepo models this with dependsOn, the same mechanism used by other task workflows. A development task can depend on a finite setup task, and the setup task can declare outputs when it creates files that Turborepo should understand as task results. This keeps initialization explicit instead of hiding it inside a long-running server command.

The example uses a root task named //#dev:setup and makes dev depend on it. Root tasks are useful when the work belongs to the repository rather than to an individual package, such as generating shared files into .codegen. The development guide also notes that the same idea applies to arbitrary tasks in packages. The design principle is that persistent tasks should stay focused on serving or watching, while prerequisite tasks should complete before the persistent process starts.

Sources: apps/docs/content/docs/crafting-your-repository/developing-applications.mdx, apps/docs/content/docs/crafting-your-repository/configuring-tasks.mdx

./turbo.json
{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true,
      "dependsOn": ["//#dev:setup"]
    },
    "//#dev:setup": {
      "outputs": [".codegen/**"]
    }
  }
}

This distinction is especially important in repositories with internal packages. The internal-package guide shows a package such as @repo/math with scripts including dev as tsc --watch and build as tsc. That means a shared package can participate in the same development command as an application, continuously compiling while the app consumes its outputs. Turborepo’s package graph, derived from package dependencies, is what lets this feel like one workspace instead of a collection of unrelated folders.

Sources: apps/docs/content/docs/crafting-your-repository/creating-an-internal-package.mdx, apps/docs/content/docs/crafting-your-repository/developing-applications.mdx

Running a Specific Application

In larger workspaces, starting every development script may be noisy or unnecessary. The development guide recommends the --filter flag when you want to run a subset of the package graph for a particular application and its dependencies. Filtering is a graph selection tool: it narrows the packages that participate in the command while keeping Turborepo’s understanding of relationships intact. This is useful when a developer is focused on one app, such as web, and only wants the relevant local processes.

Terminal
turbo dev --filter=web

Filtering should be treated as a productivity feature rather than a separate workflow. The CI guide states that task filtering works in CI as it does locally, including filters by packages, directories, and Git history when history is available. That symmetry helps teams avoid maintaining one mental model for development and another for automation. A local command can target one entry point during feature work, while a pipeline can use similar concepts to focus work around affected or deployable packages.

Sources: apps/docs/content/docs/crafting-your-repository/developing-applications.mdx, apps/docs/content/docs/crafting-your-repository/constructing-ci.mdx

Terminal UI and Watch Mode

Turborepo’s development guide emphasizes the terminal UI as part of the local developer experience. Long-running tasks produce continuous logs, and multiple packages can be active at the same time, so the UI provides interactions for navigating and focusing output. The guide lists keybinds such as m to show keybinds, arrow keys or j and k to move through tasks, p to pin a selected task, h to toggle the task list, and c to copy highlighted log output to the system clipboard.

Watch mode is presented alongside the terminal UI as a development capability and is linked from the page’s related reference material. The practical distinction is that persistent dev tasks are processes you start and keep alive, while watch behavior reruns work in response to changes. In a monorepo, that matters because application code and internal package code often change together. A good development setup makes the feedback loop visible and interactive without turning every local edit into a full repository rebuild.

Sources: apps/docs/content/docs/crafting-your-repository/developing-applications.mdx

Caching Boundaries During Development

Caching is central to Turborepo, but not every task should be cached. The caching guide explains that Turborepo speeds work by fingerprinting known inputs and restoring task outputs from local cache or remote cache. It also warns that cacheable tasks should be deterministic: if a task can produce different outputs from the same known inputs, caching may not behave as expected. Development servers and interactive watchers are intentionally outside that model because their purpose is ongoing responsiveness, not producing a final reusable artifact.

That does not make caching irrelevant to development. Cacheable setup tasks, builds, type checks, and generated outputs can still reduce repeated work around a development session. The key is to separate finite deterministic work from persistent interactive work. For example, a setup task that writes .codegen/** can declare outputs, while the dev task that starts a server uses cache: false and persistent: true. This gives Turborepo enough information to optimize what can be optimized without pretending that a live process is an artifact.

Sources: apps/docs/content/docs/crafting-your-repository/caching.mdx, apps/docs/content/docs/crafting-your-repository/developing-applications.mdx

Practical Workflow

A useful default workflow is to define the shared development behavior at the root, keep package scripts concrete, and narrow execution with filters when needed. Start by registering dev in turbo.json with cache: false and persistent: true. Add setup dependencies only when there is real finite work that must happen first. In each application or package, define the script that makes sense for that package, such as a framework development server for an app or tsc --watch for a compiled internal package.

Then run turbo dev when you want the workspace development surface and turbo dev --filter=web when you want one entry point. Use the terminal UI to move between active tasks and inspect logs. Keep build, lint, test, and setup tasks separately configured so they can remain deterministic and cacheable. Next, read the task configuration reference to refine dependencies and outputs, the caching guide to understand cache boundaries, and the watch reference when you need change-driven reruns beyond a standard persistent development command.