Turborepo example

4rokis

Bogdan Sikora

Posted on January 4, 2023

Turborepo example

Github project
https://github.com/S4n60w3n/monorepo-example

Project structure

  • apps

    • web
    • docs
    • store
  • packages

    • components (Shared codebase. Design system, shared react components, custom libraries, …)
    • tsconfig (Typescript configuration)
    • eslint-config-custom (Linter configuration)

 
NextJs apps are fully autonomous NextJS applications that runs next to each other as zones. Master app(web) handles the zone configuration and user doesn't know that the app/zone he interacts with has changed.

💡 A zone is a single deployment of a NextJS app. You can have multiple zones and merge them as a single app. [Advanced Features: Multi Zones | Next.js](https://nextjs.org/docs/advanced-features/multi-zones#how-to-merge-zones)

Zone configuration

The master app/zone hold the configuration. Officially user interacts with this app only and we use the power of rewrites to serve user with a different zone.

// /apps/web/next.config.js
const {
 DOCS_URL = 'http://localhost:3001',
 STORE_URL = 'http://localhost:3002',
} = process.env
module.exports = {
 transpilePackages: ['@shared/components'],
 async rewrites() {
 return [
 {
 source: '/:path*',
 destination: `/:path*`,
 },
 // Docs
 {
 source: '/docs',
 destination: `${DOCS_URL}/docs`,
 },
 {
 source: '/docs/:path*',
 destination: `${DOCS_URL}/docs/:path*`,
 },
 // Store
 {
 source: '/store',
 destination: `${STORE_URL}/store`,
 },
 {
 source: '/store/:path*',
 destination: `${STORE_URL}/store/:path*`,
 },
 ]
 },
}
Enter fullscreen mode Exit fullscreen mode

Every time user goes to subsection of /docs route it will get served with the docs application and so on.
Each of the zones need to have a basePath set to that path.


// /apps/docs/next.config.js
module.exports = {
 basePath: '/docs',
}
Enter fullscreen mode Exit fullscreen mode

NPM workspace configuration

💡 Workspaces is a generic term that refers to the set of features in the npm cli that provides support to managing multiple packages from your local files system from within a singular top-level, root package.
workspaces | npm Docs


// Root package.json
{
 "name": "my-monorepo",
 "version": "1.0.0",
 "description": "",
 "devDependencies": {
 },
 "dependencies": {
 },
 "workspaces": [
 "apps/*",
 "packages/*"
 ]
}
Enter fullscreen mode Exit fullscreen mode

This simple configuration will add everything in apps and packages folder to the workspace. NPM will then do its magic and link the subproject and shared dependencies.

💡 If your shared codebase project name in the package.json is "my-shared-project". NPM will create a folder in the root node_modules with that name "/node_modules/my-shared-project". The apps can then access that codebase.

Sub apps that want to use that shared codebase now only need to add it to package.json


// /apps/docs/package.json
{
 "name": "docs",
 "dependencies": {
 "@shared/components": "*",
 },
}
Enter fullscreen mode Exit fullscreen mode

and to the NextJS config.


// /apps/docs/next.config.js
module.exports = {
 basePath: '/docs',
 transpilePackages: ['@shared/components'],
}
Enter fullscreen mode Exit fullscreen mode

This simple configuration will allow you to have multi-zone NextJS monorepo.

Turborepo

Turborepo is a build system for JS codebases. It allows for easier control of the packages as well as running custom scripts.
Turborepo
The configuration is quite straightforward.

// root turbo.json
{
 "$schema": "https://turbo.build/schema.json",
 "pipeline": {
 "build": {
 "dependsOn": ["^build"],
 "outputs": ["dist/**", ".next/**"]
 },
 "tsc": {
 "outputs": []
 },
 "test": {
 "outputs": []
 },
 "lint": {
 "outputs": []
 },
 "dev": {
 "cache": false
 },
 "start": {
 "cache": false
 }
 }
 }
Enter fullscreen mode Exit fullscreen mode

You can specify commands that will run in each of the sub-projects and cache the results for faster second run if nothing has changed.

Shared configuration

Typescript

3 different configs. One is a shared base between environments and 2 for each environment.

Base config

Shared base config

// packages/tsconfig/base.json
{
 "$schema": "https://json.schemastore.org/tsconfig",
 "display": "Default",
 "compilerOptions": {
 "resolveJsonModule": true,
 "esModuleInterop": true,
 "allowSyntheticDefaultImports": true,
 "module": "CommonJS",
 "lib": [
 "es2017",
 "dom"
 ],
 "target": "es5",
 "moduleResolution": "node",
 "skipLibCheck": true,
 "isolatedModules": true,
 "strict": true,
 "incremental": true
 },
 "include": [
 "**/*.ts",
 "**/*.tsx"
 ],
 "exclude": [
 "./node_modules/*"
 ]
}
Enter fullscreen mode Exit fullscreen mode

NextJS

Configuration for NextJs environment

 


 // packages/tsconfig/nextjs.json
 {
 "$schema": "https://json.schemastore.org/tsconfig",
 "display": "Next.js",
 "extends": "@shared/tsconfig/base.json",
 "compilerOptions": {
 "experimentalDecorators": true,
 "emitDecoratorMetadata": true,
 "sourceMap": true,
 "keyofStringsOnly": true,
 "noEmit": true,
 "target": "es6",
 "jsx": "preserve",
 "allowJs": true,
 "forceConsistentCasingInFileNames": true,
 "strictNullChecks": true,
 "noImplicitAny": true
 },
 "include": [
 "next-env.d.ts"
 "**/*.ts",
 "**/*.tsx"
 ],
 }
Enter fullscreen mode Exit fullscreen mode

 

React

Configuration for shared react library (Design System) and unit tests

 


 // packages/tsconfig/react.json
 {
 "$schema": "https://json.schemastore.org/tsconfig",
 "display": "React",
 "extends": "@shared/tsconfig/base.json",
 "compilerOptions": {
 "declaration": true,
 "composite": true,
 "downlevelIteration": true,
 "outDir": "./dist",
 "jsx": "react",
 "baseUrl": "./src"
 }
 }
Enter fullscreen mode Exit fullscreen mode

Projects then need to add "@shared/tsconfig": "*", into dev-dependencies and extend the config

// /apps/docs/tsconfig.json
// include & exclude need to be redefined to point to this directory
{
 "extends": "@shared/tsconfig/nextjs.json",
 "include": [
 "**/*.ts",
 "**/*.tsx",
 "next-endv.d.ts"
 ],
 "exclude": [
 "node_modules"
 ]
}
Enter fullscreen mode Exit fullscreen mode

Linter

It is required that the name of the package starts with eslint-config-
We are using prettier and eslint mix.

// /packages/eslint-config-custom/package.json
{
 "name": "eslint-config-custom",
 "version": "0.0.0",
 "main": "index.js",
 "license": "MIT",
 "dependencies": {
 "eslint": "7.14.0",
 "@next/eslint-plugin-next": "12.1.0",
 "eslint-config-prettier": "6.15.0",
 "eslint-plugin-import": "2.22.1",
 "eslint-plugin-prettier": "3.1.4",
 "eslint-plugin-react": "7.21.5",
 "prettier": "2.2.1",
 "@typescript-eslint/eslint-plugin": "5.27.1",
 "@typescript-eslint/parser": "5.27.1"
 },
 "files": [
 "prettier.json",
 "eslint"
 ],
 "publishConfig": {
 "access": "public"
 }
}
Enter fullscreen mode Exit fullscreen mode
// /packages/eslint-config-custom/index.js
module.exports = {
 parser: "@typescript-eslint/parser",
 plugins: ["import", "@typescript-eslint"],
 ignorePatterns: [
 "node_modules/**",
 "storybook-static/**",
 ".next/**",
 "out/**",
 "dist/**",
 ],
 extends: ["plugin:prettier/recommended", "plugin:@next/next/recommended"],
 parserOptions: {
 ecmaVersion: 2018,
 sourceType: "module",
 }
};
Enter fullscreen mode Exit fullscreen mode
// /packages/eslint-config-custom/prettier.js
module.exports = {
 printWidth: 80,
 singleQuote: true,
 trailingComma: "all",
 parser: "typescript",
};
Enter fullscreen mode Exit fullscreen mode
// /.eslintrc.js
module.exports = {
 root: true,
 extends: ["custom"],
 settings: {
 next: {
 rootDir: ["apps/*/"],
 },
 },
};
Enter fullscreen mode Exit fullscreen mode

Apps then need to add "eslint-config-custom": "*", into dev-dependencies and extend the config

// /apps/docs/.eslintrc.js
module.exports = {
 root: true,
 extends: ['custom'],
}
Enter fullscreen mode Exit fullscreen mode
// /apps/docs/.prettierrc.js
module.exports = {
 …require("eslint-config-custom/prettier.js"),
};
Enter fullscreen mode Exit fullscreen mode

Kudos

Architecture, Code and Configs come from Style Space

Connect with expert stylists, over 1-on-1 video styling sessions for clothing, hair and makeup/skincare styling. Elevate your style, simplify getting ready and save time and money.

Links

💖 💪 🙅 🚩
4rokis
Bogdan Sikora

Posted on January 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Turborepo example
react Turborepo example

January 4, 2023