git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/next-monorepo-setup" ~/.claude/skills/intense-visions-harness-engineering-next-monorepo-setup && rm -rf "$T"
agents/skills/claude-code/next-monorepo-setup/SKILL.mdNext.js Monorepo Setup
Configure Next.js inside a monorepo with shared packages and Turborepo task orchestration
When to Use
- Building multiple Next.js apps that share UI components, utilities, or configuration
- Organizing a full-stack TypeScript project with a shared database schema or API types
- Setting up Turborepo for parallel builds and remote caching across workspace packages
- Sharing ESLint, TypeScript, and Tailwind configuration across apps without duplication
- Publishing internal packages consumed by multiple Next.js frontends
Instructions
- Use
(or npm/yarn workspaces) to declare the monorepo structure — define packages inpnpm workspaces
.pnpm-workspace.yaml - Add
totranspilePackages: ['@repo/ui', '@repo/lib']
for any internal packages that ship TypeScript source instead of compiled output.next.config.ts - Configure
path aliases in each app to resolve internal packages:tsconfig.json
."@repo/ui": ["../../packages/ui/src"] - Use
to define task pipelines —turbo.json
depends onbuild
(all transitive deps must build first).^build - Share TypeScript config by creating a
package with base configs that each app extends.packages/typescript-config - Share ESLint config via a
package — export named configs for Next.js, React library, and Node.js contexts.packages/eslint-config - Define environment variables in each app's
— do not share.env
files across apps (each app has its own secrets)..env - Use
to start only the web app and its local dependencies during development.turbo run dev --filter=@repo/web
monorepo/ apps/ web/ ← Next.js app next.config.ts package.json ← "name": "@repo/web" admin/ ← Second Next.js app packages/ ui/ ← Shared React components src/ button.tsx package.json ← "name": "@repo/ui", main: "./src/index.ts" typescript-config/ ← Shared tsconfig bases eslint-config/ ← Shared ESLint configs turbo.json pnpm-workspace.yaml package.json ← workspace root
// apps/web/next.config.ts import type { NextConfig } from 'next'; const config: NextConfig = { transpilePackages: ['@repo/ui', '@repo/db'], }; export default config; // turbo.json — task pipeline { "$schema": "https://turbo.build/schema.json", "tasks": { "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**"] }, "dev": { "cache": false, "persistent": true }, "lint": { "dependsOn": ["^build"] }, "typecheck": { "dependsOn": ["^build"] } } }
Details
A Next.js monorepo has two main integration points: package resolution (TypeScript can find shared packages) and build orchestration (shared packages compile before the apps that consume them).
vs compiled output: Internal packages can either ship TypeScript source (simpler, no build step needed) or compiled JavaScript. With TypeScript source, add the package to transpilePackages
transpilePackages so Next.js compiles it. Compiled output packages need a build step in the Turborepo pipeline (build must run before the consuming app's build).
Turborepo remote caching: Run
turbo login and turbo link to connect to Vercel Remote Cache. CI builds share a cache — if @repo/ui did not change, its build is replayed from cache. This dramatically reduces CI times on large monorepos.
Path aliases vs package resolution: TypeScript path aliases (
@repo/ui → ../../packages/ui/src) work for type checking but not for runtime resolution. The package's main/exports field in package.json controls runtime resolution. Keep both in sync.
task with dev
: Turborepo treats persistent tasks (long-running dev servers) differently from build tasks. Mark persistent: true
dev as persistent: true so Turbo does not wait for it to exit before starting dependent tasks.
Shared Tailwind config: Create
packages/tailwind-config/index.ts exporting a shared Tailwind preset. Each app imports it in tailwind.config.ts via presets: [require('@repo/tailwind-config')]. Include the shared package's source paths in content glob patterns so Tailwind scans them for class names.
Source
https://turbo.build/repo/docs/guides/tools/nextjs
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.