Next.js em um Monorepo Turborepo
Configure o Next.js 15 dentro de um monorepo Turborepo com componentes de UI compartilhados, configuração TypeScript compartilhada e builds paralelos entre aplicativos e pacotes.
Busque em todas as páginas da documentação
Configure o Next.js 15 dentro de um monorepo Turborepo com componentes de UI compartilhados, configuração TypeScript compartilhada e builds paralelos entre aplicativos e pacotes.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
# Scaffold a new Turborepo with the default Next.js + library template
npx create-turbo@latest my-monorepo
# Choose "pnpm" when prompted (recommended for workspace protocol support)
cd my-monorepo
pnpm install
# Run all apps in dev mode in parallel
pnpm dev
# Build everything (cached)
pnpm build
# Run a task for a single workspace
pnpm turbo run dev --filter=webO template padrão oferece:
meu-monorepo/
apps/
web/ # Aplicativo Next.js 15 + React 19
docs/ # Segundo aplicativo Next.js (opcional)
packages/
ui/ # Biblioteca de componentes React compartilhada
eslint-config/ # Configuração ESLint compartilhada
typescript-config/# Arquivos base tsconfig compartilhados
turbo.json # Pipeline / grafo de tarefas
pnpm-workspace.yaml # Definições de workspace
package.json # Scripts raiz + devDependencies
Quando usar isso: Quando você tem mais de um aplicativo Next.js, um sistema de design compartilhado ou utilitários compartilhados que devem ser versionados e compilados juntos - e você deseja compilações rápidas e com cache em CI.
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"// package.json (root)
\{
"name": "my-monorepo",
"private": true,
"scripts": \{
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"typecheck": "turbo run typecheck",
"clean": "turbo run clean && rm -rf node_modules"
\},
"devDependencies": \{
"turbo": "^2.3.0",
"typescript": "^5.6.3",
"prettier": "^3.3.3"
\},
"packageManager": "pnpm@9.12.0",
"engines": \{
"node": ">=20"
\}
\}// turbo.json
\{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": \{
"build": \{
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"],
"env": ["NODE_ENV", "NEXT_PUBLIC_*"]
\},
"dev": \{
"cache": false,
"persistent": true
\},
"lint": \{
"dependsOn": ["^lint"]
\},
"typecheck": \{
"dependsOn": ["^typecheck"]
\},
"clean": \{
"cache": false
\}
\}
\}// apps/web/package.json
\{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": \{
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit"
\},
"dependencies": \{
"@repo/ui": "workspace:*",
"next": "15.1.0",
"react": "19.0.0",
"react-dom": "19.0.0"
\},
"devDependencies": \{
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/node": "^22.10.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"typescript": "^5.6.3"
\}
\}// packages/ui/package.json
\{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": \{
"./button": "./src/button.tsx",
"./card": "./src/card.tsx",
"./styles.css": "./src/styles.css"
\},
"scripts": \{
"lint": "eslint . --max-warnings 0",
"typecheck": "tsc --noEmit"
\},
"devDependencies": \{
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/react": "^19.0.0",
"react": "19.0.0",
"typescript": "^5.6.3"
\},
"peerDependencies": \{
"react": "^19.0.0"
\}
\}// packages/ui/src/button.tsx
import type \{ ButtonHTMLAttributes, ReactNode \} from "react";
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> \{
children: ReactNode;
variant?: "primary" | "secondary";
\}
export function Button(\{ children, variant = "primary", ...rest \}: ButtonProps) \{
return (
<button data-variant=\{variant\} \{...rest\}>
\{children\}
</button>
);
\}// apps/web/next.config.ts
import type \{ NextConfig \} from "next";
const nextConfig: NextConfig = \{
// Required: Next.js must transpile workspace packages that ship raw TS/TSX
transpilePackages: ["@repo/ui"],
\};
export default nextConfig;// apps/web/app/page.tsx
import \{ Button \} from "@repo/ui/button";
export default function Home() \{
return (
<main>
<h1>Turborepo + Next.js 15</h1>
<Button variant="primary">Click me</Button>
</main>
);
\}O que isso demonstra:
workspace:* vinculando apps/web a packages/uiexports expondo importações de subpath como @repo/ui/buttontranspilePackages permitindo que o Next.js compile TSX bruto de pacotes do workspaceturbo.json com ordenação topológica ^buildturbo.json para construir um grafo de tarefas em todos os workspaces.^build significa "compile todas as dependências internas antes desta tarefa" - então packages/ui é compilado (ou está pronto) antes de apps/web.env) e pula tarefas cujo hash não mudou - este é o cache local.pnpm resolve workspace:* para um symlink dentro de node_modules, então edições em packages/ui são visíveis instantaneamente em apps/web.transpilePackages em next.config.ts informa ao Next.js que @repo/ui envia código-fonte, não JS compilado, e deve passar pelo SWC.next dev --turbopack), a recarga a quente funciona entre os limites dos pacotes sem watchers adicionais.Adicionando um segundo aplicativo Next.js:
# Copy apps/web → apps/docs, change name in package.json
cp -r apps/web apps/docs
# Edit apps/docs/package.json: "name": "docs", different port
pnpm install
pnpm turbo run dev --filter=docsCompartilhando a configuração do Tailwind v4 entre aplicativos:
/* packages/ui/src/styles.css */
@import "tailwindcss";
@theme \{
--color-brand: #2563eb;
\}/* apps/web/app/globals.css */
@import "@repo/ui/styles.css";Executando tarefas em paralelo vs. em série:
# Parallel (default for independent tasks)
pnpm turbo run lint typecheck
# Series via dependsOn in turbo.json
# "build": \{ "dependsOn": ["^build", "lint"] \}Comandos com escopo com --filter:
# Only build the web app and its dependencies
pnpm turbo run build --filter=web...
# Only what changed since main
pnpm turbo run build --filter="...[origin/main]"Cache remoto com Vercel:
pnpm turbo login
pnpm turbo link
# turbo.json - optional, already on by default when linkedChangesets para versionamento de pacotes públicos:
pnpm add -Dw @changesets/cli
pnpm changeset init
pnpm changeset # record a change
pnpm changeset version # bump versions
pnpm changeset publish # publish to npm// packages/typescript-config/nextjs.json
\{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": \{
"plugins": [\{ "name": "next" \}],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": true,
"jsx": "preserve",
"noEmit": true
\},
"include": ["src", "next-env.d.ts", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
\}// apps/web/tsconfig.json
\{
"extends": "@repo/typescript-config/nextjs.json",
"compilerOptions": \{
"baseUrl": ".",
"paths": \{
"@/*": ["./*"]
\}
\},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
\}Para um pacote apenas de tipos compartilhado, crie packages/types com "types": "./src/index.ts" e nenhum código de runtime - cada workspace pode então fazer import type \{ User \} from "@repo/types".
workspace:* é um recurso do pnpm/yarn - o npm não entende o protocolo workspace. Correção: use pnpm (recomendado pelo Turborepo), ou Yarn 3+/4, ou Bun. Se você precisar usar npm, substitua workspace:* por * e confie na vinculação integrada de workspace do npm.
Perda de cache do Turbo devido a lockfile inconsistente - se pnpm-lock.yaml mudar no CI, mas não localmente (ou vice-versa), todas as tarefas serão reexecutadas. Correção: comite o lockfile, use pnpm install --frozen-lockfile no CI e mantenha o campo packageManager no package.json raiz fixado.
Variáveis de ambiente não passando para todas as tarefas - o Turborepo faz hash das variáveis de ambiente que você lista explicitamente. Uma variável não declarada no env do turbo.json é removida da tarefa, levando a "undefined" misteriosos no momento da compilação. Correção: adicione cada variável necessária ao array env para essa tarefa (por exemplo, "env": ["DATABASE_URL", "NEXT_PUBLIC_*"]).
Recarga a quente entre pacotes quebra sem transpilePackages - o Next.js se recusa a compilar TSX bruto de node_modules por padrão, então edições em @repo/ui falham ou não são atualizadas. Correção: adicione o pacote a transpilePackages em next.config.ts. Alternativamente, compile o pacote para JS e envie uma pasta dist/.
Dependências circulares entre pacotes - @repo/ui importando de @repo/utils que importa de @repo/ui quebrará silenciosamente o grafo de tarefas do Turbo e causará falhas em tempo de execução. Correção: execute pnpm turbo run build --dry para inspecionar o grafo e use um design de pacote em camadas (pacotes folha sem dependências internas na parte inferior).
Campo exports sombreando main - uma vez que você adiciona "exports" a um pacote, qualquer subpath não listado se torna inacessível. Correção: liste explicitamente cada ponto de entrada que você precisa, incluindo "./package.json" se os consumidores o lerem.
Turbopack e pacotes de workspace - compilações muito antigas do Turbopack não seguiam symlinks corretamente. Se o servidor de desenvolvimento mostrar código desatualizado, mate-o, rm -rf apps/web/.next e reinicie.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Nx | Você precisa de geradores, visualização de grafo e ecossistema de plugins para repositórios políglotos | Você quer a ferramenta mais leve possível com zero configuração |
| Lerna | Mantendo um repositório Lerna existente | Começando do zero (agora é um wrapper fino em torno do Nx) |
| Apenas workspaces do Yarn | Monorepo simples apenas de vinculação sem necessidades de cache de tarefas | Você precisa de cache remoto ou recursos de grafo de tarefas |
| Apenas workspaces do pnpm | Você só precisa de vinculação de pacotes, não de orquestração de tarefas | Você executa muitas tarefas em CI e deseja cache |
| Rush (Microsoft) | Monorepos corporativos muito grandes com controles de política rigorosos | Equipes pequenas - tem uma curva de aprendizado acentuada |
| Workspaces do Bun | Projetos apenas do Bun que desejam a instalação mais rápida | Você precisa da maturidade e do ecossistema do pnpm/Turbo |
Isso diz ao pnpm (ou yarn/bun) para resolver essa dependência para o pacote do workspace correspondente no monorepo via um symlink, em vez de baixar do npm. O * significa "qualquer versão atualmente no workspace".
O Next.js ignora TypeScript e JSX em node_modules por padrão. Como @repo/ui é vinculado via symlink em node_modules e envia .tsx brutos, você deve listá-lo em transpilePackages para que o compilador SWC do Next o processe.
O circunflexo ^ significa "a tarefa de compilação das minhas dependências de workspace deve ser executada primeiro" (topológico). Sem o circunflexo, significa "a tarefa de compilação deste mesmo workspace deve ser executada primeiro" (dependência de tarefa dentro do mesmo pacote).
Use o filtro com reticências no final: pnpm turbo run build --filter=web.... Isso inclui web e todos os workspaces dos quais ele depende.
Ele calcula um hash de: entradas da tarefa (arquivos de código-fonte), o lockfile, as variáveis de ambiente env declaradas e o grafo de tarefas resolvido. Se o hash corresponder a uma execução anterior, ele reproduz a saída e os logs em cache em vez de reexecutar.
Execute pnpm turbo login e depois pnpm turbo link. Isso grava um arquivo .turbo/config.json apontando para sua equipe Vercel e envia artefatos de cache em cada tarefa bem-sucedida.
O Turborepo remove variáveis de ambiente não declaradas na matriz env (ou globalEnv) do turbo.json, pois variáveis de ambiente não rastreadas envenenariam o cache. Adicione o nome da variável à lista env para essa tarefa.
Provavelmente você adicionou o arquivo, mas esqueceu de listá-lo no campo exports do packages/ui/package.json. Uma vez que exports está presente, cada subpath deve ser declarado explicitamente.
Crie packages/typescript-config com arquivos como base.json e nextjs.json, adicione-o como uma devDependency do workspace e use "extends": "@repo/typescript-config/nextjs.json" no tsconfig.json de cada aplicativo.
Defina paths no tsconfig.json do aplicativo consumidor, não na base compartilhada. paths são resolvidos em relação a baseUrl, que deve ser o diretório do aplicativo - então cada aplicativo Next.js possui seu próprio bloco paths, enquanto ainda estende as opções do compilador compartilhadas.
Sim, o Turborepo suporta npm, yarn, pnpm e bun. No entanto, o npm não suporta o protocolo workspace:*, então você precisará usar * ou versões explícitas e perderá algumas garantias sobre a vinculação contra a cópia local.
Crie apps/docs, aponte-o para os pacotes compartilhados @repo/typescript-config e @repo/eslint-config, adicione @repo/ui como uma dependência do workspace, e o Turborepo o descobrirá automaticamente na próxima execução de pnpm install. Use --filter=docs para direcioná-lo.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥