Next.js projekt koji krene kao jednostavna aplikacija s desetak stranica lako preraste u kaos kad broj ruta, komponenti i API endpointa naraste na stotine. Dobra struktura ne sprječava rast projekta, ali sprječava da taj rast postane bolan. Evo pristupa koji se pokazao pouzdanim za veće Next.js projekte s App Routerom.
Osnovno pravilo: grupiraj po značajki, ne po tipu datoteke
Najčešća greška je struktura gdje su svi komponenti u /components, sve funkcije u /lib, svi hookovi u /hooks — bez obzira na to čemu služe. Za male projekte to je u redu, ali za veće, bolje je grupirati kod prema domeni ili značajki:
src/
features/
auth/
components/
hooks/
actions.ts
types.ts
billing/
components/
hooks/
actions.ts
types.ts
app/
(routes samo, minimalna logika)
components/
ui/ ← generičke, ponovno iskoristive komponente (button, input...)
lib/
(utility funkcije bez poslovne logike)
Ovaj pristup znači da kad radite na naplati (billing), sav relevantan kod je u jednom folderu — ne morate skakati između pet različitih direktorija da nađete sve dijelove jedne funkcionalnosti.
App Router: route grupe za organizaciju bez utjecaja na URL
Next.js App Router podržava route grupe — foldere u zagradama koji ne utječu na konačni URL, ali vam dopuštaju logičko grupiranje ruta i različite layoute za različite dijelove aplikacije:
app/
(marketing)/
layout.tsx ← javni layout, header/footer za marketing stranice
page.tsx → "/"
o-nama/page.tsx → "/o-nama"
(dashboard)/
layout.tsx ← autentificiran layout sa sidebarom
postavke/page.tsx → "/postavke"
Dvije potpuno različite navigacije, dva različita layouta, a URL strukture ostaju čiste.
Server komponente kao default, client komponente kao iznimka
U velikom projektu, disciplina oko "use client" direktive je bitna za performanse. Praktično pravilo: klijentska komponenta treba biti što manja i što niže u stablu komponenti — izvucite interaktivni dio (npr. gumb s onClick) u zaseban mali file, a sve oko njega ostavite kao server komponentu.
// ProizvodKartica.tsx — server komponenta (default)
import DodajUKosaricuGumb from "./DodajUKosaricuGumb";
export default function ProizvodKartica({ proizvod }) {
return (
<div>
<h3>{proizvod.naziv}</h3>
<p>{proizvod.cijena} €</p>
<DodajUKosaricuGumb id={proizvod.id} />
</div>
);
}
// DodajUKosaricuGumb.tsx — jedina client komponenta
"use client";
export default function DodajUKosaricuGumb({ id }: { id: string }) {
return <button onClick={() => dodajUKosaricu(id)}>Dodaj u košaricu</button>;
}
Server Actions umjesto raštrkanih API ruta
Za formu ili mutaciju podataka koja se koristi samo unutar aplikacije, Server Actions često zamjenjuju potrebu za zasebnim /api endpointom, čime se smanjuje broj datoteka i duplicirane validacijske logike:
// features/billing/actions.ts
"use server";
export async function azurirajPretplatu(formData: FormData) {
const plan = formData.get("plan");
// validacija, poziv bazi, revalidatePath...
}
API rute (app/api/.../route.ts) rezervirajte za slučajeve kad vam stvarno treba HTTP endpoint — webhookove, integracije s vanjskim servisima ili javni API.
Dijeljeni tipovi i validacija na jednom mjestu
U velikim projektima česta bol je isti tip podataka definiran na tri različita mjesta (baza, backend, frontend). Definirajte Zod sheme jednom, izvedite TypeScript tipove iz njih:
// features/billing/types.ts
import { z } from "zod";
export const PretplataSchema = z.object({
plan: z.enum(["basic", "pro", "enterprise"]),
cijena: z.number().positive(),
});
export type Pretplata = z.infer<typeof PretplataSchema>;
Praktični checklist za veći projekt
- Poslovna logika grupirana po značajki, ne po tipu datoteke
- Route grupe koriste se za odvajanje layouta (javno vs. autentificirano)
- Client komponente su male i izolirane, ne obavijaju cijele stranice
- Server Actions za interne mutacije, API rute samo kad su stvarno potrebne
- Zod sheme kao jedini izvor istine za oblik podataka
Zaključak
Struktura koja radi za 10 stranica ne radi nužno za 500. Ključna promjena mentaliteta je prijelaz s organizacije "po tipu datoteke" na organizaciju "po značajki", uz disciplinu oko toga gdje živi client-side kod. Ta dva poteza sama po sebi rješavaju veći dio boli koja dolazi s rastom Next.js projekta.




