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-monorepoThis 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/uiWe 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 workspacewith the following options:
devDependenciesNote 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 /utils and include necessary build scripts
{
"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 devThis starts both apps/web and apps/admin in parallel, making development smooth and efficient.
6️⃣ Build the Monorepo
To build your project, run
turbo build7️⃣ 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! 🚀
