"Why pnpm", "how would you set up a monorepo", and "how do you keep dependencies safe" are standard senior questions, and the answers connect: pnpm's strictness and catalogs make monorepos coherent, Turborepo makes them fast, changesets make publishing predictable, and a few settings make the supply chain less dangerous.
node_modules is strict: a package can only import what it declares, which catches phantom dependencies that npm and Yarn's flat layouts hide. pnpm 10 (2025) stopped running dependency lifecycle scripts by default and added minimumReleaseAge; pnpm 11 (April 2026) made a one-day minimum release age the default, replaced the build-script settings with a single allowBuilds map, requires Node 22+, moved every non-auth setting out of .npmrc into pnpm-workspace.yaml, and added pnpm ci; pnpm 12 (August 2026) is current and fails on unrecognised workspace settings instead of ignoring them. Nuxt's own tooling and starters default to pnpm.pnpm-lock.yaml is committed; CI installs with --frozen-lockfile so a drift between package.json and the lockfile fails instead of silently resolving; pnpm install --lockfile-only updates it deliberately; pnpm outdated and pnpm update --interactive --latest for upgrades; nuxt upgrade for Nuxt itself with --dedupe.pnpm-workspace.yaml catalog:) define one version per dependency for the whole workspace, referenced as "vue": "catalog:"; overrides force a nested dependency's version (security fixes, deduplication); pnpm patch edits a dependency and stores the diff in patches/, applied on install, for the fix that upstream has not merged yet.packages: globs in pnpm-workspace.yaml; workspace:* protocol for internal dependencies (rewritten to real versions on publish); pnpm --filter <name>... runs tasks in a package and its dependencies; pnpm -r across all.turbo.json tasks, dependsOn: ["^build"], outputs, inputs) runs tasks in dependency order with local and remote caching, so unchanged packages are not rebuilt in CI; Nx offers similar with generators and a richer project graph. For a workspace of a few Nuxt apps and layers, Turborepo plus pnpm is the common choice; single-app repos need neither.workspace:* packages (extends: ['@team/layer-ui']) without a build step, since Nuxt processes their source; library packages either build to dist (with exports and types) or export source with the development condition for dev and built files for production. One Vue and one Nuxt version across the workspace, enforced by the catalog..changeset/*.md with bump type), changeset version bumps and writes changelogs, changeset publish releases; npm publish --provenance from CI (GitHub Actions OIDC) attaches a signed attestation; publishConfig.access, files, exports with types conditions, sideEffects: false for tree-shaking, and pnpm pack --dry-run to inspect the tarball; @nuxt/module-builder for modules and unbuild or Vite library mode for packages.packageManager (or devEngines.packageManager) field, honoured by pnpm itself (managePackageManagerVersions, on by default) now that Corepack is no longer bundled with Node (removed from the distribution starting with Node 25); .nvmrc/.node-version read by actions/setup-node and fnm, or pnpm 12's project-aware node shim driven by devEngines.runtime; engines with engineStrict. Node 24 is the active LTS for a new project in September 2026; Node 26 becomes LTS on 28 October 2026.minimumReleaseAge, one day by default since pnpm 11), disallow install scripts by default (allowBuilds names the exceptions), require provenance for your own packages, use pnpm audit and Socket or Snyk in CI, review postinstall allow-lists, use trusted publishing with OIDC instead of long-lived tokens, and enable two-factor authentication on registry accounts. The security section goes deeper.bun preset), Yarn Berry with Plug'n'Play in some enterprises, npm with npm ci where the platform mandates it; the trade-off is ecosystem compatibility versus speed and strictness.A workspace with two Nuxt apps, two layers and a shared package:
packages:
- apps/*
- layers/*
- packages/*
catalog:
nuxt: ^4.5.2
vue: ^3.5.43
typescript: ^6.0.3
vitest: ^5.0.0
'@nuxt/eslint': ^1.17.0
allowBuilds: # pnpm 11+: the only packages allowed to run install scripts
esbuild: true
sharp: true
minimumReleaseAge: 4320 # minutes; pnpm 11+ defaults to 1440 (one day), this waits 3 days
{
"name": "@team/shop",
"private": true,
"type": "module",
"packageManager": "pnpm@12.5.1",
"engines": { "node": ">=24" },
"scripts": { "dev": "nuxt dev", "build": "nuxt build", "typecheck": "nuxt typecheck", "postinstall": "nuxt prepare" },
"dependencies": {
"nuxt": "catalog:",
"vue": "catalog:",
"@team/layer-ui": "workspace:*",
"@team/schemas": "workspace:*"
}
}
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": [".output/**", "dist/**"], "env": ["NUXT_PUBLIC_*"] },
"typecheck": { "dependsOn": ["^build"] },
"lint": {},
"test": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", "!**/*.md"] },
"dev": { "cache": false, "persistent": true }
}
}
pnpm turbo run lint typecheck test build --filter=...[origin/main] # only what changed since main, with caching
pnpm --filter @team/shop dev
pnpm -r exec nuxt upgrade --dedupe
Publishing a package with changesets and provenance:
permissions: { contents: write, pull-requests: write, id-token: write }
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with: { node-version-file: .nvmrc, cache: pnpm, registry-url: https://registry.npmjs.org }
- run: pnpm install --frozen-lockfile
- run: pnpm -r build
- uses: changesets/action@v1
with: { publish: pnpm changeset publish }
env: { NPM_CONFIG_PROVENANCE: 'true' } # trusted publishing via OIDC; no long-lived token needed
Fixing a dependency before upstream merges:
pnpm patch some-lib@2.4.1 # opens a temp copy to edit
pnpm patch-commit <path> # writes patches/some-lib@2.4.1.patch and links it in package.json
Code imports lodash-es because a dependency pulled it in, and it works locally under npm's flat layout. Under pnpm, or after that dependency drops lodash, the import fails. Declare what you import; pnpm's strict layout is the guardrail, not the problem.
shamefullyHoist: true or publicHoistPattern: ['*'] in pnpm-workspace.yaml (since pnpm 11 .npmrc holds only auth and registry settings) makes pnpm behave like npm and silences resolution errors, usually to satisfy one misbehaving package. Prefer a targeted publicHoistPattern for that package or a patch, and file the upstream issue.
Current pnpm settings names and defaults (minimumReleaseAge, allowBuilds, managePackageManagerVersions), whether Node 26 ships without Corepack as announced, the current Turborepo config keys, and npm trusted publishing requirements.
pnpm why vue and make sure one version resolves everywhere.build on an unchanged package; it should be a cache hit.pnpm pack --dry-run."pnpm installs fast and cheaply through a content-addressable store, and its strict node_modules catches phantom dependencies that flat layouts hide. Since version 10 it refuses dependency install scripts unless allow-listed, and since 11 it waits a day by default before installing a newly published version, which are real supply-chain defences. For the monorepo: a pnpm-workspace.yaml with apps, layers and packages, a catalog that pins one Nuxt and one Vue version for everyone, workspace:* for internal dependencies, Nuxt layers consumed from source, and Turborepo for task ordering and remote caching so an unchanged layer is not rebuilt in CI. Publishing internal packages goes through changesets and trusted publishing with provenance. The lockfile is committed and CI installs frozen, so what runs in production is exactly what was reviewed."
Nothing yet. Notes filed here show up on this page and the section overview.
Linting, formatting and types in CI
ESLint 10 flat config through @nuxt/eslint with stylistic rules instead of Prettier, Vue and TypeScript rule sets worth enabling, type checking with vue-tsc via nuxt typecheck and the TypeScript 7 situation, Oxlint and Biome as fast complements, editor integration, pre-commit hooks versus CI gates, and treating warnings as failures.
CI/CD pipelines
Designing a pipeline for a Nuxt app — stages and their order, caching installs and build outputs, parallel jobs and sharded tests, building once and testing the artifact, preview deployments per pull request, environment promotion, release strategies and rollbacks, secrets and OIDC, flaky-test policy, and the metrics that tell you the pipeline is healthy.