# Što ćete izgraditi u ovom vodiču#
Skalabilna arhitektura React komponenti manje je stvar savršenog stabla mapa, a više stvar ponovljivih odluka koje cijeli tim može primjenjivati pod pritiskom vremena.
Ovaj vodič iznosi pragmatičnu arhitekturu za velike React i Next.js codebaseove, s fokusom na kompoziciju, složene (compound) komponente, polimorfne komponente, tematiziranje i konvencije mapa. Dobit ćete i anti-uzorke koje treba izbjegavati te plan refaktoriranja koji možete provoditi sprint po sprint.
Na kraju ćete imati strukturu koja podržava održiv dizajnerski sustav, bez pretvaranja UI sloja u kruti framework.
# Zašto se arhitektura React komponenti raspada na velikom opsegu#
Većina timova počne s dobrim namjerama i završi s UI entropijom: više implementacija gumba, nekonzistentni razmaci i komponente koje rade samo na jednom ekranu.
Trošak se brzo vidi:
- Sporija isporuka: inženjeri ponovno implementiraju UI umjesto da ga ponovno koriste ili troše vrijeme na dešifriranje “koji je Button onaj pravi”.
- Umnožavanje bugova: popravak problema pristupačnosti u tri verzije iste komponente su tri prilike da jednu propustite.
- Regresije performansi: previše konfigurabilne komponente i kaskade ponovnih rendera potaknute propsima česte su u velikim UI sustavima.
ℹ️ Napomena: Dizajnerski sustavi najčešće propadaju zbog governancea i arhitekture, a ne zbog boja i tipografije. Ako se vaše komponente teško komponiraju, timovi će ih zaobilaziti.
Ako gradite na Next.js, arhitektonske odluke utječu i na server rendering te granice dohvaćanja podataka. Kad miješate interaktivnost i layout bez jasnih granica, veća je vjerojatnost da ćete stvoriti nepotrebne client bundleove i dodatni hydration posao. Za dublji mentalni model pogledajte naš vodič o React Server Components u Next.js.
# Temeljna načela: pravila koja sustav čine održivim#
Uzorci u nastavku mogu se implementirati na više načina, ali sustav drži samo ako je nekoliko pravila dosljedno.
1) Dajte prednost kompoziciji u odnosu na konfiguraciju#
Ako komponenta ima više od 10 propsa i pola njih se rijetko koristi, to je obično znak da pokušavate jednom komponentom pokriti previše layouta.
Kompozicija čuva API-e malima, potiče ponovno korištenje i sprječava eksploziju varijanti.
2) “Komponente dizajnerskog sustava” neka po defaultu budu prezentacijske#
Interaktivne komponente teže je održati stabilnima jer se tokovi stanja razlikuju od featurea do featurea.
Krenite od prezentacijskih komponenti i izložite hookove ili manje interaktivne wrapere kad je potrebno.
3) Učinite “sretni put” najlakšim putem#
Ako korištenje dizajnerskog sustava zahtijeva dodatne wrapere, nezgodne propse ili čitanje internih dokumenata, inženjeri će copy-pasteati.
Ergonomija je governance.
4) Eksplicitno definirajte granice#
Trebate granice za:
- stiliziranje i tematiziranje
- klijentske naspram serverskih komponenti u Next.js
- domenski UI naspram UI-a dizajnerskog sustava
Granica je mapa, pravilo izvoza i pravilo ovisnosti.
# Skalabilna konvencija mapa i izvoza#
Česta greška je jedna components/ mapa sa stotinama datoteka. Postane ladica za svašta.
Evo strukture koja se skalira, a ostaje praktična u Next.js:
| Područje | Mapa | Što ide ovdje | Pravilo ovisnosti |
|---|---|---|---|
| Primitive dizajnerskog sustava | src/ui/primitives/ | Button, Input, Text, Stack, Icon | Bez app importova |
| Složene komponente dizajnerskog sustava | src/ui/components/ | Modal, Dropdown, DatePicker, DataTable | Može koristiti primitive |
| UI featurea | src/features/*/components/ | Komponente specifične za ekran | Može koristiti src/ui/* |
| Logika featurea | src/features/*/hooks/ | Hookovi za stanje featurea, podatke | Nisu potrebni importovi iz src/ui |
| App rute | src/app/ | Next.js rute i layouti | Koristi features i ui |
| Dijeljeni utilityji | src/lib/ | fetcheri, formateri, logging | Idealno bez React importova |
Strategija izvoza jednako je važna kao i mape.
src/ui/index.tsizvozi stabilne javne UI API-e.- Izbjegavajte duboke importe poput
src/ui/primitives/button/Button.tsxjer stvaraju tijesnu vezu i čine refaktore bolnima. - Interna pomagala držite neizvezenima ili u
internal/mapama.
💡 Savjet: Granice provedite ESLint pravilima. Jednostavno pravilo “bez cross-feature importova” sprječava da
features/billingimportirafeatures/authkomponente i tiho stvara cikluse.
# Obrazci kompozicije koji se skaliraju#
Obrazac 1: Slotovi umjesto boolean propsa#
Umjesto hasIcon, showSubtitle, withBadge, koristite slotove.
Loše dizajnirani API-i često vode do uvjetnog špageta koda unutar komponente i desetaka “gotovo istih” varijanti.
```typescript
type CardProps = \\{
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
\\};
export function Card(props: CardProps) \\{
return (
<div className="rounded-xl border p-4">
\\{props.header ? <div className="mb-3">\\{props.header\\}</div> : null\\}
<div>\\{props.children\\}</div>
\\{props.footer ? <div className="mt-3">\\{props.footer\\}</div> : null\\}
</div>
);
\\}
Ovaj se API skalira jer se nove potrebe implementiraju kao komponirani UI, a ne kao dodatni propsi.
### Obrazac 2: “Layout primitive” za smanjenje ad-hoc CSS-a
Ako svaki ekran definira vlastite razmake, vaš UI će se s vremenom razići. Layout primitive čine razmake konzistentnima i ubrzavaju razvoj.
Uobičajene primitive uključuju Stack, Inline, Grid, Container. Trebale bi biti dosadne i predvidljive.
Ako koristite utility CSS, držite ga discipliniranim. Ovo se dobro slaže s [najboljim praksama za Tailwind CSS](https://samioda.com/en/blog/tailwind-css-best-practices), posebno oko konzistentnih skala razmaka i izbjegavanja proizvoljnih vrijednosti.
### Obrazac 3: Kontrolirane i nekontrolirane komponente s jednim mentalnim modelom
Za inpute, dropdownove, tabove i modale podržite oba obrasca:
- kontrolirano: parent posjeduje state
- nekontrolirano: komponenta posjeduje state, parent sluša
Učinite API eksplicitnim s `value` i `defaultValue`, `open` i `defaultOpen`.
| Komponenta | Kontrolirani propsi | Nekontrolirani propsi | Obavezni callbackovi |
| --- | --- | --- | --- |
| Tabs | `value` | `defaultValue` | `onValueChange` |
| Modal | `open` | `defaultOpen` | `onOpenChange` |
| Input | `value` | `defaultValue` | `onChange` |
Ovo smanjuje potrebu za custom wrapperima i čini komponente upotrebljivima i na jednostavnim i na složenim ekranima.
## Složene (compound) komponente: pragmatičan pristup
Složene komponente daju čist API za kompleksne UI strukture bez guranja goleme površine propsa.
Ideja je: jedan parent koordinira zajedničko stanje i context, a child komponente ga koriste.
### Primjer: Tabs kao složene (compound) komponente
```tsx
```typescript
import * as React from "react";
type TabsContextValue = \\{
value: string;
setValue: (v: string) => void;
\\};
const TabsContext = React.createContext<TabsContextValue | null>(null);
export function Tabs(props: \\{ defaultValue: string; children: React.ReactNode \\}) \\{
const [value, setValue] = React.useState(props.defaultValue);
return <TabsContext.Provider value=\\{\\{ value, setValue \\}\\}>\\{props.children\\}</TabsContext.Provider>;
\\}
export function TabsList(props: \\{ children: React.ReactNode \\}) \\{
return <div className="flex gap-2">\\{props.children\\}</div>;
\\}
export function Tab(props: \\{ value: string; children: React.ReactNode \\}) \\{
const ctx = React.useContext(TabsContext);
if (!ctx) throw new Error("Tab must be used within Tabs");
const active = ctx.value === props.value;
return (
<button
type="button"
className=\\{active ? "font-semibold" : "opacity-70"\\}
onClick=\\{() => ctx.setValue(props.value)\\}
>
\\{props.children\\}
</button>
);
\\}
export function TabsPanel(props: \\{ value: string; children: React.ReactNode \\}) \\{
const ctx = React.useContext(TabsContext);
if (!ctx) throw new Error("TabsPanel must be used within Tabs");
if (ctx.value !== props.value) return null;
return <div className="pt-4">\\{props.children\\}</div>;
\\}
Korištenje ostaje čitljivo i prilagodljivo:
- dodajte ikone u sadržaj `Tab` bez novih propsa
- dodajte badgeve, keyboard handling ili analitiku bez mijenjanja svakog mjesta korištenja
### Kada se složene (compound) komponente isplate
Koristite ih kada imate:
- zajedničko stanje preko više poddijelova
- potrebu za fleksibilnim layoutom i prilagođenim sadržajem
- ponavljajuće obrasce “komponenta unutar komponente”
Nemojte ih koristiti kada je dovoljno jednostavno prop-driven rješenje. Pretjerano korištenje contexta dodaje indirekciju i otežava debugiranje.
> **⚠️ Upozorenje:** Česta zamka je izvoz internog contexta ili oslanjanje na context u leaf komponentama daleko od parenta. Držite compound grupacije tijesnima i ko-lociranima kako biste izbjegli implicitne ovisnosti.
## Polimorfne komponente bez kaosa u API-u
Polimorfne komponente omogućuju potrošačima da odaberu koji se element renderira, tipično preko `as` propa. To smanjuje duplikacije poput ButtonLink, ButtonAnchor, ButtonRouterLink.
Dobar polimorfni pristup mora zadržati type safety i ne smije “curiti” implementacijske detalje.
### Primjer: tipizirani polimorfni Button
```tsx
```typescript
import * as React from "react";
type PropsOf<E extends React.ElementType> = React.ComponentPropsWithoutRef<E>;
type ButtonProps<E extends React.ElementType> = \\{
as?: E;
variant?: "solid" | "outline";
\\} & Omit<PropsOf<E>, "as" | "color">;
export function Button<E extends React.ElementType = "button">(
props: ButtonProps<E>
) \\{
const \\{ as, variant = "solid", className, ...rest \\} = props;
const Comp = as ?? "button";
const base = "inline-flex items-center justify-center rounded-md px-3 py-2";
const styles = variant === "solid" ? "bg-black text-white" : "border";
return <Comp className=\\{[base, styles, className].filter(Boolean).join(" ")\\} \\{...rest\\} />;
\\}
Primjeri korištenja:
- `<Button onClick={...}>Save</Button>`
- `<Button as="a" href="/pricing">Pricing</Button>`
### Ograde (guardrails) za polimorfizam
Polimorfizam je moćan, ali se lako zloupotrebljava. Držite ga uskim:
- Koristite ga za primitive poput Button, Text, Box.
- Izbjegavajte `as` na kompleksnim komponentama poput DataTable ili Modal.
- U dokumentaciji i testovima osigurajte obavezne accessibility propse po elementu.
## Theming: prvo tokeni, tek onda stilovi
Theming je često razlika između “biblioteke komponenti” i pravog dizajnerskog sustava.
Skalabilan pristup:
1) definirajte design tokene
2) mapirajte tokene na CSS varijable
3) komponente neka koriste tokene, ne sirove boje
### Model tokena koji se skalira
| Vrsta tokena | Primjer tokena | Zašto je važno |
| --- | --- | --- |
| Semantičke boje | `--color-bg-surface` | Omogućuje light i dark temu bez prepisivanja komponenti |
| Tipografija | `--font-size-sm` | Održava tekst konzistentnim kroz ekrane |
| Razmaci | `--space-4` | Sprječava drift proizvoljnih razmaka |
| Radijus | `--radius-md` | Usklađuje zaobljenja kroz sustav |
| Sjena | `--shadow-sm` | Konzistentno kontrolira elevaciju |
### Minimalna postavka CSS varijabli
```css
```css
:root \\{
--color-bg-surface: #ffffff;
--color-fg: #111111;
--space-4: 16px;
--radius-md: 10px;
\\}
[data-theme="dark"] \\{
--color-bg-surface: #0b0b0b;
--color-fg: #f5f5f5;
\\}
Zatim komponente referenciraju tokene:
- background koristi `--color-bg-surface`
- tekst koristi `--color-fg`
- padding koristi `--space-4`
Ovo radi i s Tailwindom, ali disciplina se mora provoditi. Ako vaš UI koristi jednokratne hex vrijednosti ili proizvoljne razmake, theming će uvijek biti djelomičan.
Radi performansi, promjene teme držite jeftinima korištenjem CSS varijabli umjesto rerenderiranja stabala komponenti. Ovo se također uklapa u šire prakse [optimizacije performansi web stranice](https://samioda.com/en/blog/website-performance-optimization), jer smanjuje layout thrash i JS-driven ažuriranja stilova.
## Anti-uzorci koji ubijaju održivost
Anti-uzorci su vrijedni jer pokazuju što treba odmah prestati raditi.
### Anti-uzorak 1: “God komponente” s desecima propsa
Simptomi:
- više boolean vrijednosti koje međusobno čudno djeluju
- `variant` prop s 10 opcija
- mnogo propsa koji se izravno ulijevaju u logiku class nameova
Smjer refaktoriranja:
- razdvojite na primitive i složene wrapere
- layout odluke premjestite u kompozicijske slotove
### Anti-uzorak 2: Stiliziranje copy-pasteanjem stringova klasa
Ako se isti popis klasa pojavljuje u 10 komponenti, vaš dizajnerski sustav već postoji, ali je implicitan i bez governancea.
Smjer refaktoriranja:
- izdvojite primitive
- uvedite tokene za razmake i boje
- standardizirajte jedan izvor istine za zajedničke stilove
### Anti-uzorak 3: Miješanje dohvaćanja podataka i renderiranja UI-a unutar reusable komponenti
Reusable komponenta koja zove `fetch` ili čita iz feature storea postaje tijesno vezana uz jednu domenu.
Smjer refaktoriranja:
- komponente dizajnerskog sustava prihvaćaju podatke preko propsa
- feature sloj posjeduje dohvaćanje podataka
- u Next.js App Routeru, gurajte dohvaćanje podataka prema server komponentama i prosljeđujte propsove prema dolje
### Anti-uzorak 4: “Wrapper komponente” koje samo preimenuju propsove
Wrapper koji samo mapira `primary` na `variant="solid"` dodaje indirekciju i umnaža datoteke.
Smjer refaktoriranja:
- poboljšajte API bazne komponente
- kreirajte složene komponente samo kad dodaju ponašanje ili strukturu, ne preimenovanje
> **🎯 Ključna poruka:** Ako ne možete u jednoj rečenici objasniti zašto komponenta postoji, vjerojatno ne pripada dizajnerskom sustavu.
## Plan timskog refaktoriranja koji možete provoditi kroz sprintove
Većina timova ne može pauzirati razvoj featurea radi potpunog rewrita. Cilj je postupno poboljšavati arhitekturu dok isporučujete.
### Korak 1: Napravite inventuru i izmjerite duplikacije
Krenite s činjenicama, ne mišljenjima:
- popišite nazive komponenti koje postoje više puta: Button, Modal, Card, Input
- prebrojite koliko call siteova ima svaka varijanta
- identificirajte UI bugove koji se ponavljaju kroz ekrane, posebno pristupačnost i razmake
Jednostavna heuristika: ako imate 3+ implementacije istog UI obrasca, konsolidirajte.
### Korak 2: Definirajte “javni UI” i zamrznite ga
Odaberite jednu izvoznu površinu za korištenje UI-a, npr. `src/ui/index.ts`, i obvežite se da će se u novom kodu koristiti isključivo to.
Zatim interna mjesta tretirajte kao refaktorabilna.
### Korak 3: Prvo primitive, zatim kompozicija
Izgradite ili konsolidirajte:
- Button
- Text
- Input
- Stack i Inline
- Icon
Tek nakon što su primitive stabilne, gradite složene komponente poput Dropdown, Modal, Toast.
Tako izbjegavate gradnju kompleksnih komponenti na nestabilnim temeljima.
### Korak 4: Migrirajte inkrementalno uz codemodove i lint pravila
Možete migrirati ekran po ekran. Progres osigurajte tako da spriječite nova korištenja legacy komponenti.
Praktične ideje za provedbu:
- eslint pravilo: zabrani importe iz `src/components/legacy`
- eslint pravilo: prisili `src/ui` import putanju
- CI provjera: fail ako se dodaju nove datoteke u `legacy/`
### Korak 5: Uvedite theming bez prepisivanja svega
Dodajte CSS varijable i postupno mapirajte stare boje:
- krenite s tokenima za background i tekst
- zatim rubovi, sjene i focus ringovi
- na kraju semantički tokeni poput success, warning, danger
Pristup “sve odjednom” je ono što ubija momentum.
### Korak 6: Dodajte dokumentaciju koja odgovara na jedno pitanje po komponenti
Dokumentacija treba biti kratka i praktična:
- koji problem rješava
- tipično korištenje
- zahtjevi za pristupačnost
- koji propsi su stabilni, a koji napredni
Ako još nemate docs site, krenite s `README.md` po mapi komponente. Bolje je od plemenskog znanja.
### Korak 7: Dodajte testove tamo gdje su kvarovi skupi
Ne treba vam 100 posto pokrivenosti. Fokusirajte se na komponente koje mogu slomiti mnogo ekrana:
- Button: disabled stanje, focus stilovi
- Modal: ponašanje tipkovnice i focus trap
- Form komponente: labele, greške, aria atributi
UI testove držite malima i stabilnima. Snapshot testiranje velikih stabala obično je šumovito.
## Next.js-specifične arhitektonske napomene za dizajnerske sustave
### Većinu UI-a držite kompatibilnom sa serverom
U Next.js App Routeru komponenta postaje client komponenta ako koristi hookove ili event handlere. Ako dizajnerski sustav učini sve client-only, isporučit ćete više JS-a nego što je potrebno.
Praktično pravilo:
- primitive poput Text, Box, Card trebaju biti server-kompatibilne
- interaktivne komponente poput Dropdown, Dialog, Tabs mogu biti client komponente
- izolirajte interaktivnost u leaf komponentama
Ovo mjerljivo utječe na veličinu bundlea i cijenu hydracije, posebno na stranicama bogatim sadržajem.
### Izbjegavajte implicitne client granice
Ako je parent komponenta client-only, sva djeca postaju client-rendered. U dizajnerskim sustavima to može slučajno povući velika UI stabla u client bundle.
Interaktivne wrapere držite malima i dopustite da ih server-rendered layouti kompozicijski koriste.
## Ključne poruke
- Koristite kompoziciju i slotove kako bi API-i komponenti ostali mali i kako biste spriječili eksploziju varijanti.
- Primijenite složene (compound) komponente za kompleksne UI obrasce koji dijele stanje kroz poddijelove, i držite granicu contexta tijesnom.
- Koristite polimorfne komponente samo za primitive, uz stroge ograde kako biste izbjegli tipovni i accessibility kaos.
- Implementirajte theming pomoću tokena i CSS varijabli tako da se tema mijenja bez rerenderiranja stabala komponenti.
- Provodite granice mapa i javne izvoze lint pravilima kako biste zaustavili cross-feature coupling i širenje legacy koda.
- Refaktorirajte inkrementalno uz inventuru, konsolidaciju “prvo primitive”, migracijska pravila i ciljane testove.
## Zaključak
Skalabilna arhitektura React komponenti je skup ograničenja kojih se vaš tim dogovorno drži, a ne jednokratni refaktor. Krenite definiranjem granica, konsolidirajte primitive i standardizirajte obrasce kompozicije kako bi nove funkcionalnosti automatski bile usklađene s dizajnerskim sustavom.
Ako želite pomoć u reviziji vaše trenutne biblioteke komponenti, definiranju plana migracije ili implementaciji token-based teme u Next.js codebaseu, Samioda vam može pomoći da isporučite održiv dizajnerski sustav bez pauziranja isporuke. Javite se i predložit ćemo praktičan plan refaktoriranja prilagođen vašem repozitoriju i veličini tima.FAQ
Više iz kategorije Web razvoj
Sve →Next.js autentikacija u 2026.: NextAuth vs Clerk vs Supabase (što koristimo na klijentskim projektima)
Praktična usporedba opcija za Next.js autentikaciju u 2026. — NextAuth, Clerk i Supabase — kroz UX, sigurnost, trošak, vrijeme postavljanja i enterprise zahtjeve, uz matrice odluke za SaaS, interne alate i B2B portale.
Kontrolni popis za migraciju na Next.js App Router (s Pages Routera) + česte zamke
Praktičan, korak-po-korak plan migracije na Next.js App Router s Pages Routera, uključujući kontrolni popis za routing, dohvat podataka, SEO metadata, deployment i vodič za rješavanje čestih zamki.
Najbolje prakse za Tailwind CSS: izgradnja održivih korisničkih sučelja (production obrasci za 2026.)
Vodič usmjeren na produkciju o najboljim praksama za Tailwind CSS: obrasci komponenti, prilagođena konfiguracija, dark mode, responsive strategija i primjeri koje možete kopirati za održiva korisnička sučelja.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Kontrolni popis za migraciju na Next.js App Router (s Pages Routera) + česte zamke
Praktičan, korak-po-korak plan migracije na Next.js App Router s Pages Routera, uključujući kontrolni popis za routing, dohvat podataka, SEO metadata, deployment i vodič za rješavanje čestih zamki.
Najbolje prakse za Tailwind CSS: izgradnja održivih korisničkih sučelja (production obrasci za 2026.)
Vodič usmjeren na produkciju o najboljim praksama za Tailwind CSS: obrasci komponenti, prilagođena konfiguracija, dark mode, responsive strategija i primjeri koje možete kopirati za održiva korisnička sučelja.
React Server Components (RSC): Što su i kako ih koristiti u Next.js App Routeru
Praktičan vodič za 2026. o React Server Components: što su, zašto su važni i kako ispravno koristiti server vs client komponente u Next.js App Routeru uz primjere koje možete copy-pasteati.