package-template
A TypeScript monorepo template with pnpm workspaces and Turborepo

About
A TypeScript monorepo template with pnpm workspaces and Turborepo
Install
Source
Labels
- monorepo
- nodejs
- pnpm
A TypeScript monorepo template with pnpm workspaces and Turborepo

A TypeScript monorepo template with pnpm workspaces and Turborepo
Ready to ship?
Install the CLI to scaffold a project in under five minutes, or talk to our delivery team if you need a hand. Same templates, same contracts, same guarantees.
👉 Building an app, not a library? See deessejs/saas-template — a full single-tenant SaaS starter with Next.js, Better Auth, Drizzle, and three deployable apps.
After clicking Use this template, do these five things before your first commit:
# 1. Install
pnpm install
# 2. Rename the example package
# packages/example/package.json → set "name" to "@your-scope/your-package"
# Update the directory name too: git mv packages/example packages/your-package
#
# Or skip steps 2–3 by running `pnpm setup`, which rewrites every
# manifest and the README in one pass.
# 3. Point the repo at yourself
# package.json → name, description, author, repository
# .github/CODEOWNERS → your team
# LICENSE → copyright holder
# 4. (Optional) Configure npm Trusted Publishing for @your-scope
# https://docs.npmjs.com/trusted-publishers/ — no NPM_TOKEN required.
# 5. Verify everything is green
pnpm lint && pnpm type-check && pnpm test:run && pnpm buildThat's it. Write code in packages/your-package/src/, run pnpm changeset when you
want to release, and the pipeline handles the rest.
| Layer | What you get | Why it matters |
|---|---|---|
| Package | packages/example — ESM-only, exports map, separate build tsconfig, files allowlist | Publishes clean. No source, tests, or configs in the tarball. |
| Docs site | apps/web — Next.js 16 + Fumadocs, MDX content, full-text search, llms.txt | Deployable docs from day one, LLM-readable by default. |
| Testing | Vitest, node environment, globals enabled | Fast, zero-config, ESM-native. |
| Releases | Changesets + label-gated publish workflow | Semver and changelogs are decided in the PR, not at publish time. |
exports map is correct, and the tarball ships dist/ and nothing else. You don't have to learn npm packaging to ship.llms.txt routes, not a placeholder README rendered as HTML.engines.node: ">=22" is enforced)corepack enable if you don't have itRun from the repo root.
| Command | What it does |
|---|---|
pnpm dev | Start the docs site in dev mode |
pnpm build | Build every workspace |
pnpm test | Run tests in watch mode |
pnpm test:run | Run tests once, then exit — use this in scripts |
pnpm test:coverage | Run tests once with V8 coverage and write reports to packages/example/coverage/ |
pnpm coverage | Workspace alias for pnpm test:coverage |
Scoped to a single workspace:
pnpm --filter @deessejs/example build
pnpm --filter @deessejs/example test
pnpm --filter web devmkdir -p packages/my-package/srcCopy package.json, tsconfig.json, tsconfig.build.json, eslint.config.js, and
vitest.config.ts from packages/example, then update the name. Turbo and pnpm pick
it up on the next pnpm install — the workspace glob is packages/*, so there's no
registry to edit.
To depend on it from another workspace package:
{
"dependencies": {
"@deessejs/my-package": "workspace:*"
}
}Releases are driven by Changesets. Versions are decided in the PR, not at publish time.
# 1. On your feature branch, describe the change
pnpm changeset
# → pick the packages, pick patch/minor/major, write a one-line summary
# → commit the generated file in .changeset/
# 2. Open your PR as usualWhen the PR is ready to ship, merge it. The release workflow is fully
automatic: it builds, tests, versions, and publishes to npm on every push
to main. No labels, no manual steps.
Every push to a branch and every pull request installs a preview tarball via pkg.pr.new. The bot posts the install command on each PR automatically — no secrets, no npm publish, no extra config.
To enable PR comments, install the pkg-pr-new GitHub App
on the repository. The preview.yml workflow runs without it; it just
won't post comments.
| Secret | Required | Purpose |
|---|---|---|
NPM_TOKEN | No | Only required if you opt out of npm Trusted Publishing (OIDC) |
TURBO_TOKEN | No | Turborepo remote cache token |
TURBO_TEAM | No | Turborepo remote cache team (a repo variable, not a secret) |
GITHUB_TOKEN is provided automatically.
apps/web is a Fumadocs site. Write MDX in content/docs/ and it appears in the sidebar
automatically — the file tree is the navigation.
pnpm --filter web dev # http://localhost:3000| Env var | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_APP_URL | No | Canonical URL, used for OG images and metadata |
NEXT_PUBLIC_FUMADOCS_URL | No | Override the docs base URL when hosted separately |
Copy apps/web/.env.example to apps/web/.env.local to start. Defaults work locally.
Point a Vercel project at this repo with Root Directory set to apps/web. Build and
install commands are detected. The llms.txt and llms-full.txt routes are generated at
build time, so LLM crawlers get your docs as plain text with no extra setup.
tsconfig.json is noEmit for your editor and type-check. tsconfig.build.json emits to dist/ and excludes tests, so test files never reach the tarball.exports map declares import and types, with no CommonJS fallback. If you need CJS consumers, add a bundler — tsc alone won't produce a dual build.files, not .npmignore. The package allowlists what ships. New files are excluded by default, which is the safe direction to be wrong in.build declares dependsOn: ["^build"], so dependencies build before dependents. Add workspace deps freely; the order follows.eslint.config.js), not .eslintrc. Each workspace owns its own..husky/pre-commit. Use git commit --no-verify to skip when you need to.Open an issue to discuss larger changes. For typos, broken links, and small fixes, PRs are welcome. See CONTRIBUTING.md for the workflow and commit conventions.
Using this template for your own project and hit a rough edge? Open an issue here so the template improves for everyone.
MIT.
| Lint, type-check, test, build, coverage — each its own workflow, all with Turbo cache |
| Failures name themselves. The coverage workflow posts a PR comment with thresholds, never blocks. |
| Tooling | pnpm 10 workspaces, Turborepo v2, Prettier, ESLint 9 flat config, husky | One command lints, types, tests, builds the whole workspace. |
| Governance | 5 issue templates, PR template, CODEOWNERS, Dependabot, SECURITY.md | The paperwork a public repo needs, already filled in. |
pnpm lint| Lint every workspace |
pnpm type-check | Type-check every workspace |
pnpm format | Rewrite files with Prettier |
pnpm format:check | Check formatting without writing |
pnpm changeset | Record a version bump and changelog entry |
pnpm clean | Remove build outputs and node_modules |
.
├── apps/
│ └── web/ # Next.js 16 + Fumadocs documentation site
│ ├── content/docs/ # Your MDX pages
│ └── src/app/ # Routes, incl. llms.txt and OG image generation
├── packages/
│ └── example/ # The publishable package — rename this
│ ├── src/ # Source
│ ├── tests/ # Vitest suites (outside src so dist/ stays clean)
│ ├── tsconfig.json # Editor / type-check config (noEmit, includes tests)
│ └── tsconfig.build.json # Build config (emits dist/, excludes tests)
├── .changeset/ # Pending version bumps
├── .github/
│ ├── workflows/ # lint, types, tests, build, coverage, release
│ └── ISSUE_TEMPLATE/ # bug, feature, docs, refactor, task
├── pnpm-workspace.yaml
└── turbo.json # Task pipeline and cache confignpm i https://pkg.pr.new/<owner>/<repo>/<package>@<sha>