Turborepo is a high-performance build system for monorepos. It is optimized for JavaScript/TypeScript projects, making it an excellent choice for full-stack applications using Next.js, React, Node.js, and Express.
We will create a Turborepo monorepo with:
✅ A Next.js client front end and back end
✅ A Next.js admin front end and back end
✅ An E2E test suite
✅ A shared utilities package
✅ A shared UI package
Run the following commands to set up a new Turborepo workspace with some default configurations. When asked for a package manager, please use pnpm
pnpx create-turbo@latest my-monorepo
cd my-monorepo
This command creates the following packages by default:
>>> Creating a new Turborepo with:
Application packages
- apps/docs
- apps/web
Library packages
- packages/eslint-config
- packages/typescript-config
- packages/ui
We will remove the docs
app, as we do not need it. Instead, we will create the new admin
app that will be a copy of the web
app. Therefore, we will finally set up all common packages that web
shares with admin
, such as Tailwind.
Then, we will copy the existing web
package, using turbo gen workspace --copy
command. When prompted for options, please use the defaults and name your new app admin
, importing only dependencies
and devDependencies
.
cd apps
rm -rf docs
cd web
pnpm install tailwindcss @tailwindcss/postcss postcss // <- Then configure tailwind
cd ../..
turbo gen workspace --copy
Shared code, such as authentication helpers or data formatters, should be stored in a separate package for reuse.
This is also documented in the official documentation of TurboRepo.
Make sure you are in the root of your monorepo and use the following command:
turbo gen workspace
with the following options:
devDependencies
Note that the other option is to copy the UI package and remove the unnecessary parts
✅ Install Packages
cd packages/utils
pnpm i
✅ Configure Tools
Then, please create the eslint.config.mjs
that uses the internal shared eslint-config
import { config } from "@repo/eslint-config/base";
/** @type {import("eslint").Linter.Config} */
export default config;
And also, you need to create a tsconfig.json
with the folowing content (reusing again the config in your package)
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Modify your package.json
and name your package and include necessary build scripts /utils
{
"name": "@repo/utils",
"version": "0.0.1",
"private": true,
"scripts": {
+ "dev": "tsc --watch",
+ "build": "tsc",
+ "test": "tsc --noEmit",
+ "lint": "eslint . --max-warnings 0"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*"
}
}
✅ Add Functionality
Inside packages/utils/src/messages.ts
, add:
export function greet(name: string) {
return `Hello, ${name}!`;
}
In the package.json
you have to specify what functionality you are exporting from the package:
{
"name": "@repo/math",
...
+ "exports": {
+ "./messages": {
+ "types": "./src/messages.ts",
+ "default": "./dist/messages.js"
+ },
}
...
}
In the packages/utils
folder, please run the pnpm build
command. You can also run the turbo build
command in the root, or turbo dev
command in the root. Please make sure you understand the difference.
First, you need to add the utils package to the list of dependencies of any other package that is using it, in our case <strong>apps/web/package.json</strong>
:
"dependencies": {
+ "@repo/utils": "workspace:*",
"next": "latest",
"react": "latest",
"react-dom": "latest"
},
Then, run pnpm i
in the apps/web
directory to link this package.
✅ Use the Shared Package in the Front End
Edit apps/web/app/page.tsx
to import the shared function:
import { greet } from '@repo/utils/messages';
export default function Home() {
return (
<div>
{greet("world")}
</div>
);
}
And observe the magic!
Inside package.json
, add if not already:
"workspaces": {
"packages": ["apps/*", "packages/*"]
}
This tells Turborepo to manage dependencies for apps and packages.
Also, Add the artifacts for the new /math library to the
outputs
for the build
task in /turbo.json
. This ensures that its build outputs will be cached by Turborepo, so they can be restored instantly when you start running builds.
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
}
}
}
Just run:
turbo dev
This starts both apps/web
and apps/admin
in parallel, making development smooth and efficient.
6️⃣ Build the Monorepo
To build your project, run
turbo build
7️⃣ Deploy the Monorepo
We are skipping ahead a bit, but if you would like to deploy this monorepo for example to Vercel, you will need to set up two projects on Vercel. For the build command you can filter out only the dependent projects. Therefor for the web project you set up the build command as:
turbo run build --env-mode=loose --filter @repo/web...
And the Root Directory to apps/web
.
For the admin project you set up the build command as:
turbo run build --env-mode=loose --filter @repo/admin...
And the Root Directory to apps/admin
.
✅ Blazing Fast Builds: Turborepo caches previous builds to speed up development.
✅ Parallel Task Execution: Runs frontend and backend concurrently.
✅ Code Sharing: The packages/utils
library can be used in both frontend and backend.
This setup enables a powerful full-stack monorepo with Turborepo! 🚀