# Što ćeš izgraditi u ovom vodiču#
Implementirat ćeš feature-based strategiju strukture mapa za React frontend arhitekturu koja skalira i nakon prvih 3 do 5 značajki, bez pretvaranja u labirint zajedničkih utila i kružnih importa.
Naučit ćeš gdje smjestiti API klijente, UI primitive, domensku logiku, testove i poprečne brige (cross-cutting concerns) u Reactu i Next.js-u. Dobit ćeš i pravila ovisnosti koja možeš provoditi s ESLintom, uz primjere koje možeš izravno kopirati u svoj projekt.
Ova struktura je namijenjena timovima gdje:
- Aplikacija će rasti izvan jednog developera.
- Više značajki se isporučuje paralelno.
- Želiš da refaktori budu lokalizirani umjesto “diraj 40 datoteka u 10 mapa”.
# Zašto feature-based arhitektura pobjeđuje na skali#
Struktura mapa postaje arhitektura onog trenutka kad imaš:
- više od 10 ekrana,
- više od 2 developera,
- više od 1 integracije,
- više od 1 izdanja tjedno.
Feature-based pristup smanjuje trošak koordinacije jer promjene koda najčešće ostaju unutar modula koji odgovara načinu na koji se o proizvodu govori: onboarding, naplata, pretraga, checkout, postavke računa.
Trend u industriji to potvrđuje. 2024 State of JS survey navodi da je React i dalje najkorištenija UI biblioteka, a najčešća bol koju React timovi prijavljuju je održavanje i rastuća kompleksnost kako aplikacije rastu. Feature-based granice ne uklanjaju kompleksnost, ali je zadržavaju pod kontrolom.
Ako ti je važna i konzistentnost UI primitiva te skalabilan dizajn komponenti, upari ovaj vodič s člankom React arhitektura komponenti za skalabilne design sisteme.
# Osnovna načela: granice, ovisnosti i javni API-ji#
Feature-based arhitektura radi samo ako su granice stvarne. To znači da definiraš:
- 1Što svaki modul posjeduje
- 2Što smije importati
- 3Što javno izlaže
Načelo 1: Moduli posjeduju poslovnu sposobnost, ne samo UI#
Feature modul bi trebao uključivati:
- UI na razini rute (pages, screens)
- stanje značajke i orkestraciju
- domenska pravila za tu značajku
- API pozive značajke
- komponente specifične za značajku
- testove značajke
Ako domensku logiku držiš u nasumičnim zajedničkim utilima, ponovno stvaraš monolit, samo s više mapa.
Načelo 2: Ovisnosti teku u jednom smjeru#
Tvoja struktura treba spriječiti “svaštaru” shared sloj o kojem svi ovise i koji svi mijenjaju.
Jednostavno pravilo koje dobro skalira:
- shared ne ovisi ni o čemu
- entities ovisi o shared
- features ovisi o entities i shared
- app slaže features i globalne providere
Ovo je blisko poznatim obrascima poput feature-sliced designa, ali pojednostavljeno kako bi ostalo pragmatično.
Načelo 3: Svaki modul exporta javni API#
Ako drugi moduli importaju duboke putanje, granice su imaginarne. Želiš importe poput:
import { CheckoutPage } from "@/features/checkout";
import { formatMoney } from "@/shared/lib/money";a ne:
import { formatMoney } from "@/shared/lib/money/formatMoney";
import { CheckoutForm } from "@/features/checkout/ui/forms/CheckoutForm";Duboki importi zaobilaze odluke i stvaraju “spaghetti coupling”.
🎯 Ključna poruka: Struktura mapa je arhitektura tek kad provodiš smjer ovisnosti i javne exporte.
# Preporučena struktura mapa za React i Next.js#
Ova struktura podržava:
- React SPA s React Routerom
- Next.js App Router s React Server Components
Koristi src kao korijen i drži Next.js app na rootu projekta.
Layout na visokoj razini#
| Sloj | Odgovornost | Smije importati | Ne smije importati |
|---|---|---|---|
| app | Kompozicija ruta, provideri, bootstrapping | features, entities, shared | interne dijelove feature-a |
| features | Korisnički vidljive sposobnosti, orkestracija | entities, shared | interne dijelove drugih feature-a |
| entities | Osnovni domenski modeli i ponovno upotrebljiv entity UI | shared | features, app |
| shared | UI primitive, libovi, config, bazni API klijent | ništa (ili samo npm ovisnosti) | app, features, entities |
| tests | Cross-feature test utili, fixtures | shared | interne dijelove feature-a (idealno) |
Primjer stabla mapa#
src/
app/
providers/
routes/
features/
auth/
checkout/
search/
entities/
user/
product/
order/
shared/
api/
ui/
lib/
config/
types/
tests/
fixtures/
helpers/Za Next.js App Router tipično ćeš imati:
app/
(marketing)/
(app)/
api/
src/
features/
entities/
shared/Next.js app direktorij je routing kojim upravlja framework. Stvarni product kod živi u feature modulima pod src.
ℹ️ Napomena: U Next.js-u izbjegavaj stavljati poslovnu logiku izravno u
apprute. Route datoteke tretiraj kao kompozicijsko “ljepilo”, a logiku drži usrc/featureskako bi ostala prenosiva i testabilna.
# Kako dizajnirati feature module#
Feature modul bi trebao djelovati kao mali paket sa svojom javnom površinom.
Predložak feature modula#
src/features/checkout/
index.ts
model/
useCheckout.ts
checkoutMachine.ts
selectors.ts
api/
checkoutApi.ts
types.ts
ui/
CheckoutPage.tsx
CheckoutForm.tsx
components/
PaymentMethodPicker.tsx
lib/
pricing.ts
validation.ts
tests/
checkout.test.ts
pricing.test.tsŠto ide gdje#
| Mapa | Stavi ovdje | Primjer |
|---|---|---|
| api | API pozivi specifični za feature i mapiranje DTO-a | createOrder, applyCoupon |
| model | stanje, hookovi, orkestratori | useCheckout, XState machineovi |
| ui | ekrani i komponente feature-a | CheckoutPage, CheckoutForm |
| lib | čiste funkcije koje se koriste unutar feature-a | validateAddress, calcTotals |
| tests | testovi feature-a blizu koda | pricing.test.ts |
| index.ts | samo javni exporti | export { CheckoutPage } |
Primjer javnog API-ja feature-a#
// src/features/checkout/index.ts
export { CheckoutPage } from "./ui/CheckoutPage";
export { useCheckout } from "./model/useCheckout";
export type { CheckoutDraft } from "./model/types";Exportaj samo ono o čemu drugi moduli trebaju ovisiti. Sve ostalo ostaje interno.
⚠️ Upozorenje: Ako feature-i importaju druge feature-e direktno, brzo ćeš dobiti cikluse poput
checkout -> auth -> cart -> checkout. Radije komuniciraj među feature-ima kroz shared apstrakcije ili podigni orkestraciju uapp.
# Gdje pripadaju API klijenti (bez stvaranja globalnog “God” modula)#
Većina timova krene sa src/api i završi s 200 datoteka i nejasnim vlasništvom. Umjesto toga, razdvoji “transport” od “endpointa”.
Zajednički API transport#
Drži minimalan HTTP klijent u shared/api. Trebao bi biti dosadan i stabilan.
src/shared/api/
httpClient.ts
errors.ts
authHeader.ts// src/shared/api/httpClient.ts
export async function http<T>(
input: string,
init?: RequestInit
): Promise<T> {
const res = await fetch(input, {
...init,
headers: {
"content-type": "application/json",
...(init?.headers ?? {}),
},
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return (await res.json()) as T;
}Feature endpointi i mapiranje#
Endpointi i DTO mapiranje pripadaju feature-u jer se mijenjaju s potrebama feature-a.
// src/features/checkout/api/checkoutApi.ts
import { http } from "@/shared/api/httpClient";
export type CreateOrderInput = {
items: Array<{ productId: string; qty: number }>;
};
export type CreateOrderResponse = { orderId: string };
export function createOrder(input: CreateOrderInput) {
return http<CreateOrderResponse>("/api/orders", {
method: "POST",
body: JSON.stringify(input),
});
}Time izbjegavaš da centralna api mapa postane usko grlo i držiš API promjene lokalizirane.
Next.js napomena: server-only API pozivi#
Ako koristiš App Router i želiš fetchanje na serveru, premjesti server-only API module u server mapu i izbjegavaj importe u client kod.
src/features/orders/
server/
ordersService.ts
api/
types.tsZatim zovi ordersService iz server komponenti, route handlera ili server actions.
Za dublje obrasce oko RSC-a, vidi vodič za React Server Components.
# UI slojevi: primitive vs feature UI vs entity UI#
Tvoj UI sloj treba spriječiti dva česta problema:
- 1Copy-paste UI primitiva kroz feature-e
- 2“components” mapa koja postane odlagalište
Shared UI primitive#
To su niskorazinski gradivni blokovi: Button, Input, Modal, Tabs, Skeleton, Toast. Moraju biti:
- stilski konzistentni
- pristupačni po defaultu
- s malo ovisnosti
src/shared/ui/
button/
Button.tsx
Button.test.tsx
index.ts
input/
modal/
index.tsDobro pravilo: ako komponenta ima poslovno značenje poput CheckoutSummary, nije shared UI.
Entity UI#
Entity moduli su ponovno upotrebljivi jer predstavljaju osnovne domenske koncepte: user, product, order. Često uključuju “entity widgete” poput UserAvatar, ProductCard, OrderStatusBadge.
src/entities/product/
model/
types.ts
ui/
ProductCard.tsx
PriceTag.tsx
index.tsEntity UI je “ljepilo” koje drži feature-e konzistentnima bez guranja svega u shared.
Feature UI#
Feature UI slaže shared UI i entity UI u ishode: forme, ekrane, tokove.
src/features/search/ui/
SearchBar.tsx
SearchResults.tsx
SearchPage.tsx💡 Savjet: Kad uvodiš nove shared UI primitive, odradi design review. Shared komponente su kasnije skupe za promjenu jer skupljaju mnogo konzumenata. Koristi checklistu poput React checklist za design review: performanse, pristupačnost, održivost.
# Domenska logika: drži je čistom, blizu feature-a i testabilnom#
Kad su domenska pravila razbacana po UI komponentama, dobiješ:
- dupliciranu logiku
- krhke promjene
- ponašanje koje je teško testirati
Gdje domenska logika treba živjeti#
- Pravila specifična za feature ostaju u
features/<feature>/libilifeatures/<feature>/model. - Cross-feature, stabilne domenske primitive mogu ići pod
entities/<entity>/model. - Stvarno generički utili idu pod
shared/lib.
Primjer: logika cijena u checkoutu#
// src/features/checkout/lib/pricing.ts
export function calcTotals(
subtotalCents: number,
discountCents: number,
taxRate: number
) {
const discounted = Math.max(0, subtotalCents - discountCents);
const taxCents = Math.round(discounted * taxRate);
return {
subtotalCents,
discountCents,
taxCents,
totalCents: discounted + taxCents,
};
}Ovo se može unit-testati bez renderiranja Reacta, što testove čini brzim i pouzdanim.
# Specifičnosti Next.js-a: App Router, RSC i granice klijenta#
Next.js dodaje ograničenja koja zapravo pomažu arhitekturi, ako ih iskoristiš.
Route datoteke samo za kompoziciju#
Drži app rute tanke:
- čitaj parametre
- pozovi entry komponente feature-a
- poveži providere
// app/(app)/checkout/page.tsx
import { CheckoutPage } from "@/features/checkout";
export default function Page() {
return <CheckoutPage />;
}Izbjegavaj stavljati API pozive, validacijska pravila i transformacije u route datoteku.
Razdvajanje client i server koda#
Praktična konvencija:
| Mapa | Izvršava se gdje | Tipičan sadržaj |
|---|---|---|
features/x/ui | po defaultu client | interaktivne komponente |
features/x/server | samo server | servisi koji ovise o bazi, tajni API pozivi |
features/x/api | oboje | DTO tipovi, zajedničko mapiranje |
Ako trebaš client komponentu, označi je eksplicitno na vrhu datoteke.
// src/features/checkout/ui/CheckoutForm.tsx
"use client";
import { useCheckout } from "../model/useCheckout";
export function CheckoutForm() {
const { submit, isLoading } = useCheckout();
return (
<button onClick={submit} disabled={isLoading}>
Place order
</button>
);
}To čini granicu server/klijent eksplicitnom i sprječava slučajno bundlanje server-only ovisnosti u browser.
# Pravila ovisnosti koja možeš provoditi#
Pravila sprječavaju regresije kad kodna baza raste i kad dolaze novi developeri.
Praktična pravila ovisnosti#
- 1
sharedne import-a ništa iz koda aplikacije. - 2
entitiessmije importati samo izshared. - 3
featuressmije importati izentitiesishared. - 4
appsmije importati iz svega, ali drugi slojevi ne bi smjeli importati izapp. - 5Feature-i ne bi smjeli importati interne dijelove drugih feature-a. Samo javne exporte.
Primjer ESLint pravila (ograničenja importa)#
Ovo je minimalan primjer s ESLint core pravilima. Mnogi timovi dodaju i eslint-plugin-boundaries, ali čak su i core pravila dobar početak.
// .eslintrc.cjs
module.exports = {
rules: {
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["@/features/*/*"],
message: "Importaj samo iz javnog API-ja feature-a, ne iz internih putanja.",
},
{
group: ["@/app/*"],
message: "Ne importaj iz app sloja; app je samo za kompoziciju.",
},
],
},
],
},
};Ovo sprječava duboke importe poput @/features/checkout/ui/CheckoutForm iz drugih modula i gura developere da exportaju iz @/features/checkout.
# Strategija testiranja: što testirati i gdje to staviti#
Skalabilan setup testiranja izbjegava pretjerano oslanjanje na spore UI testove.
Preporučeno smještanje testova#
| Vrsta testa | Lokacija | Što pokriva | Brzo? |
|---|---|---|---|
| Unit testovi | features/*/lib ili entities/*/model | čista logika i domenska pravila | Da |
| Testovi komponenti | shared/ui ili features/*/ui | ponašanje komponente, a11y, stanja | Srednje |
| Integracijski testovi | features/*/tests | tokovi feature-a s mockanim API-jem | Srednje |
| E2E testovi | e2e/ | stvarna korisnička putovanja | Ne |
Primjer: unit test za pricing#
// src/features/checkout/lib/pricing.test.ts
import { calcTotals } from "./pricing";
test("calcTotals computes total with discount and tax", () => {
const r = calcTotals(10000, 2000, 0.25);
expect(r.totalCents).toBe(10000);
expect(r.taxCents).toBe(2000);
});Drži unit testove blizu logike. Kad refaktoriraš, testovi se sele s kodom i ostaju lako pronađivi.
# Česte skalabilnosne zamke i kako ih izbjeći#
Zamka 1: shared mapa postane “ladica za svašta”#
Ako shared uključuje kod specifičan za feature, svi počnu ovisiti o njemu i refaktori postaju rizični.
Rješenje: shared sadrži samo primitive i stvarno generičke libove. Poslovno značenje premjesti u entities ili features.
Zamka 2: “components” kao jedini organizacijski koncept#
Top-level components mapa skriva poslovni kontekst. Završit ćeš tako da tražiš “gdje je checkout forma” i pogađaš.
Rješenje: feature moduli posjeduju svoj UI. Shared UI su samo primitive.
Zamka 3: Duboki importi koji zaobilaze granice#
Duboki importi su vodeći indikator budućeg sprezanja (coupling).
Rješenje: provodi javne API-je kroz index.ts datoteke i lint pravila.
Zamka 4: Duplicirana logika mapiranja API-ja#
Kad svaki ekran drugačije mapira isti DTO, bugovi se pojavljuju pri promjenama API-ja.
Rješenje: mapiraj DTO-e jednom unutar feature api modula ili entity model mappera.
# Održivi primjer: kako sve povezati#
Pretpostavimo da gradiš e-commerce aplikaciju s pretragom, checkoutom i računom.
shared/uidaje Button, Input, Dialog.entities/productdaje ProductCard, tipove i formatiranje.features/searchposjeduje stanje upita pretrage, debouncing, search API i UI rezultata.features/checkoutposjeduje pricing logiku, slanje košarice i payment flow.appspaja routing, providere i kompoziciju feature-a.
Ovo drži promjene lokalnima:
- Promjena pricing pravila utječe na
features/checkout/lib/pricing.tsi testove. - Promjena načina prikaza proizvoda utječe na
entities/product/ui/ProductCard.tsx. - Promjena Button stylinga utječe na
shared/ui/button.
Kad radiš arhitekturni review, možeš brzo skenirati granice feature-a, a zatim primijeniti checklistu poput React checklist za design review: performanse, pristupačnost, održivost.
# Ključne poruke#
- Organiziraj po feature-ima i entitetima, ne po generičkim “components”, i route datoteke drži tankima kao kompoziciju.
- Stavi tanak HTTP transport u
shared/api, a endpoint-e i DTO mapiranje drži unutar svakog feature-a. - Razdvoji UI na shared primitive, entity UI i feature UI kako bi izbjegao napuhan shared sloj.
- Provodi jednosmjerne ovisnosti i javne API-je feature-a koristeći
index.tsexporte i ESLint ograničenja importa. - Drži domensku logiku čistom i testiraj je brzim unit testovima blizu koda, a zatim dodaj integracijske i E2E samo gdje je potrebno.
# Zaključak#
Feature-based React frontend arhitektura uspijeva kad su granice provedive, importi namjerni, a poslovna logika ostaje blizu feature-a koji je posjeduje. Ako želiš pomoć pri primjeni ove strukture na postojeću kodnu bazu ili ti treba Next.js App Router arhitektura spremna za RSC, pristupačnost i performance review, javi se Samiodi za arhitekturni audit i konkretan plan refaktora.
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 →Observabilnost za Next.js App Router u 2026.: Sentry, OpenTelemetry, traceovi i korisna upozorenja
End-to-end postavke za Next.js logiranje, nadzor i tracing sa Sentryjem i OpenTelemetryjem kroz Server Actions, Route Handlers i razliku Edge naspram Node runtimea—uz dashboarde, pragove upozorenja i korelaciju sesije s traceom.
UX obrasci u Next.js App Routeru: Error Boundaries, Loading UI i streaming kako treba
Praktičan vodič za otporan UX u Next.js App Routeru: struktura route segmenata, error.tsx, loading.tsx, not-found.tsx i Suspense streaming obrasci za djelomično renderiranje, sigurnije dohvaćanje podataka i manje pomaka layouta.
Izrada dizajn sustava u Next.js s Radix UI, Tailwindom i Storybookom: vodič od početka do kraja za 2026.
Praktičan, produkcijski spreman pristup izradi i održavanju Next.js dizajn sustava uz Radix UI za pristupačnost, Tailwind za stiliziranje i Storybook za dokumentaciju, testiranje i verzionirana izdanja.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
React Query u velikim aplikacijama: invalidacija cachea, paginacija i obrasci mutacija za stvarne aplikacije
Najbolje prakse invalidacije cachea u React Queryju za aplikacije iz stvarnog svijeta: skalabilan dizajn query keyjeva, strategija invalidacije, optimistična ažuriranja, infinite queryji i pozadinski refetch u Next.js App Routeru.
UX obrasci u Next.js App Routeru: Error Boundaries, Loading UI i streaming kako treba
Praktičan vodič za otporan UX u Next.js App Routeru: struktura route segmenata, error.tsx, loading.tsx, not-found.tsx i Suspense streaming obrasci za djelomično renderiranje, sigurnije dohvaćanje podataka i manje pomaka layouta.
Izrada dizajn sustava u Next.js s Radix UI, Tailwindom i Storybookom: vodič od početka do kraja za 2026.
Praktičan, produkcijski spreman pristup izradi i održavanju Next.js dizajn sustava uz Radix UI za pristupačnost, Tailwind za stiliziranje i Storybook za dokumentaciju, testiranje i verzionirana izdanja.