Turborepo + pnpm monorepo template for building and publishing a React component library to npm.
| Package / App | Description |
|---|---|
packages/lib |
React component library, built with tsup |
apps/playground |
Vite app for developing and testing components live |
apps/storybook |
Storybook 8 for component docs and visual testing |
| Tool | Purpose |
|---|---|
| Turborepo | Task orchestration and caching |
| pnpm workspaces | Package manager |
| tsup | Library bundler — CJS, ESM and .d.ts output |
| Vite | Dev server for playground and Storybook |
| Vitest + @testing-library/react | Unit tests |
| Storybook 8 | Component docs and visual testing |
CSS + injectStyle |
Styles injected into the JS bundle at runtime |
| ESLint 9 + Prettier | Linting and formatting |
| commitlint + husky | Conventional Commits enforcement |
- nvm
- pnpm 9+ (activated automatically via corepack in
setup.sh)
Run the setup script once — it installs the correct Node.js and pnpm versions, then installs all dependencies:
pnpm setupAfter setup:
pnpm build
pnpm dev # starts playground on :5173 and Storybook on :6006pnpm setup # install correct Node.js and pnpm versions, then install dependencies
pnpm build # build all packages
pnpm dev # watch mode: playground + storybook
pnpm test # run all tests
pnpm check # lint + typecheck + format check (all at once)Releases are handled automatically by semantic-release on every push to main. A new version is published to npm when there is at least one feat or fix commit since the last release.
Go to Settings → Secrets and variables → Actions in your GitHub repository and add:
| Secret | Where to get it |
|---|---|
NPM_TOKEN |
npmjs.com → Avatar → Access Tokens → Generate New Token → Granular Access Token → Packages: Read and Write → enable Allow publishing without 2FA |
GITHUB_TOKEN |
Provided automatically by GitHub Actions — no setup needed |
SSH_DEPLOY_KEY |
See below |
GITHUB_TOKEN is injected by GitHub automatically; the CI workflow already requests the required permissions (contents: write, issues: write, pull-requests: write).
CI uses SSH for all git push operations (tags, release commits) because GitHub's HTTPS smart-HTTP may reject pushes from GITHUB_TOKEN in some repository configurations.
Generate a key pair locally (no passphrase):
ssh-keygen -t ed25519 -C "ci deploy key" -f deploy_key -N ""Then:
- Settings → Deploy keys → Add deploy key — paste
deploy_key.pub, enable Allow write access - Settings → Secrets and variables → Actions → New repository secret — name
SSH_DEPLOY_KEY, paste the contents ofdeploy_key - Delete both key files locally
- Replace
@repo/libwith your package name inpackages/lib/package.jsonand in any app that imports it. - In
packages/lib/package.json: remove"private": true, fill indescription,keywords,repository,license. - CI will publish automatically on the next push to
mainwith afeatorfixcommit. To publish manually:pnpm build cd packages/lib npm publish
By default styles are injected into the JS bundle at runtime (injectStyle: true in tsup.config.ts). This is the simplest setup for client-side apps.
For SSR (Next.js, Remix, etc.) switch to a separate CSS file:
- In
tsup.config.tssetinjectStyle: false. - Add a
./stylesexport topackages/lib/package.json:{ "./styles": "./dist/index.css" } - Import it in consumers:
import '@repo/lib/styles'
Commits follow Conventional Commits and are validated on every commit by a husky hook.
feat: add Tooltip component
fix: correct Button disabled pointer-events
docs: add CSS Modules SSR guide
chore: bump tsup to 8.4
Common types: feat, fix, docs, style, refactor, test, chore, build, ci.