Partager des composants UI dans un monorepo avec Next.js 16 et shadcn/ui (Bun + Turborepo)

Illustration de Partager des composants UI dans un monorepo avec Next.js 16 et shadcn/ui (Bun + Turborepo)

La configuration d’une bibliothèque UI partagée dans un monorepo Bun et Turborepo avec Next.js 16, React 19, Tailwind CSS v4 et shadcn/ui.

Partager des composants UI entre plusieurs applications d’un monorepo paraît simple, jusqu’à ce que l’on se heurte aux contraintes des outils front-end modernes : paquets exclusivement ESM, moteur CSS monofichier de Tailwind v4, graphe de modules strict de Next.js 16 et fonctionnement de shadcn/ui. Ce guide explique comment faire fonctionner l’ensemble proprement dans un monorepo Bun/Turborepo, sans exécuter shadcn init dans chaque application.

Cette configuration repose sur une intégration réelle : une bibliothèque UI partagée, @workspace/ui, utilisée par une application de tableau de bord sous Next.js 16.

L’objectif est simple : une seule configuration Tailwind, une seule bibliothèque UI, plusieurs applications et aucune duplication.

Vue d’ensemble de l’architecture

Voici la stack utilisée :

  • Next.js 16 + React 19

  • Tailwind CSS v4 (le nouveau moteur monofichier)

  • des composants shadcn/ui (Radix UI + tailwind-variants + tailwind-merge)

  • Bun 1.3+, Turborepo, Biome

  • packages/ui → la bibliothèque de composants partagés

  • apps/dashboard → l’application Next.js qui utilise la bibliothèque

L’idée centrale est la suivante : le paquet UI gère Tailwind v4, PostCSS, les tokens, les styles globaux et les composants primitifs. Les applications se contentent de les utiliser. Il n’est donc plus nécessaire de dupliquer les configurations Tailwind entre les applications.

Configurer les workspaces

Le monorepo utilise des workspaces classiques au format npm, entièrement pris en charge par Bun.

package.json à la racine :

JSON
{
  "workspaces": ["packages/*", "apps/*"]
}

Le tableau de bord déclare ensuite la bibliothèque comme dépendance au moyen du protocole workspace :

JSON
{
  "dependencies": {
    "@workspace/ui": "workspace:*",
  },
  "name": "@workspace/dashboard",
}

Next.js peut ainsi transpiler et regrouper le paquet partagé comme s’il s’agissait d’une dépendance locale.

Construire le paquet UI partagé

Le paquet UI constitue la « source de vérité » pour les composants, les styles, la configuration Tailwind/PostCSS, les tokens et les alias du générateur shadcn/ui.

packages/ui/package.json:

JSON
{
  "name": "@workspace/ui",
  "type": "module",
  "exports": {
    "./components/*": "./src/components/*.tsx",
    "./lib/*": "./src/lib/*.ts",
    "./globals.css": "./src/styles/globals.css",
    "./postcss.config": "./postcss.config.mjs"
  }
}

Le champ exports permet de conserver des chemins d’importation clairs :

JavaScript
import { Button } from "@workspace/ui/components/button";
import "@workspace/ui/globals.css";

L’ensemble utilise strictement ESM.

Par défaut, Tailwind v4 n’utilise plus de fichiers de configuration. Des directives placées dans un fichier CSS pilotent désormais l’ensemble du moteur. Voici la feuille de style unifiée :

packages/ui/src/styles/globals.css:

CSS
@import "tailwindcss";
@import "tw-animate-css";

/* Tell Tailwind what to scan */
@source "../../../apps/**/*.{ts,tsx}";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";

Ce fichier est exporté afin que chaque application puisse l’importer une seule fois.

packages/ui/postcss.config.mjs:

JavaScript
export default {
  plugins: {
    "@tailwindcss/postcss": {}
  }
}

Cette configuration utilise le plugin PostCSS officiel de Tailwind v4, documenté sur ui.shadcn.com/monorepo.

Vous l’exportez pour permettre aux applications d’utiliser la même configuration. Pour le moment, les applications consommatrices doivent également installer @tailwindcss/postcss de leur côté.

Le fichier central, packages/ui/components.json :

JSON
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "aliases": {
    "components": "@workspace/ui/components",
    "hooks": "@workspace/ui/hooks",
    "lib": "@workspace/ui/lib",
    "ui": "@workspace/ui/components",
    "utils": "@workspace/ui/lib/utils"
  },
  "iconLibrary": "lucide",
  "rsc": true,
  "style": "new-york",
  "tailwind": {
    "baseColor": "neutral",
    "config": "",
    "css": "src/styles/globals.css",
    "cssVariables": true
  },
  "tsx": true
}

Le fichier utils.ts expose généralement la fonction utilitaire cn, chargée de fusionner les classes :

JavaScript
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...args) {
  return twMerge(clsx(args));
}

Configurer l’application Next.js 16

C’est généralement à cette étape que les difficultés apparaissent. Next.js 16 applique des règles strictes aux paquets externes. Voici comment les intégrer correctement.

apps/dashboard/next.config.ts:

JavaScript
const nextConfig = {
  transpilePackages: ["@workspace/ui"]
};

export default nextConfig;

Cette configuration est nécessaire pour les raisons suivantes :

  • Le paquet UI distribue du code TS/ESM.

  • Il contient des importations CSS.

  • Il utilise les règles d’analyse de Tailwind v4.

Sans cette configuration, des erreurs de modules difficiles à interpréter apparaissent.

apps/dashboard/postcss.config.mjs:

JavaScript
export { default } from "@workspace/ui/postcss.config";

apps/dashboard/src/app/layout.tsx:

JavaScript
import "@workspace/ui/globals.css";

Puisque le paquet UI gère tous les tokens et toutes les couches, l’application déclare les styles qu’elle utilise sans en définir la source.

apps/dashboard/tsconfig.json:

JSON
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@workspace/ui/*": ["../../packages/ui/src/*"]
    }
  }
}

Le second alias est essentiel :

  • Les types sont disponibles en temps réel pendant le développement.

  • Il n’est pas nécessaire de publier une version compilée de @workspace/ui uniquement pour obtenir les bons types.

L’application possède également un fichier components.json :

JSON
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "aliases": {
    "components": "@/components",
    "hooks": "@/hooks",
    "lib": "@/lib",
    "ui": "@workspace/ui/components",
    "utils": "@workspace/ui/lib/utils"
  },
  "iconLibrary": "lucide",
  "rsc": true,
  "style": "new-york",
  "tailwind": {
    "baseColor": "neutral",
    "config": "",
    "css": "../../packages/ui/src/styles/globals.css",
    "cssVariables": true
  },
  "tsx": true
}

Ainsi, si vous exécutez :

Bash
cd apps/dashboard
bunx --bun shadcn@latest add button

Le composant généré référence correctement la bibliothèque partagée. Vous pouvez ensuite l’utiliser dans votre page :

TypeScript
import { Button } from "@workspace/ui/components/button";

export default function Page() {
  return <Button>Click me</Button>;
}

Si tout est correctement configuré, cela fonctionne directement.

Pour conclure

En résumé :

  • Le tableau de bord importe un seul fichier CSS, @workspace/ui/globals.css.

  • Grâce aux directives @source, Tailwind v4 analyse à la fois apps/**/* et packages/ui/**/*.

  • shadcn/ui génère des importations qui pointent directement vers @workspace/ui.

  • Next.js transpile le paquet UI afin de traiter correctement CSS, TS et ESM.

  • Une seule configuration Tailwind/PostCSS existe dans l’ensemble du monorepo. Toutes les applications l’utilisent, ce qui évite toute divergence.

Bon développement !

Articles liés