TurborepoMonorepoNext.jsNestJSTypeScript

Turborepo : gérez un monorepo Next.js + NestJS comme un pro

Découvrez comment Turborepo simplifie la gestion d'un monorepo full-stack avec Next.js et NestJS : configuration, pipelines de build et partage de code.

AMAlexis Mouchon8 min de lecture

Quand un projet grandit, la question de l'organisation du code se pose inévitablement. Deux dépôts séparés — un pour le front Next.js, un pour le back NestJS — ça fonctionne au départ. Mais rapidement on se retrouve à dupliquer des types TypeScript, à jongler entre plusieurs package.json, et à oublier de synchroniser les interfaces entre le client et le serveur. Turborepo propose une réponse élégante à ce problème : unifier les deux dans un monorepo performant, avec un système de cache intelligent qui garde des builds rapides.

Qu'est-ce qu'un monorepo et pourquoi Turborepo ?

Un monorepo (monolithic repository) consiste à héberger plusieurs projets dans un seul dépôt Git. Ce n'est pas un concept nouveau — Google, Meta ou Vercel utilisent cette approche à grande échelle. L'avantage principal : le partage de code devient trivial, et les changements cross-packages sont atomiques (un seul commit, une seule PR).

Turborepo, créé par la team Vercel, se distingue de ses concurrents (Nx, Lerna) par sa philosophie minimaliste :

Mettre en place la structure du monorepo

Voici la structure typique d'un monorepo Next.js + NestJS avec Turborepo :

my-app/
├── apps/
│   ├── web/          # Application Next.js
│   └── api/          # Application NestJS
├── packages/
│   ├── shared/       # Types et utilitaires partagés
│   └── ui/           # Composants React réutilisables (optionnel)
├── turbo.json
└── package.json      # Root workspace

Commencez par initialiser le workspace avec pnpm (recommandé avec Turborepo) :

# À la racine
pnpm init
pnpm add turbo -D -w

Configurez le package.json racine pour déclarer le workspace :

{
  "name": "my-app",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "^2.0.0"
  }
}

Configurer le pipeline Turborepo

Le fichier turbo.json définit comment les tâches se propagent entre packages. C'est ici que la magie opère :

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

La syntaxe "^build" signifie : "attend que tous les packages dont je dépends aient fini leur build avant de lancer le mien". Ainsi, si web dépend de shared, Turborepo buildera shared en premier — automatiquement.

Créer un package partagé de types

C'est là que le monorepo prend tout son sens. Créez un package packages/shared qui contient vos types TypeScript partagés entre le front et le back :

// packages/shared/src/types/user.ts
export interface User {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  role: 'admin' | 'user';
  createdAt: Date;
}

export interface CreateUserInput {
  email: string;
  firstName: string;
  lastName: string;
  password: string;
}

export interface ApiResponse<T> {
  data: T;
  success: boolean;
  message?: string;
}
// packages/shared/package.json
{
  "name": "@my-app/shared",
  "version": "0.0.1",
  "main": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  }
}

Ensuite, dans apps/web et apps/api, il suffit d'ajouter la dépendance :

// apps/web/package.json et apps/api/package.json
{
  "dependencies": {
    "@my-app/shared": "workspace:*"
  }
}

Fini la duplication de types ! Un changement dans packages/shared se propage immédiatement aux deux applications.

Intégrer Next.js et NestJS dans leurs apps/

Côté Next.js (apps/web)

Next.js fonctionne nativement dans un workspace pnpm. La seule configuration particulière à ajouter dans next.config.ts :

// apps/web/next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  transpilePackages: ['@my-app/shared', '@my-app/ui'],
};

export default nextConfig;

La directive transpilePackages indique à Next.js de transpiler les packages locaux, qui sont en TypeScript natif.

Côté NestJS (apps/api)

Dans NestJS, l'import est immédiat grâce à la résolution de workspace :

// apps/api/src/users/users.service.ts
import type { CreateUserInput, User } from '@my-app/shared';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  async create(input: CreateUserInput): Promise<User> {
    // Votre logique de création
    // Les types sont partagés avec le front !
  }
}

Optimiser le cache distant avec Vercel Remote Cache

Par défaut, le cache Turborepo est local. En équipe ou en CI, activez le Remote Cache pour partager les résultats entre machines :

# Authentification (si déployé sur Vercel)
npx turbo login

# Lier le projet
npx turbo link

Avec le Remote Cache, si votre collègue a déjà buildé le package shared, votre CI ne le rebuildera pas. Le gain en temps sur des pipelines complexes peut dépasser 80%.

Pour les projets self-hosted, des alternatives open-source existent : Ducktape ou un simple serveur compatible.

Lancer l'environnement de développement

Une seule commande à la racine pour démarrer tout le stack :

pnpm dev
# Équivalent à turbo run dev --parallel
# → Lance Next.js sur :3000 et NestJS sur :3001 simultanément

Turborepo gère l'ordre de démarrage selon les dépendances déclarées, et chaque app affiche ses logs avec un préfixe coloré pour s'y retrouver.

Quelques pièges à éviter

Les dépendances fantômes : dans un workspace pnpm strict, vous ne pouvez pas utiliser un package qui n'est pas explicitement déclaré dans votre package.json. C'est une bonne pratique, mais ça surprend au début.

La config TypeScript : chaque package doit avoir son propre tsconfig.json, qui extend idéalement un tsconfig.base.json à la racine. Évite les incohérences de compilation.

Le mode dev non cacheable : n'oubliez pas "cache": false pour la tâche dev. Sinon Turborepo tentera (en vain) de cacher un processus long-lived.

Conclusion

Turborepo transforme la gestion d'un projet full-stack Next.js + NestJS : plus de synchronisation manuelle de types, des builds rapides grâce au cache, et un seul dépôt à maintenir. La mise en place initiale demande une heure ou deux, mais les bénéfices sur la durée sont considérables — surtout quand l'équipe grandit ou que le projet se complexifie.

Si vous souhaitez approfondir, les articles sur GraphQL Code First avec NestJS et sur CI/CD avec GitHub Actions s'intègrent parfaitement dans cette architecture monorepo.

Vous avez un projet full-stack à lancer ou à restructurer ? N'hésitez pas à me contacter pour en discuter — je serais ravi de vous aider à poser des bases solides dès le départ.