Monorepo Setup
Link a shared Nocta UI workspace to an application using Bun workspaces.
Nocta UI treats shared UI workspaces as the source of truth for component files. In a monorepo you can run CLI commands from an application workspace while the actual React source lives inside a sibling package. This guide recreates the Bun + Next.js example step by step so you can adapt it to your own tooling.
Prerequisites
- Bun 1.1 or newer
- Node.js (for occasional
npxutilities) - Git (recommended for tracking generated files)
Repository Layout
nocta-bun-monorepo
├── apps/
│ └── web (Next.js application)
└── packages/
└── ui (shared Nocta UI workspace)Step-by-step
1. Initialise the repo root
mkdir nocta-bun-monorepo
cd nocta-bun-monorepo
bun init --yesUpdate package.json so Bun knows where workspaces live:
{
"name": "nocta-bun-monorepo",
"module": "index.ts",
"type": "module",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}2. Scaffold the shared UI workspace (packages/ui)
mkdir -p packages/ui
cd packages/ui
bun init --yes
bun add -D tsc-alias rimraf concurrently tailwindcssConfigure TypeScript with alias support so registry files that import from @/... compile cleanly:
{
"compilerOptions": {
"lib": ["ESNext","DOM"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"verbatimModuleSyntax": false,
"outDir": "dist",
"declaration": true,
"declarationDir": "dist",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}Add build scripts so the package emits compiled artefacts to dist/:
{
"name": "ui",
"type": "module",
"private": true,
"files": ["dist"],
"exports": {
".": {
"import": "./dist/src/index.js",
"types": "./dist/src/index.d.ts"
},
"./dist/styles.css": "./dist/styles.css"
},
"main": "./dist/src/index.js",
"module": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"peerDependencies": {
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/bun": "latest",
"concurrently": "^9.2.1",
"rimraf": "^6.0.1",
"tailwindcss": "^4.1.15",
"tsc-alias": "^1.8.16",
"typescript": "^5.9.3"
},
"scripts": {
"build": "bun run clean && bun run build:css && bun run build:ts",
"build:css": "mkdir -p dist && bunx tailwindcss --input src/styles.css --output dist/styles.css --minify",
"build:ts": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"clean": "rimraf dist",
"dev": "concurrently -k -n TS,ALIAS,CSS \"bun run dev:ts\" \"bun run dev:alias\" \"bun run dev:css\"",
"dev:ts": "tsc -w -p tsconfig.json",
"dev:alias": "tsc-alias -w -p tsconfig.json",
"dev:css": "bunx tailwindcss --input src/styles.css --output dist/styles.css --watch"
}
}tsc-alias rewrites the @/... imports in the compiled output so downstream apps can resolve files without custom TS paths.
3. Set up the Next.js app (apps/web)
cd ../../
mkdir -p apps/web
cd apps/web
bun create next-app@latest . --yesPoint the app at the shared workspace via Bun workspaces:
{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"next": "16.0.0",
"react": "19.2.0",
"react-dom": "19.2.0",
"ui": "workspace:*"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}Import the compiled UI stylesheet once inside app/layout.tsx (or your preferred root file):
import "ui/dist/styles.css";4. Initialise the Nocta CLI
Run init inside each workspace so the CLI records relationships and writes helpers.
cd packages/ui
npx @nocta-ui/cli init # choose Shared UI
cd ../../apps/web
npx @nocta-ui/cli init # choose Application and link "ui"- The shared UI workspace receives
nocta.config.json, helper utilities, Tailwind tokens, and an entry insidenocta.workspace.json. - The application workspace stores a link to
ui, so futurenocta-ui addruns fromapps/webcopy React source intopackages/ui.
5. Install components and rebuild the shared package
From apps/web install components like normal:
npx @nocta-ui/cli add button cardThe CLI writes component source files into packages/ui/src/..., updates export barrels, and scopes dependency installs to the owning workspace. Rebuild or watch the shared package after each run so the compiled artefacts remain fresh:
bun run --filter ui build
# or keep everything hot
bun run --filter ui devUsing the Components
After rebuilding, import components from the shared package inside your application:
import { Button } from "ui";
export default function Page() {
return <Button>Ready for launch</Button>;
}You now have a monorepo-aware workflow where shared UI code lives in packages/ui, applications call CLI commands from their own directory, and Bun handles dependencies for the entire workspace.