Partager des composants UI dans un monorepo Next.js 16 avec Bun et Turborepo

Illustration de Partager des composants UI dans un monorepo Next.js 16 avec Bun et Turborepo

Configurer un paquet shadcn/ui partagé entre plusieurs applications Next.js 16 avec Bun, Turborepo et Tailwind CSS v4.

Partager des composants entre plusieurs applications ne consiste pas seulement à déplacer des fichiers dans un paquet commun. Next.js 16 doit transpiler ce paquet, Tailwind CSS v4 doit trouver ses classes et shadcn/ui doit écrire au bon endroit. Voici une configuration Bun et Turborepo qui réunit ces contraintes sans exécuter shadcn init dans chaque application.

La bibliothèque partagée s’appelle @workspace/ui. Une application Next.js 16 de tableau de bord la consomme directement.

Le paquet UI possède la configuration Tailwind et les composants. Les applications les importent sans dupliquer cette configuration.

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

Le paquet UI gère Tailwind v4, PostCSS, les tokens, les styles globaux et les composants primitifs. Les applications les utilisent sans dupliquer cette configuration.

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 contient les composants, les styles, la configuration Tailwind et 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 relie l’application au paquet partagé :

  • 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.

Ce que chaque partie possède

  • 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.

Cette organisation garde les composants et les styles dans un seul paquet. Les applications conservent seulement leur configuration Next.js et importent @workspace/ui. Le point à surveiller est la résolution des sources Tailwind. Si une classe du paquet partagé n’apparaît pas dans le CSS produit, vérifiez d’abord les chemins déclarés dans le fichier global.

Articles liés