Multi-language Repositories

Purpose and Scope

Turborepo can coordinate JavaScript, TypeScript, Rust, and other toolchains because it schedules package scripts rather than interpreting the implementation language of those scripts. The multi-language guide states the core rule directly: Turborepo uses JavaScript ecosystem conventions to discover scripts and tasks, but it does not care what those scripts do. In practice, the bridge between a non-JavaScript project and Turborepo is a workspace package with a package.json script that invokes the native toolchain. That lets a Rust cargo build, a Go command, or another compiler participate in the same task graph as a Next.js app or shared TypeScript package.

Sources: apps/docs/content/docs/guides/multi-language.mdx

This page focuses on the repository design pattern for mixed-language workspaces: wrap each non-JavaScript component in package-manager workspace metadata, expose its meaningful operations as scripts, describe cacheable outputs in turbo.json, and use normal workspace dependencies to express ordering. It also covers CI and remote cache implications, because multi-language builds often produce expensive binaries or generated artifacts that should not be rebuilt on every developer machine or CI run. The goal is not to replace Rust, Cargo, Docker, or other tools; it is to let Turborepo orchestrate them consistently.

Sources: apps/docs/content/docs/guides/multi-language.mdx, apps/docs/content/docs/guides/ci-vendors/github-actions.mdx, apps/docs/content/blog/free-vercel-remote-cache.mdx

Relevant Source Files

  • apps/docs/content/docs/guides/multi-language.mdx - The primary guide. It defines the multi-language model, shows adding a Rust ./cli directory to workspaces, adds a package.json wrapper, configures outputs, and creates dependency relationships.
  • apps/docs/content/blog/free-vercel-remote-cache.mdx - Explains why remote caching matters for expensive shared work and documents TURBO_TOKEN, TURBO_TEAM, turbo login, and turbo link flows.
  • apps/docs/content/blog/joining-vercel.mdx - Provides project context that Turborepo is open source, backed by Vercel, and integrated with zero-config remote caching through Vercel.
  • apps/docs/content/docs/guides/ci-vendors/github-actions.mdx - Shows the CI pattern of root package scripts calling turbo run, a turbo.json task graph, package-manager install steps, and optional remote cache environment variables.
  • apps/docs/content/docs/guides/ci-vendors/vercel.mdx - Documents Vercel’s zero-config Turborepo integration and automatic Vercel Remote Cache setup for imported monorepos.
  • apps/docs/content/blog/2-10.mdx - Adds current workflow signals relevant to mixed toolchains, including graceful task shutdown, deferred input hashing, composable affected filtering, and local cache eviction.

Workspace Wrapping Pattern

The essential multi-language pattern is to make the non-JavaScript directory visible to the JavaScript package manager. The official example uses a Rust project in ./cli, then adds cli to the workspace definition next to conventional apps/* and packages/* globs. This step matters because Turborepo follows workspace packages and package relationships. If the Rust project is outside the workspace, Turborepo has no package-level script to run and no dependency edge to use when ordering work.

Sources: apps/docs/content/docs/guides/multi-language.mdx

pnpm-workspace.yaml
{
  "packages": [
    "apps/*",
    "packages/*",
    "cli"
  ]
}

After the directory is part of the workspace, add a minimal package.json inside the non-JavaScript project. The name gives other workspace packages a dependency target, and scripts exposes toolchain commands using names Turborepo can schedule. In the Rust example, the package is named @repo/rust-cli and its build script runs cargo build --release. When the repository runs turbo build, that script is included with the other packages that also define build. Turborepo is therefore coordinating Cargo, not replacing it.

Sources: apps/docs/content/docs/guides/multi-language.mdx

./cli/package.json
{
  "name": "@repo/rust-cli",
  "scripts": {
    "build": "cargo build --release"
  }
}

Task Graph, Outputs, and Dependencies

A multi-language repository becomes useful when Turborepo can avoid repeated work and order tasks correctly. Cache outputs are the first part of that contract. For a Rust release build, the guide identifies target/release as the build artifact location and configures the build task with outputs: ["target/release/**"]. That tells Turborepo which files should be restored from cache after a cache hit. For other languages, use the same idea: map the toolchain’s build directory, generated code directory, binary output, or test artifact directory to the task that creates it.

Sources: apps/docs/content/docs/guides/multi-language.mdx

./turbo.json
{
  "tasks": {
    "build": {
      "outputs": ["target/release/**"]
    }
  }
}

Dependency relationships should stay ordinary workspace relationships. The guide demonstrates making a web application depend on @repo/rust-cli by adding it to the application’s devDependencies. With that relationship in place, a turbo.json task that uses dependency-aware ordering can make sure upstream packages build before dependents. This is an important design choice: the package manager remains the source of truth for package relationships, while turbo.json describes task behavior such as outputs, caching, and dependency traversal.

Sources: apps/docs/content/docs/guides/multi-language.mdx

./web/package.json
{
  "devDependencies": {
+   "@repo/rust-cli": "workspace:*"
  }
}

CI, Remote Cache, and Deployment Considerations

Mixed-language builds are often expensive, so CI should be designed around Turborepo’s cache and task graph rather than around independent per-tool commands. The GitHub Actions guide shows a root package.json where build maps to turbo run build and test maps to turbo run test, plus a turbo.json where build declares outputs and dependsOn: ["^build"]. The same pattern works when one package builds TypeScript and another invokes Cargo, as long as each package exposes the expected scripts and cache outputs.

Sources: apps/docs/content/docs/guides/ci-vendors/github-actions.mdx, apps/docs/content/docs/guides/multi-language.mdx

Remote caching is especially valuable for non-JavaScript outputs because binaries and generated artifacts can be large or slow to rebuild. The remote cache announcement describes Remote Caching as a distributed caching layer that keeps developers and CI from doing the same work twice. For Vercel Remote Cache on non-Vercel CI providers, configure TURBO_TOKEN and TURBO_TEAM; locally, developers can run npx turbo login and npx turbo link. On Vercel, the integration is zero-config for imported Turborepos and automatically uses Vercel Remote Cache.

Sources: apps/docs/content/blog/free-vercel-remote-cache.mdx, apps/docs/content/docs/guides/ci-vendors/vercel.mdx, apps/docs/content/blog/joining-vercel.mdx

.github/workflows/ci.yml excerpt
env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
  - name: Build
    run: pnpm build
  - name: Test
    run: pnpm test

Implementation Guidance and Edge Cases

Treat each language boundary as an integration boundary. The workspace package should provide stable scripts such as build, test, lint, or generate, and each script should be safe to run from the package directory according to that toolchain’s expectations. Keep language-specific configuration files, lockfiles, and generated directories in places that make sense for the tool, then reflect only the orchestration-relevant details in turbo.json. This keeps Turborepo lightweight and lets teams retain full control over native compilers, package managers, and framework commands.

Sources: apps/docs/content/docs/guides/multi-language.mdx

Recent Turborepo workflow improvements are useful in multi-tool repositories. The 2.10 release notes call out graceful task shutdown, which helps when development tasks need to flush logs, terminate Docker Compose processes, or close background servers. Deferred input hashing is relevant when generated files or dependency outputs are produced by upstream tasks before downstream inputs can be finalized. Composable --affected and --filter helps narrow CI work to a specific scope, while local cache eviction helps reclaim disk space as multiple toolchains generate artifacts over time.

Sources: apps/docs/content/blog/2-10.mdx

Practical Checklist

  1. Add the non-JavaScript directory, such as cli, to your package manager workspace definition.
  2. Add a package.json in that directory with a stable package name and scripts that call the native toolchain.
  3. Add task outputs in turbo.json for artifacts such as target/release/**, generated clients, compiled binaries, or framework build folders.
  4. Express package ordering through normal workspace dependencies, such as adding @repo/rust-cli to an app’s dependencies or dev dependencies.
  5. Run shared workflows through root scripts like turbo run build and turbo run test so local and CI behavior stays consistent.
  6. Enable Remote Caching with Vercel integration or TURBO_TOKEN and TURBO_TEAM when builds are expensive enough to share across machines.

Next Steps

Start with a single non-JavaScript package rather than migrating every toolchain at once. Add it to the workspace, expose one cacheable script, run it through turbo build, and confirm that the expected output directory is restored from cache. Then add dependency relationships to downstream applications and move the same root commands into CI. For deeper follow-up, read the structuring, configuring tasks, caching, remote caching, GitHub Actions, Vercel, and migrating-from-Nx pages, because those topics define the workspace and task-graph conventions that make multi-language orchestration predictable.