Web razvoj
Next.jsMonorepoTurborepopnpmWorkspacesCI/CDTypeScript

Next.js monorepo s Turborepo + pnpm Workspaces: praktičan vodič za postavljanje u 2026.

AO
Adrijan Omićević
·13 min čitanja

# Što ćete izgraditi i zašto je to važno#

Ovaj vodič prikazuje produkcijski spremno postavljanje za Next.js monorepo Turborepo pnpm workspaces stack sa zajedničkim paketima, dosljednim toolingom i cacheiranjem prilagođenim CI-u.

Dobro strukturiran monorepo može smanjiti dupliciranje koda, ubrzati buildove i standardizirati “quality gateove”. Turborepoov task graph i caching mogu drastično skratiti CI vrijeme kada se mijenja samo podskup paketa — što je u stvarnim timovima uobičajeno.

Ako ste novi u Next.js osnovama, krenite s našim vodičem: Uvod u Next.js. Ako vam je glavni motiv dijeljeni UI, pročitajte i Arhitektura React komponenti za skalabilan design system.

# Kada monorepo ima smisla (a kada nema)#

Monorepo nije zadani izbor. To je alat koji daje leverage u specifičnim organizacijskim i tehničkim okolnostima.

Dobri slučajevi za Next.js monorepo#

Koristite monorepo kada je barem jedno od ovoga istinito:

  • Više aplikacija dijeli kod: marketing stranica, dashboard, admin portal, dokumentacija — svi ponovno koriste isti UI kit i auth utilse.
  • Dijeljeni UI i design system: jedna biblioteka komponenti koju koristi 2 do 10 aplikacija, održavana sinkronizirano.
  • Dijeljeni backend klijenti i tipovi: zajednički OpenAPI klijenti, Zod sheme ili domenski modeli korišteni kroz aplikacije.
  • Platform timovi: jedan tim održava zajedničke pakete, a drugi timovi ih koriste uz predvidljivo verzioniranje i CI provjere.
  • Želite dosljedan tooling: jedna lint konfiguracija, jedna TS konfiguracija, jedna test strategija — provedena svugdje.

U praksi, monorepoi često smanjuju “configuration drift” među projektima. Taj drift predstavlja stvaran produkcijski rizik: nedosljedna lint pravila, zastarjele ovisnosti u jednom repozitoriju, propuštene sigurnosne provjere u drugom.

Kada je monorepo loša ideja#

Monorepo može biti pogrešan potez kada:

  • Imate samo jednu aplikaciju i nemate zajedničke pakete.
  • Imate stroge zahtjeve izolacije između projekata, npr. različite compliance scopeove.
  • Timovi nisu usklađeni oko zajedničkih konvencija, pa “centralna konfiguracija” postaje usko grlo.
  • Vaša CI infrastruktura je spora i ne možete učinkovito koristiti caching, pa monorepo buildovi djeluju “teže” nego odvojeni repozitoriji.

🎯 Ključna poruka: Monorepo se isplati kada imate više aplikacija i zajedničke pakete. Ako toga nema, uglavnom kupujete složenost.

# Preduvjeti#

ZahtjevVerzijaNapomene
Node.js20+Koristite LTS verziju za predvidljivo ponašanje u CI-u
pnpm9+Podrška za workspace i učinkovita potrošnja diska
GitHub Actions ili sličan CIPrimjeri koriste GitHub Actions koncepte
Next.js14 ili 15Radi s obje verzije, primjeri pretpostavljaju App Router obrasce
TypeScript5+Snažno preporučeno za zajedničke pakete

# Struktura repozitorija (Apps + Packages)#

Praktična Turborepo struktura odvaja deployable aplikacije od zajedničkih paketa:

PutanjaTipŠto ide ovdje
apps/webNext.js appGlavna web aplikacija okrenuta korisnicima
apps/adminNext.js appInterni admin dashboard
packages/uiShared packageKomponente design systema
packages/configShared packageESLint, TS config, Prettier config
packages/sharedShared packageUtils, tipovi, API klijenti
toolingOptionalSkripte, generatori, dev utilsi

Ova struktura podržava neovisne deployeve po aplikaciji, dok se zajednički kod verzionira zajedno.

ℹ️ Napomena: Držanje konfiguracije u paketu poput packages/config izbjegava copy-paste i čini nadogradnje jednom promjenom umjesto čišćenja kroz cijeli repo.

# Korak 1: Inicijalizirajte Turborepo s pnpm Workspaces#

Kreirajte repo i inicijalizirajte Turborepo. Možete krenuti od službenih Turbo templateova, ali vrijedi razumjeti što se zapravo kreira.

Bash
mkdir nextjs-monorepo
cd nextjs-monorepo
pnpm init
pnpm add -D turbo

Kreirajte pnpm-workspace.yaml u rootu repozitorija:

YAML
packages:
  - "apps/*"
  - "packages/*"

Ažurirajte package.json skripte u rootu kako biste upravljali svim taskovima:

JSON
{
  "name": "nextjs-monorepo",
  "private": true,
  "packageManager": "pnpm@9.0.0",
  "scripts": {
    "dev": "turbo run dev --parallel",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "typecheck": "turbo run typecheck",
    "clean": "turbo run clean && rm -rf node_modules .turbo"
  },
  "devDependencies": {
    "turbo": "^2.0.0"
  }
}

Dodajte Turbo konfiguraciju taskova#

Kreirajte turbo.json:

JSON
{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    },
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**", "build/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "typecheck": {
      "dependsOn": ["^typecheck"]
    },
    "test": {
      "dependsOn": ["^test"],
      "outputs": ["coverage/**"]
    },
    "clean": {
      "cache": false
    }
  }
}

Pravilo dependsOn osigurava da se paketi buildaju prije aplikacija koje ih koriste. Lista outputs je ključna za ispravno caching ponašanje u CI-u.

💡 Savjet: Budite konzervativni s outputs. Ako zaboravite output folder, Turborepo će promašivati cache hitove. Ako uključite previše, možete slučajno cacheirati nedeterminističke artefakte.

# Korak 2: Kreirajte Next.js aplikacije u apps/*#

Kreirajte dvije aplikacije kako biste demonstrirali zajedničke pakete. Koristite omiljenu Next.js bootstrap metodu, zatim premjestite foldere u apps/.

Primjer postavljanja foldera:

Bash
mkdir -p apps/web apps/admin packages/ui packages/shared packages/config

Unutar svake aplikacije želite dosljedne skripte. Primjer apps/web/package.json:

JSON
{
  "name": "web",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3000",
    "build": "next build",
    "start": "next start -p 3000",
    "lint": "next lint",
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "test": "vitest run",
    "clean": "rm -rf .next"
  }
}

Ponovite za apps/admin i odaberite drugi port.

Zajedničke Next.js postavke: ESLint i TS#

Next.js dolazi s opinionated defaultima. U monorepu je ključno centralizirati što više, bez “borbe” s frameworkom.

  • Držite Next.js-specifičnu konfiguraciju unutar svake aplikacije.
  • Centralizirajte ESLint pravila, TS base config i Prettier u packages/config.

# Korak 3: Dodajte zajedničke pakete (UI + zajednički utilsi)#

packages/shared: tipovi i utilsi#

Kreirajte packages/shared/package.json:

JSON
{
  "name": "@acme/shared",
  "version": "0.0.0",
  "private": true,
  "main": "src/index.ts",
  "types": "src/index.ts",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "lint": "eslint .",
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "test": "vitest run",
    "clean": "rm -rf dist"
  }
}

Kreirajte packages/shared/src/index.ts:

TypeScript
export type User = {
  id: string;
  email: string;
};
 
export function formatUserLabel(user: User) {
  return `${user.email} (${user.id})`;
}

packages/ui: zajedničke komponente design systema#

UI paket bi trebao biti eksplicitan oko React verzije i exportova. Držite ga jednostavnim i izbjegavajte bundlanje prerano, osim ako je potrebno.

Kreirajte packages/ui/package.json:

JSON
{
  "name": "@acme/ui",
  "version": "0.0.0",
  "private": true,
  "sideEffects": false,
  "main": "src/index.ts",
  "types": "src/index.ts",
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "scripts": {
    "lint": "eslint .",
    "typecheck": "tsc -p tsconfig.json --noEmit",
    "test": "vitest run",
    "clean": "rm -rf dist"
  }
}

Kreirajte packages/ui/src/index.ts:

TypeScript
export { Button } from "./components/Button";

Kreirajte packages/ui/src/components/Button.tsx:

TypeScript
import type { ButtonHTMLAttributes } from "react";
 
type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: "primary" | "secondary";
};
 
export function Button({ variant = "primary", ...props }: Props) {
  const className =
    variant === "primary" ? "btn btn-primary" : "btn btn-secondary";
  return <button className={className} {...props} />;
}

Koristite zajedničke pakete u aplikacijama#

U apps/web/package.json dodajte workspace dependencies:

JSON
{
  "dependencies": {
    "@acme/shared": "workspace:*",
    "@acme/ui": "workspace:*"
  }
}

Zatim ih koristite u kodu aplikacije, npr. na stranici:

TypeScript
import { Button } from "@acme/ui";
import { formatUserLabel } from "@acme/shared";
 
export default function Page() {
  const label = formatUserLabel({ id: "u1", email: "ana@company.com" });
  return (
    <main>
      <p>{label}</p>
      <Button>Continue</Button>
    </main>
  );
}

# Korak 4: Centralizirajte TypeScript, ESLint i formatiranje#

Centralna konfiguracija je ono zbog čega se monorepo isplati. Bez toga završite s više suprotstavljenih standarda unutar istog repozitorija.

TypeScript base config#

Kreirajte packages/config/tsconfig.base.json:

JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Zatim u svakoj aplikaciji i paketu kreirajte tsconfig.json koji ga extend-a. Primjer apps/web/tsconfig.json:

JSON
{
  "extends": "../../packages/config/tsconfig.base.json",
  "compilerOptions": {
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

ESLint config paket#

Kreirajte packages/config/eslint-nextjs.cjs:

JavaScript
module.exports = {
  root: false,
  extends: ["next/core-web-vitals"],
  rules: {
    "no-console": ["warn", { allow: ["warn", "error"] }]
  }
};

U apps/web/.eslintrc.cjs:

JavaScript
module.exports = {
  extends: ["../../packages/config/eslint-nextjs.cjs"]
};

⚠️ Upozorenje: Nemojte postaviti root: true u više ugniježđenih ESLint konfiguracija. Dobit ćete zbunjujuću rezoluciju pravila i nedosljedne lint rezultate između lokalnog okruženja i CI-a.

Formatiranje#

Prettier u monorepu je tipično na root razini kako bi bilo jednostavno. Držite konfiguraciju minimalnom i stabilnom kako ne biste nepotrebno “vrtjeli” diffove.

# Korak 5: Path aliasi koji rade kroz aplikacije i pakete#

Path aliasi su jedna od najčešćih bolnih točaka monorepa, posebno kada svaka aplikacija drugačije definira @/*.

Preporučeni pristup#

  • Za zajednički kod koristite package importe: @acme/ui, @acme/shared.
  • App-lokalne aliase držite jednostavnima: @/components unutar svake aplikacije zasebno.

U apps/web/tsconfig.json:

JSON
{
  "extends": "../../packages/config/tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    }
  }
}

Izbjegavajte kreiranje globalnog aliasa koji pokazuje u packages/* jer zamagljuje granice i može razbiti bundlere. Package importi su “čist” ugovor.

Next.js transpile za workspace pakete#

Ako vaš UI paket isporučuje TypeScript source, Next.js ga možda treba transpileati. U apps/web/next.config.js:

JavaScript
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ["@acme/ui", "@acme/shared"]
};
 
module.exports = nextConfig;

Ovo je često potrebno kako biste izbjegli “Unexpected token” greške ili ESM/CJS mismatch.

# Korak 6: Postavljanje testiranja za aplikacije i pakete#

Realističan monorepo odvaja vrste testova:

SlojToolingCilj
Unit testoviVitestBrz feedback za čistu logiku i komponente
Integration testoviVitest plus MSWPovjerenje u API ugovor bez pozivanja stvarnih servisa
E2E testoviPlaywrightCijeli user flowovi po aplikaciji

Za mnoge timove najbolji ROI je unit + mali set E2E smoke testova.

Dodajte vitest relevantnim paketima i aplikacijama, zatim izložite dosljednu test skriptu kako bi Turbo to mogao pokretati.

Primjer vitest.config.ts po paketu može biti minimalan, ali držite konfiguraciju dosljednom da izbjegnete fragmentaciju.

# Korak 7: Produkcijski spremna strategija za environment varijable#

Environment varijable postaju neuredne u monorepou jer više aplikacija može dijeliti iste ključeve, ali s različitim vrijednostima.

Pravila koja vas čuvaju#

  • Koristite .env.local po aplikaciji za developer overrideove.
  • Držite .env.example po aplikaciji kako biste dokumentirali potrebne ključeve.
  • Nikad se ne oslanjajte na root .env za konfiguriranje više aplikacija osim ako imate strogu konvenciju imenovanja.
ScenarijPreporuka
Zajednički ključ kroz aplikacijeKoristite isti naziv, ali vrijednosti spremite po app okruženju na hosting platformi
Ključ specifičan za aplikacijuPrefiksirajte s nazivom aplikacije, npr. WEB_SOME_KEY, ADMIN_SOME_KEY
Public ključevi u Next.jsKoristite NEXT_PUBLIC_ prefiks, provjerite da su sigurni za izlaganje

Utjecaj na CI i caching#

Ako uključite environment varijable u build korake, caching postaje osjetljiv. U Turbo-u možete deklarirati task inpute kao environment varijable u turbo.json kako biste spriječili pogrešan cache reuse.

Budite eksplicitni. Pogrešan cache hit može proizvesti build artefakt spojen na pogrešan API URL.

ℹ️ Napomena: Tretirajte environment varijable kao build inpute. Ako se vrijednost promijeni, želite cache miss, a ne ponovno korišten artefakt.

# Korak 8: CI razmatranja: caching, filteri i affected buildovi#

Turborepo najviše vrijedi u CI-u. Bez remote cachinga i dalje dobivate lokalna ubrzanja za developere, ali CI ostaje skup.

Buildajte samo ono što se promijenilo#

Koristite filtere da pokrećete taskove samo za pogođene (affected) aplikacije:

  • turbo run build --filter=web
  • turbo run test --filter=... za ciljane provjere

U CI-u možete mapirati promijenjene putanje na filtere. Tako monorepo ne postaje “uvijek buildaj sve”.

Uključite remote caching#

Remote caching je razlika između “lijepo” i “game-changing”. Timovi često vide 30 do 70 posto smanjenja CI vremena kada caching postane stabilan, posebno kada build vrijeme dominiraju install ovisnosti + Next.js kompilacija.

Minimalno cacheirajte:

  • pnpm store
  • Turbo cache direktorij
  • Next.js build outpute gdje ima smisla

Observability u CI-u i produkciji#

Monorepoi olakšavaju standardizaciju logiranja i monitoringa kroz aplikacije. Koristite jedan zajednički paket za logging utilse i jednu konvenciju za correlation ID-eve.

Ako želite praktičan pristup traceovima i metrikama, koristite naš vodič: Vodič za observability web aplikacija: logging, metrics, tracing.

# Trade-offovi: Što dobivate, a što plaćate#

Monorepo je skup trade-offova, ne besplatna nadogradnja.

AspektKoristCijena
Zajednički kodJedan izvor istine, manje dupliciranih popravakaJača međusobna vezanost aplikacija
ToolingUjedinjeni lint, TS i test strategijaViše početnog setupa, više “platform” posla
CICaching i affected buildovi mogu biti jako brziLoše konfiguriran caching uzrokuje zbunjujuće padove
OvisnostiJedno mjesto za nadogradnju verzijaKonflikti ovisnosti utječu na više projekata
Developer experienceLakše promjene kroz aplikacijeRepo djeluje “težak” bez dobrih skripti

Ako ne uložite u granice, monorepo postaje odlagalište. Ako uložite, postaje multiplikator.

# Uobičajene zamke i kako ih izbjeći#

Zamka 1: Neispravni workspace importi u Next.js buildovima#

Simptomi uključuju module parse greške ili upozorenja o dupliciranom Reactu.

Checklist za popravak:

  1. 1
    Dodajte transpilePackages za workspace pakete.
  2. 2
    Koristite peerDependencies za React u UI paketima.
  3. 3
    Osigurajte da je u workspace lockfileu instalirana samo jedna React verzija.

Zamka 2: Zbrka s path aliasima između aplikacija#

Ako više aplikacija različito definira @/, developeri kopiraju kod i importi pucaju.

Izbjegnite tako da:

  • Držite @/ strogo app-lokalnim.
  • Zajednički kod importajte samo kroz pakete poput @acme/shared.

Zamka 3: Curenje environment varijabli između aplikacija#

Root .env može slučajno konfigurirati sve aplikacije istim vrijednostima.

Izbjegnite tako da:

  • Koristite per-app .env.example i .env.local.
  • Dokumentirate potrebne ključeve u README-ju svake aplikacije.
  • Tretirate env varijable kao build inpute kako biste spriječili nevažeće cache hitove.

Zamka 4: Cache missovi zbog nedeterminističkih koraka#

Česti krivci:

  • Generiranje datoteka s timestampovima tijekom builda.
  • Pisanje u foldere koji nisu navedeni u Turbo outputs.
  • Network pozivi tijekom build koraka.

Učinite build taskove determinističkima i deklarirajte sve outpute.

💡 Savjet: Dodajte “repro build” provjeru u CI: pokrenite turbo run build dvaput i provjerite je li drugi run uglavnom cache hit. Ako nije, build nije determinističan ili su outputi nepotpuni.

Zamka 5: Nedosljednosti lintanja i typechecka kroz pakete#

Ako paketi pokreću različite lintere ili TS postavke, monorepo gubi glavnu prednost.

Riješite centralizacijom TS i ESLint konfiguracija te njihovim provođenjem kroz Turbo skripte.

# Ključne poruke#

  • Koristite monorepo kada imate više Next.js aplikacija koje dijele UI, utilse i konfiguraciju; u suprotnom ostanite na single-repo pristupu.
  • Preferirajte workspace pakete poput @acme/ui umjesto cross-repo path aliasa radi čišćih granica i manje bundler problema.
  • Pažljivo konfigurirajte turbo.json outpute i ovisnosti taskova kako biste dobili pouzdane cache hitove u CI-u.
  • Tretirajte environment varijable kao build inpute i držite ih scoped po aplikaciji kako biste izbjegli pogrešne buildove i cache poisoning.
  • Koristite transpilePackages i React peerDependencies kako biste spriječili probleme s Next.js workspace importima i dupliciranim Reactom.
  • Rano uložite u centralizirane lint i TypeScript konfiguracije kako biste spriječili tooling drift dok repo raste.

# Zaključak#

Next.js monorepo s Turborepo i pnpm workspaces može biti veliko povećanje produktivnosti kada imate više aplikacija i zajedničke pakete — ali samo ako dizajnirate jasne granice i tretirate caching, env varijable i tooling kao first-class brige.

Ako želite da vam Samioda pomogne postaviti produkcijski monorepo, migrirati više Next.js aplikacija u zajedničku platformu ili standardizirati CI s pouzdanim cachingom, kontaktirajte nas i predložit ćemo konkretnu strukturu repozitorija i plan uvođenja temeljen na vašim aplikacijama i deployment ciljevima.

FAQ

Share
A
Adrijan OmićevićOsnivač i senior developer

Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.

Više iz kategorije Web razvoj

Sve

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.