# Š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#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Node.js | 20+ | Koristite LTS verziju za predvidljivo ponašanje u CI-u |
| pnpm | 9+ | Podrška za workspace i učinkovita potrošnja diska |
| GitHub Actions ili sličan CI | — | Primjeri koriste GitHub Actions koncepte |
| Next.js | 14 ili 15 | Radi s obje verzije, primjeri pretpostavljaju App Router obrasce |
| TypeScript | 5+ | Snažno preporučeno za zajedničke pakete |
# Struktura repozitorija (Apps + Packages)#
Praktična Turborepo struktura odvaja deployable aplikacije od zajedničkih paketa:
| Putanja | Tip | Što ide ovdje |
|---|---|---|
apps/web | Next.js app | Glavna web aplikacija okrenuta korisnicima |
apps/admin | Next.js app | Interni admin dashboard |
packages/ui | Shared package | Komponente design systema |
packages/config | Shared package | ESLint, TS config, Prettier config |
packages/shared | Shared package | Utils, tipovi, API klijenti |
tooling | Optional | Skripte, 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/configizbjegava 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.
mkdir nextjs-monorepo
cd nextjs-monorepo
pnpm init
pnpm add -D turboKreirajte pnpm-workspace.yaml u rootu repozitorija:
packages:
- "apps/*"
- "packages/*"Ažurirajte package.json skripte u rootu kako biste upravljali svim taskovima:
{
"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:
{
"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:
mkdir -p apps/web apps/admin packages/ui packages/shared packages/configUnutar svake aplikacije želite dosljedne skripte. Primjer apps/web/package.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:
{
"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:
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:
{
"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:
export { Button } from "./components/Button";Kreirajte packages/ui/src/components/Button.tsx:
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:
{
"dependencies": {
"@acme/shared": "workspace:*",
"@acme/ui": "workspace:*"
}
}Zatim ih koristite u kodu aplikacije, npr. na stranici:
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:
{
"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:
{
"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:
module.exports = {
root: false,
extends: ["next/core-web-vitals"],
rules: {
"no-console": ["warn", { allow: ["warn", "error"] }]
}
};U apps/web/.eslintrc.cjs:
module.exports = {
extends: ["../../packages/config/eslint-nextjs.cjs"]
};⚠️ Upozorenje: Nemojte postaviti
root: trueu 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:
@/componentsunutar svake aplikacije zasebno.
U apps/web/tsconfig.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:
/** @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:
| Sloj | Tooling | Cilj |
|---|---|---|
| Unit testovi | Vitest | Brz feedback za čistu logiku i komponente |
| Integration testovi | Vitest plus MSW | Povjerenje u API ugovor bez pozivanja stvarnih servisa |
| E2E testovi | Playwright | Cijeli 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.localpo aplikaciji za developer overrideove. - Držite
.env.examplepo aplikaciji kako biste dokumentirali potrebne ključeve. - Nikad se ne oslanjajte na root
.envza konfiguriranje više aplikacija osim ako imate strogu konvenciju imenovanja.
| Scenarij | Preporuka |
|---|---|
| Zajednički ključ kroz aplikacije | Koristite isti naziv, ali vrijednosti spremite po app okruženju na hosting platformi |
| Ključ specifičan za aplikaciju | Prefiksirajte s nazivom aplikacije, npr. WEB_SOME_KEY, ADMIN_SOME_KEY |
| Public ključevi u Next.js | Koristite 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=webturbo 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.
| Aspekt | Korist | Cijena |
|---|---|---|
| Zajednički kod | Jedan izvor istine, manje dupliciranih popravaka | Jača međusobna vezanost aplikacija |
| Tooling | Ujedinjeni lint, TS i test strategija | Više početnog setupa, više “platform” posla |
| CI | Caching i affected buildovi mogu biti jako brzi | Loše konfiguriran caching uzrokuje zbunjujuće padove |
| Ovisnosti | Jedno mjesto za nadogradnju verzija | Konflikti ovisnosti utječu na više projekata |
| Developer experience | Lakše promjene kroz aplikacije | Repo 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:
- 1Dodajte
transpilePackagesza workspace pakete. - 2Koristite
peerDependenciesza React u UI paketima. - 3Osigurajte 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.examplei.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 builddvaput 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/uiumjesto cross-repo path aliasa radi čišćih granica i manje bundler problema. - Pažljivo konfigurirajte
turbo.jsonoutpute 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
transpilePackagesi ReactpeerDependencieskako 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
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 →Kontrolni popis pristupačnosti za React: ARIA, navigacija tipkovnicom, upravljanje fokusom i testiranje (2026.)
Kontrolni popis pristupačnosti za React usmjeren na developere, koji pokriva ARIA, navigaciju tipkovnicom, upravljanje fokusom i automatizirano testiranje s axe i Playwrightom — uz konkretne primjere za forme, modale, izbornike i toast obavijesti.
Virtualizacija React tablica i beskonačno skrolanje: izrada brzih data gridova s TanStackom (vodič za 2026.)
Naučite virtualizaciju tablica u Reactu uz TanStack Table, TanStack Virtual i React Query: učinkovito renderiranje, beskonačno skrolanje, sortiranje/filtriranje na serveru, sinkronizacija s URL-om, postojanost selekcije i optimistična ažuriranja.
Next.js real-time značajke: WebSockets vs SSE vs Supabase Realtime (kada što koristiti)
Praktična usporedba Next.js opcija za real-time—WebSockets, Server-Sent Events i Supabase Realtime—s fokusom na ograničenja hostinga, skalabilnost, autentikaciju, trošak i što koristiti za chat, nadzorne ploče, obavijesti i suradničko uređivanje.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Izgradnja React dizajn sustava s dizajnerskim tokenima: Tailwind CSS + Radix UI + TypeScript
Praktičan vodič za 2026. o izgradnji React dizajn sustava s Tailwindom i Radixom uz dizajnerske tokene, pristupačne primitive, tematiziranje i ponovno iskoristive pakete kroz više aplikacija.
React obrasci u velikim aplikacijama: React Hook Form + Zod obrasci za složene proizvode
Najbolje prakse za React obrasce u velikim aplikacijama uz React Hook Form i Zod: validacija po shemi, višekratno upotrebljiva polja, asinkrone provjere, višekoračni tokovi, performanse, pristupačnost i obrasci integracije sa serverom/API-jem.
Arhitektura React komponenti za skaliranje: obrasci za održiv dizajnerski sustav
Pragmatična arhitektura React komponenti za velike React i Next.js codebaseove: kompozicija, složene (compound) i polimorfne komponente, tematiziranje, konvencije mapa, anti-uzorci i plan refaktoriranja koji vaš tim može pratiti.