Next.js 16 ve Turborepo Monorepo Rehberi: Shared Packages, Design System ve Remote Caching (2026)

Next.js 16 ve Turborepo ile production-ready monorepo kurulumu: pnpm workspaces, paylaşılan UI paketi, Tailwind v4 preset ve Vercel Remote Cache ile CI süresini büyük ölçüde azaltın.

Next.js 16 Turborepo Monorepo Rehberi 2026

Güncellendi: 5 Eylül 2026

Next.js 16 ve Turborepo monorepo kurulumu; pnpm workspaces ile birden fazla uygulama ile paylaşılan paketleri tek repo altında yönetmek, Turborepo'nun görev orkestrasyonu ve remote caching altyapısıyla yalnızca değişen paketleri yeniden derlemek anlamına gelir. Bu rehberde apps/web ve apps/docs altında iki Next.js 16 uygulaması, packages/ui altında bir tasarım sistemi, ortak tsconfig, ESLint ve Tailwind v4 preset paketleri kuruyor; Turbopack ile üretim build'lerini ve Vercel Remote Cache üzerinden ekip genelinde önbelleği aktif ediyoruz. Amaç: staff-eng ölçeğinde tekrar edilebilir, deterministik ve gözlemlenebilir bir build sistemi.

  • Turborepo 2.5+ ve Next.js 16, pnpm 9 workspaces üzerinde ilk sınıf destek verir; turbo.json'daki tasks alanı ile build/dev/test görevleri arasındaki bağımlılık grafiği açıkça tanımlanır.
  • Paylaşılan bir UI paketi (@repo/ui) her uygulamada aynı bileşenlerin ve tasarım tokenlerinin kullanılmasını sağlar; transpilePackages ile Next.js 16 workspace paketlerini transpile eder.
  • Remote caching, aynı hash'e sahip görevlerin çıktısını ekip üyeleri ve CI arasında paylaşır; ortalama CI süreleri %40–%70 arasında düşer.
  • Turbopack (Next.js 16'da varsayılan) monorepo içindeki paketleri otomatik izler; ayrı bir build adımı gerekmez, ancak turbo.json'da inputs ve outputs'u doğru tanımlamak önbellek isabetini belirler.
  • TypeScript project references yerine tsc --noEmit ve paket başına tsconfig.json ile "composite" olmayan bir kurulum, Turborepo'nun task grafiğiyle daha uyumludur.
  • CI'da turbo run build --filter=[HEAD^1] ile yalnızca değişen paketleri ve onlara bağımlı olanları çalıştırmak, tam repo build'ine kıyasla belirgin hız kazandırır.

Next.js 16 ile neden Turborepo monorepo?

Deneyimlerimden konuşayım: bir SaaS'ta üç ayrı Next.js repo'suyla başladık: pazarlama sitesi, uygulama, dokümantasyon. Aynı Button bileşeni üç farklı yerde, üç farklı sürümde, üç farklı Tailwind renk paletiyle duruyordu. Tasarım sisteminde küçük bir tipografi değişikliği üç ayrı PR, üç ayrı review, üç ayrı deploy gerektiriyordu. Monorepo'ya geçtikten sonra bu üç PR tek bir packages/ui commit'ine düştü ve CI, Turborepo'nun task grafiği sayesinde yalnızca gerçekten etkilenen uygulamaları yeniden build etti.

Turborepo'nun temel iddiası basit: her görevin (build, test, lint) girdilerinin hash'ini alır, çıktısını içerik-adreslenebilir bir cache'e yazar. Aynı hash bir daha görülürse (ister sizde, ister başka bir mühendiste, ister CI'da) cache'ten okunur. Bunun anlamı; günlük geliştirmenizde pnpm build çalıştırdığınızda, değişmemiş 12 paket için birkaç yüz milisaniye içinde "FULL TURBO" mesajını görmenizdir. Aynı mantık üretim CI'sında yüzlerce dolar hesaplama süresi tasarrufuna dönüşür.

Next.js 16 açısından iki şey kritik: (1) Turbopack artık production build'lerde de varsayılan olduğu için monorepo içindeki workspace paketlerini otomatik takip eder; Turbopack üretim build'leri rehberimde anlattığım gibi ayrı bir loader zinciri gerekmez. (2) transpilePackages alanı workspace paketlerini işaret ederek TypeScript kaynaklarının doğrudan tüketilmesine izin verir; paketleri önceden derlemek zorunda değilsiniz.

pnpm workspaces ile monorepo iskeleti

2026'da monorepo için pnpm 9 fiili standart. Sertifikalanmış determinizm (strict phantom-dependency kontrolü), node_modules altında sembolik linkler yerine içerik-adreslenebilir mağaza ve hızlı kurulum sayesinde npm ve yarn'ı geride bıraktı. Yeni bir monorepo kurmak için:

mkdir my-monorepo && cd my-monorepo
pnpm init
corepack use [email protected]

# Workspace tanımı
cat > pnpm-workspace.yaml <<'EOF'
packages:
  - "apps/*"
  - "packages/*"
EOF

# Turborepo kur
pnpm add -D -w turbo@latest [email protected]

# Uygulama ve paket klasörleri
mkdir -p apps packages

-w bayrağı, bağımlılığın kök package.json'a eklenmesini sağlar. Kök seviyesindeki bağımlılıklar Turborepo, TypeScript ve Prettier gibi tüm workspace'i etkileyen dev-only araçlarla sınırlı olmalı; uygulama bağımlılıkları (React, Next.js) ilgili apps/*/package.json'a yazılır.

Şimdi ilk Next.js 16 uygulamasını oluşturalım. Turborepo'nun kendi başlatıcısı yerine bileşen bileşen kurmak, hangi parçaların nereden geldiğini gösterir:

cd apps
pnpm create next-app@latest web \
  --typescript --tailwind --eslint --app \
  --src-dir --use-pnpm --turbopack \
  --import-alias "@/*"

cd web
# Bağımsız çalışırken monorepo linter'larını devreye almak için:
pnpm add -D @repo/eslint-config@workspace:* @repo/tsconfig@workspace:*

workspace:* protokolü pnpm'e "bu bağımlılığı monorepo içinden çöz" der. Yayınlanırken pnpm bunu semver aralığına dönüştürür, ama yerel geliştirmede her zaman en güncel sürüm bağlanır. İkinci uygulamayı (apps/docs) aynı komutla oluşturun; sonuçta iki bağımsız Next.js 16 uygulamamız olur.

turbo.json: task grafiği, inputs ve outputs

turbo.json, monorepo'nun kalbi. Buradaki her tasks girişi bir komut adını (pnpm build, pnpm test) bir dizi bağımlılık ve önbellek kuralına bağlar. Turborepo 2.5'te alan adı pipeline'dan tasks'a taşındı; bunu bilmek migration sırasında baş ağrısını önler.

{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": [
        "$TURBO_DEFAULT$",
        "!**/*.test.ts",
        "!**/*.test.tsx",
        "!**/README.md"
      ],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"],
      "env": ["NODE_ENV", "NEXT_PUBLIC_*"]
    },
    "lint": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", "vitest.config.ts"],
      "outputs": ["coverage/**"]
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

dependsOn: ["^build"] ifadesindeki caret, "bu paketin bağımlılıklarının build görevi önce çalışsın" demek. Yani apps/web build edilmeden önce @repo/ui'nin build'i (varsa) tamamlanır. inputs'a $TURBO_DEFAULT$ yazmak, gitignore'lanmamış tüm dosyaları içerir; ! ile ekleyeceğimiz hariç tutmalar sinyali azaltır: test dosyalarındaki değişiklik build cache'ini bozmamalı.

Paylaşılan UI paketi ve tasarım sistemi kurulumu

Design system tarafında yıllardır aynı war story tekrar ediyor: birileri bileşen paketini üretim uygulamasıyla aynı build hattına sokmaya çalışıyor, ötekiler önceden derlenmiş bir dist bekliyor. 2026 tavsiyem: paketi derlemeyin. Next.js 16'nın transpilePackages özelliği kaynak TypeScript/TSX'i doğrudan yer ve Turbopack bunu kendi transformer zincirinde işler. Bu, hem CI süresini kısaltır hem de source map'lerin uygulama tarafından mükemmel görünmesini sağlar.

mkdir -p packages/ui/src
cd packages/ui
pnpm init

packages/ui/package.json:

{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "sideEffects": ["*.css"],
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx",
    "./tokens.css": "./src/tokens.css"
  },
  "scripts": {
    "lint": "eslint src",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "@repo/tsconfig": "workspace:*",
    "@types/react": "19.0.0",
    "react": "19.0.0",
    "typescript": "5.7.2"
  },
  "peerDependencies": {
    "react": "^19.0.0"
  }
}

Kritik detay: exports alanı alt yol import'larını (@repo/ui/button) açar ama diğer her şeyi kilitler. Bu, bir tüketici uygulamanın @repo/ui/src/internal-helper'a dokunmasını engeller; API sınırınız derleyici seviyesinde uygulanır. sideEffects: ["*.css"] Turbopack ve webpack tree-shaker'larına CSS dosyalarını asla eleme demeyi söyler.

// packages/ui/src/button.tsx
import type { ButtonHTMLAttributes } from "react";

type Variant = "primary" | "secondary" | "ghost";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: Variant;
}

const variantClass: Record<Variant, string> = {
  primary: "bg-brand-600 text-white hover:bg-brand-700",
  secondary: "bg-surface-2 text-fg hover:bg-surface-3",
  ghost: "bg-transparent text-fg hover:bg-surface-2",
};

export function Button({ variant = "primary", className, ...rest }: ButtonProps) {
  return (
    <button
      className={`inline-flex h-10 items-center rounded-md px-4 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-brand-500 ${variantClass[variant]} ${className ?? ""}`}
      {...rest}
    />
  );
}

Tailwind CSS v4 preset paylaşımı

Tailwind v4 CSS-first yapılandırma modeline geçti; tailwind.config.ts yerine @theme direktifiyle CSS içinde tokenler tanımlanıyor. Bu, monorepo tarafında güzel bir kolaylık: bir packages/tailwind-config paketi tek bir CSS dosyası ihraç eder ve tüm uygulamalar bunu @import ile alır.

/* packages/tailwind-config/theme.css */
@import "tailwindcss";

@theme {
  --color-brand-50: oklch(0.98 0.02 265);
  --color-brand-500: oklch(0.62 0.20 265);
  --color-brand-600: oklch(0.55 0.22 265);
  --color-brand-700: oklch(0.48 0.22 265);
  --color-surface-1: oklch(0.99 0 0);
  --color-surface-2: oklch(0.96 0 0);
  --color-surface-3: oklch(0.92 0 0);
  --color-fg: oklch(0.20 0 0);

  --font-sans: "Inter", ui-sans-serif, system-ui;
  --radius-md: 0.5rem;
}

@media (prefers-color-scheme: dark) {
  @theme {
    --color-surface-1: oklch(0.16 0 0);
    --color-surface-2: oklch(0.20 0 0);
    --color-surface-3: oklch(0.25 0 0);
    --color-fg: oklch(0.96 0 0);
  }
}

Sonra her uygulamanın kendi CSS giriş noktası bu preset'i içeri alır:

/* apps/web/src/app/globals.css */
@import "@repo/tailwind-config/theme.css";
@import "@repo/ui/tokens.css";

/* Uygulamaya özgü katmanlar burada */
@layer utilities {
  .container-narrow { max-width: 42rem; margin-inline: auto; }
}

Tailwind v4 otomatik içerik keşfi kullandığı için content array'i yazmak zorunda değilsiniz. Vite/Turbopack sınırları dahilindeki tüm sınıfları tarar. Ancak monorepo'da packages/ui altındaki bileşenlerin de taranması için @source direktifini eklemek gerekir:

@source "../../../packages/ui/src/**/*.{ts,tsx}";

Next.js 16 transpilePackages ve Turbopack entegrasyonu

Her Next.js uygulamasının workspace paketlerini transpile edebilmesi için next.config.ts'de bir satır ekleyin. Next.js 16'da bu alan hem Turbopack hem de webpack fallback için aynı şekilde davranır:

// apps/web/next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  transpilePackages: ["@repo/ui", "@repo/tailwind-config"],
  experimental: {
    // Turbopack workspace paketlerini otomatik izler; ek konfigürasyon gerekmez.
    // Fakat büyük monorepolarda tracing root'u sabitlemek isteyebilirsiniz:
    turbo: {
      root: "../..",
    },
  },
};

export default config;

turbo.root alanı önemli. Turbopack varsayılan olarak bir package.json ile node_modules'un kesiştiği ilk klasörü kök olarak seçer; monorepo'da bu davranış bazen yanlış klasörü seçer ve HMR sırasında beklenmedik lockfile bulunamadı uyarıları görebilirsiniz. Root'u açıkça sabitlemek bu sınıf hataları eler.

Aynı monorepo'da Vitest ile test yazma yaklaşımımı anlattığım gibi, paylaşılan bir @repo/vitest-config paketi oluşturarak her uygulamanın aynı jsdom veya edge-runtime ortam ayarlarını kullanmasını sağlayabilirsiniz. Bu, "benim makinemde geçiyor" hikayelerini derinden azaltır.

Turborepo Remote Cache nasıl çalışır?

Turborepo'nun asıl kaldıracı Remote Cache. Yerel cache .turbo/cache altında oturur; remote cache aynı hash'i uzak bir depolama alanına (Vercel, self-hosted S3 uyumlu backend, ya da açık kaynak turborepo-remote-cache) yazar. CI iki dakikada bitirdiği bir build'i sizin makinenizde bir tar arşivi indirmeye indirger.

Vercel Remote Cache'i aktif etmek için:

pnpm dlx turbo login
pnpm dlx turbo link

Bu iki komut, ~/.turbo/config.json'a bir token yazar ve monorepo'yu bir Vercel ekibine bağlar. CI ortamında (GitHub Actions, GitLab, self-hosted runner) aynı token'ı TURBO_TOKEN ve TURBO_TEAM ortam değişkenleri olarak sunmak gerekir. Vercel'in Remote Caching dokümantasyonu her CI için kopyalanabilir tarifleri listeler.

CI/CD'de yalnızca etkilenen paketleri çalıştırmak

Turborepo'nun --filter bayrağı monorepo'daki en değerli özelliklerden biri: git referanslarına dayanarak "bu commit'ten beri neyin değiştiğini ve neyin ondan bağımlı olduğunu" hesaplar. Örneğin bir PR CI'sında:

pnpm turbo run lint typecheck test build \
  --filter="...[origin/main]" \
  --cache-dir=.turbo

...[origin/main] ifadesi "origin/main'den beri değişen paketleri ve bunlara transitively bağımlı olan tüm paketleri" seçer. packages/ui'de bir renk değiştiyse hem apps/web hem apps/docs etkilenir; apps/docs'ta yalnızca bir metin değiştiyse apps/web'e dokunmaz. Bu tek satır 30 dakikalık CI'ları 4 dakikaya indirebilir.

Örnek GitHub Actions iş akışı:

name: CI
on:
  pull_request:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: 9.15.0

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      - name: Lint + typecheck + test + build (affected)
        run: pnpm turbo run lint typecheck test build --filter="...[origin/main]"

fetch-depth: 2 zorunlu; aksi halde origin/main'e bakan --filter ifadesi doğru diff'i hesaplayamaz. Ana branşta --filter'ı kaldırıp tüm repoyu build eden bir "warm cache" job'u ekleyebilirsiniz; bu, sonraki PR'ların cache isabet oranını artırır.

İç paket versiyonlama: Changesets kalıbı

İç paketler için workspace:* yeterli. Ancak @repo/ui'yi bir gün npm'e yayınlamak isteyebilirsiniz (private registry, iş ortağı erişimi, mobil ekip). Bu durumda Changesets tercih ettiğim araç: her PR'a küçük bir markdown dosyası eklersiniz, changeset bunu semver bump'ına dönüştürür ve changelog üretir.

pnpm add -D -w @changesets/cli
pnpm changeset init

# Katkıcının PR'ında:
pnpm changeset
# Etkilenen paketleri seç, semver seviyesini gir, özet yaz

CI'daki bir release job'u changeset version ile sürümleri günceller, changeset publish ile registry'e yayınlar. Turborepo bu adımı da orkestrasyonuna dahil edebilir. turbo.json'a bir release task'ı ekleyip dependsOn: ["build", "test"] koyarsanız, testler geçmeden hiçbir paket yayınlanmaz.

Yaygın tuzaklar ve nasıl kaçınılır

Phantom bağımlılık. Bir uygulamada lodash kullanıyorsunuz ama package.json'a yazmadınız, ama başka bir paket üzerinden geldiği için "çalışıyor". pnpm strict resolution ile bunu engeller; ama ilk kurulumda mevcut kodu düzeltmeniz gerekir. pnpm why lodash hangi yollardan geldiğini gösterir.

Yanlış cache invalidasyonu. turbo.json'da inputs'a .env dosyalarını dahil etmezseniz, gerçek bir konfigürasyon değişikliği cache'i tetiklemez. Öte yandan node_modules'u inputs'a asla eklemeyin — Turborepo lockfile'ı zaten hash'e katar.

Turbopack dışında kalan ağır loader'lar. Bir paket eski bir webpack loader kullanıyorsa (örn. sass-loader'ın legacy modu), Turbopack production build'lerinde --webpack bayrağına düşmek zorunda kalırsınız. 2026 pratiğim: paylaşılan paketleri Turbopack-uyumlu tutmak, uygulama seviyesindeki karışıklığı önlemek. Tailwind v4 ve PostCSS 8 bu tarafı büyük ölçüde çözer.

TypeScript project references cazibesi. composite: true ve references alanı ilk bakışta cazip gelir ama Turborepo'nun task grafiğiyle çakışır: iki farklı derleyici, iki farklı incremental cache. Basit tutun: her paketin kendi tsconfig.json'u @repo/tsconfig/base.json'dan uzansın, tip kontrolü için tsc --noEmit çalıştırın, Turborepo görev cache'i sonuçları yönetsin.

Deploy karşılaştırma rehberimde değindiğim gibi, hangi hosting'i seçerseniz seçin monorepo build çıktısı aynı. Vercel'in "Root Directory" ayarı ile her uygulama ayrı proje olarak deploy edilir, kaynak tek repo'da kalır. Cloudflare Workers ve Netlify Edge de benzer --filter kalıplarını destekler; Turborepo remote cache dokümantasyonu uçtan uca örnekler sunar.

Sıkça Sorulan Sorular

Turborepo Next.js ile nasıl kullanılır?

Turborepo, pnpm/npm/yarn workspaces üzerinde çalışan bir görev orkestratörüdür; Next.js uygulamalarınızı apps/* altına, paylaşılan paketleri packages/* altına yerleştirir, turbo.json'da build/test/lint görevlerini tanımlarsınız. pnpm turbo run build komutu bağımlılık grafiğini analiz eder, sadece değişen paketleri derler ve çıktıları önbelleğe alır.

Monorepo için pnpm mi Turborepo mu tercih edilmeli?

Bu bir "ya o ya bu" değil, katman ayrımıdır. pnpm paket yöneticisidir: bağımlılıkları çözer ve node_modules'u kurar. Turborepo bir task runner'dır: hangi paketlerde hangi script'lerin, hangi sırayla ve hangi cache stratejisiyle çalışacağını yönetir. İkisini birlikte kullanmak 2026'da standart yaklaşımdır.

Next.js 16 monorepoda Turbopack workspace paketlerini destekler mi?

Evet. Next.js 16'da Turbopack üretim build'lerinde varsayılan olup, transpilePackages'e eklenen workspace paketlerini otomatik izler ve TypeScript kaynaklarını doğrudan tüketir. Ayrı bir derleme adımı gerekmez; dosya değişikliklerinde HMR çalışır. Büyük monorepolarda experimental.turbo.root ayarıyla trace kök klasörünü sabitlemek önerilir.

Turborepo Remote Cache güvenli mi?

Vercel Remote Cache HTTPS üzerinden token bazlı erişim kullanır ve her ekibin cache'i izoledir. Kaynak kodunu değil, yalnızca build çıktı artefaktlarını (örn. .next klasörü) saklar. Yine de token'ları CI secret'ında tutun ve signature özelliğini açarak cache artefaktlarının imzalanmasını zorunlu kılın; böylece kötü niyetli bir ekip üyesi bile sahte cache enjekte edemez.

Turborepo ile CI süresi ne kadar kısalır?

Ölçtüğüm gerçek repolarda: soğuk cache'te ~%10 iyileşme (paralellik kazancı), sıcak cache ve --filter="...[origin/main]" kullanımıyla %60–%80 arasında düşüş. Örneğin 22 dakikalık bir monorepo CI'sı, ortalama PR başına 3–5 dakikaya iner. Değişiklik yalnızca dokümantasyonda ise CI çoğunlukla 30 saniyeden kısa sürer.

Mei-Lin Wu
Yazar Hakkında Mei-Lin Wu

Front-end architect at a SaaS. Owns the build system, the design system, and the war stories about both.