# Što ćeš izgraditi (i zašto je to važno)#
Ovaj vodič daje ti end-to-end postavku observabilnosti za Next.js App Router: greške, logove i traceove kroz Server Actions, Route Handlers i podjelu Edge naspram Node.js runtimea. Na kraju ćeš imati dashboarde i pragove upozorenja koji hvataju stvarne incidente umjesto da spamaju tim.
Čak i ako već imaš “neko logiranje”, vrlo vjerojatno ti i dalje nedostaju odgovori na pitanja koja su ključna tijekom incidenta: koji je deploy uzrokovao skok, koja je ruta spora, koji je segment korisnika pogođen i koji točno downstream poziv pada. Cilj je smanjiti vrijeme do detekcije i rješavanja tako da probleme učiniš operativno rješivima.
Za šire koncepte observabilnosti pročitaj naš osnovni pregled: Vodič za observabilnost web aplikacija: logovi, metrike, tracing.
# Arhitektura observabilnosti za Next.js App Router#
Praktična postavka za “Next.js logging monitoring Sentry OpenTelemetry” koristi dva komplementarna sloja:
- 1Sentry za nadzor grešaka, release health, utjecaj na korisnike, session replay i workflowe performansi.
- 2OpenTelemetry za vendor-neutral distribuirani tracing i izvoz traceova u backend po izboru.
Možeš koristiti samo Sentry i biti produktivan. Dodavanje OpenTelemetryja se isplati kad imaš više servisa, queueova ili ti trebaju konzistentni traceovi kroz infrastrukturu.
Što instrumentirati u App Routeru#
| Next.js površina | Tipični kvarovi | Što uhvatiti | Primarni alat |
|---|---|---|---|
| Server Actions | spori DB upiti, auth greške, validation greške | spanovi oko poslovne logike, greška s kontekstom | Sentry + OpenTelemetry |
| Route Handlers | timeouti, upstream HTTP greške, problemi s parsiranjem | request span, downstream spanovi, status kodovi | Sentry + OpenTelemetry |
| RSC renderiranje | spori fetch pozivi, serialization greške | render transakcije, fetch spanovi | Sentry |
| Navigacija na klijentu | JS greške, spore rute, puknuti API pozivi | session replay, web vitals, long tasks | Sentry |
| Edge runtime | cold startovi, upstream latencija, ograničeni Node API-ji | minimalni spanovi, hvatanje grešaka | Sentry + selektivni OTel |
Za tradeoffove i ograničenja runtimea vidi: Next.js Edge runtime vs Node.js runtime na Vercelu i Cloudflareu.
ℹ️ Napomena: Logove tretiraj kao dokaz, traceove kao vremensku crtu, a greške kao simptom. Incidente najbrže rješavaš kad možeš skočiti s upozorenja na trace, pa na točnu grešku i njen utjecaj na korisnike.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Next.js | 14 ili 15 | Pretpostavljen App Router |
| Node.js | 18+ | Node runtime za potpunu server instrumentaciju |
| Sentry račun | — | Jedan projekt za frontend plus backend (ili kombinirano) |
| OpenTelemetry collector | opcionalno | Preporučeno za kontrolu izvoza u produkciji |
| Hosting | Vercel, AWS, itd. | Edge i Node se razlikuju |
# Korak 1: Definiraj standarde observabilnosti#
Prije instalacije SDK-ova, postavi standarde kako bi podaci ostali konzistentni kroz timove i servise.
Konvencije imenovanja koje čine podatke pretraživima#
| Stavka | Preporuka | Primjer |
|---|---|---|
| Naziv servisa | stabilan, neovisan o okruženju | web-app |
| Okruženje | production, staging, preview | preview |
| Release | CI commit SHA ili semver | 2026.08.10+sha.abc123 |
| Nazivi transakcija | prema rutama, niska kardinalnost | POST /api/orders |
| Ključ korisnika/sesije | bez PII, stabilan | sid_9f3... |
Kako izgleda “dobro” u produkciji#
Koristi ove početne SLO-ish ciljeve kao startnu točku:
| Signal | Početni cilj | Zašto |
|---|---|---|
| 5xx stopa (Route Handlers) | manje od 0.5% | iznad toga korisnici to brzo osjete |
| P95 latencija API-ja | manje od 800 ms | drži UI responzivnim pod opterećenjem |
| P95 latencija Server Actiona | manje od 600 ms | Server Actions su interakcije vidljive korisniku |
| Regresija grešaka | 2x baseline unutar 15 min | rano hvata loše deployeve |
| Apdex | 0.85+ | brza provjera “je li sporo” |
Podesi pragove nakon 2 do 4 tjedna baznih podataka.
# Korak 2: Instaliraj i konfiguriraj Sentry za Next.js App Router#
Sentryjev Next.js SDK pokriva klijent i server te dobro podržava App Router površine kad je ispravno konfiguriran.
Instaliraj ovisnosti#
npm i @sentry/nextjs
npx @sentry/wizard@latest -i nextjsOvo tipično kreira:
sentry.client.config.*sentry.server.config.*sentry.edge.config.*- ažuriranja u
next.config.*
Ključne Sentry postavke koje su važne#
Provjeri da tvoja Sentry konfiguracija uključuje:
- Releases i environments
- Traces sampling za performanse
- Profiles sampling ako ga koristiš (server profiling ovisi o runtime podršci)
Primjer minimalne server konfiguracije:
// sentry.server.config.js
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.VERCEL_ENV || process.env.NODE_ENV,
release: process.env.SENTRY_RELEASE,
tracesSampleRate: 0.1,
});Klijentska konfiguracija također treba postaviti sampling rate i uključiti session replay samo kad je potrebno, kako bi se kontrolirao trošak.
Preporučena strategija samplinga#
| Okruženje | Greške | Traceovi | Replay |
|---|---|---|---|
| production | 100% (uz grupiranje) | 5% do 15% | 0.5% sesija, 10% na grešku |
| staging | 100% | 25% | 2% sesija, 25% na grešku |
| preview | 100% | 10% | isključeno ili 0.1% |
Prilagodi prema prometu. Primjerice, pri 1000 zahtjeva u minuti, 10% traceova daje 100 traceova u minuti, što je obično dovoljno za trijažu performansi bez prevelikog ingestiona.
💡 Savjet: Kreni s 10% traceova u produkciji, pa dodaj pravila za dynamic sampling: zadrži 100% za spore transakcije (P95+), 5xx odgovore i ključne korisničke tokove poput checkouta.
# Korak 3: Hvataj greške i kontekst u Server Actions#
Server Actions često “tiho” failaju ako se oslanjaš samo na console output. Želiš:
- iznimku
- naziv actiona
- korisnički/sesijski kontekst
- oblik inputa (sanitiziran)
- trace koji uključuje downstream pozive
Primjer Server Actiona sa Sentryjem i strukturiranim kontekstom#
// app/actions/createOrder.ts
"use server";
import * as Sentry from "@sentry/nextjs";
export async function createOrderAction(input: { sku: string; qty: number }) {
return await Sentry.startSpan(
{ name: "server_action:createOrder", op: "function" },
async () => {
try {
// Your business logic here
if (input.qty <= 0) throw new Error("Invalid quantity");
return { ok: true };
} catch (err) {
Sentry.captureException(err, {
tags: { surface: "server_action" },
extra: { sku: input.sku, qty: input.qty },
});
throw err;
}
}
);
}Ovo kreira jasan span i dodaje prave metapodatke za grupiranje i filtriranje.
Što ne logirati u Server Actions#
Izbjegavaj logiranje:
- email adresa, brojeva telefona, adresa
- raw access tokena
- cijelih request headera
- podataka o plaćanju
Umjesto toga logiraj stabilne identifikatore: userId, tenantId, orderId, sessionId.
# Korak 4: Instrumentiraj Route Handlers (API) s traceovima i request ID-jevima#
Route Handlers su mjesto gdje želiš korelaciju kroz:
- inbound request
- outbound fetch pozive
- DB pozive
- slanje u queue
- status i latenciju odgovora
Dodaj stabilan request ID i propagiraj trace headere#
U Next.js možeš implementirati middleware koji dodaje request ID header. Neka bude niske kardinalnosti i jedinstven po requestu.
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const requestId = crypto.randomUUID();
const res = NextResponse.next();
res.headers.set("x-request-id", requestId);
return res;
}Zatim u Route Handleru logiraj i tagiraj s x-request-id, te proslijedi trace headere kada zoveš downstream servise.
// app/api/orders/route.ts
import * as Sentry from "@sentry/nextjs";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const requestId = req.headers.get("x-request-id") || "missing";
return await Sentry.startSpan(
{ name: "POST /api/orders", op: "http.server" },
async () => {
try {
const body = await req.json();
const traceHeaders = {
"sentry-trace": req.headers.get("sentry-trace") || "",
baggage: req.headers.get("baggage") || "",
"x-request-id": requestId,
};
const upstream = await fetch(process.env.ORDERS_SERVICE_URL!, {
method: "POST",
headers: { "content-type": "application/json", ...traceHeaders },
body: JSON.stringify(body),
});
if (!upstream.ok) {
throw new Error(`Upstream failed with status ${upstream.status}`);
}
return NextResponse.json({ ok: true }, { headers: { "x-request-id": requestId } });
} catch (err) {
Sentry.captureException(err, {
tags: { surface: "route_handler" },
extra: { requestId },
});
return NextResponse.json({ ok: false }, { status: 500, headers: { "x-request-id": requestId } });
}
}
);
}Ovo stvara end-to-end lanac: browser event → Route Handler trace → upstream servis, uz request ID za logove.
# Korak 5: Dodaj OpenTelemetry tracing (vendor-neutral) za server runtime#
Sentry ti daje odličan workflow, ali OpenTelemetry donosi prenosivost i konzistentnost kroz infrastrukturu. Najrobustnija priča za Next.js OpenTelemetry i dalje je na Node runtimeu, jer exporter-i i auto-instrumentation tipično ovise o Node API-jima.
Preporučena produkcijska topologija#
| Komponenta | Gdje radi | Odgovornost |
|---|---|---|
| Next.js app | Node runtime | kreiranje spanova, dodavanje konteksta |
| OpenTelemetry Collector | sidecar ili zaseban servis | batchanje, sampling, export |
| Trace backend | Tempo, Honeycomb, Datadog, New Relic | pohrana, upiti, dashboardi |
Instaliraj osnovne OpenTelemetry pakete#
Drži minimalno kako bi izbjegao bundle bloat.
npm i @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
npm i @opentelemetry/exporter-trace-otlp-httpMinimalni OTel init za Node runtime#
Napravi server-only datoteku i učitaj je rano. Točna strategija učitavanja ovisi o deploymentu, ali srž konfiguracije izgleda ovako:
// otel-node.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
headers: { "x-otlp-api-key": process.env.OTEL_API_KEY || "" },
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();Zadrži OTel export isključivo na server strani. Ne pokušavaj ovo pokretati na Edgeu.
⚠️ Upozorenje: Auto-instrumentation može stvarati spanove visoke kardinalnosti ako hvataš pune URL-ove s ID-jevima. Normaliziraj nazive transakcija i izbjegavaj stavljati korisničke identifikatore u nazive spanova. Identifikatore stavljaj u atribute samo kad je nužno, i tek nakon privacy reviewa.
Dodaj ručne spanove tamo gdje auto-instrumentation ne pomaže#
Auto-instrumentation često propušta granice poslovne logike poput “checkout validacija” ili “credit check”.
// app/lib/pricing.ts
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("web-app");
export async function calculatePrice(sku: string) {
return await tracer.startActiveSpan("pricing:calculate", async (span) => {
try {
span.setAttribute("sku", sku);
// ... do work
return 1999;
} finally {
span.end();
}
});
}Koristi ručne spanove štedljivo, oko top-level operacija koje stvarno upituješ tijekom incidenta.
# Korak 6: Observabilnost Edge runtimea bez razbijanja buildova#
Edge runtime je odličan za latenciju, ali ograničava Node API-je i neke SDK-ove. Siguran pristup je:
- držati Edge instrumentaciju laganom
- ne oslanjati se na Node-only OTel SDK na Edgeu
- tagirati eventove s
runtime=edgekako bi se dashboardi čisto razdvajali
Čest obrazac je osloniti se na Sentry Edge config za greške i osnovni tracing, a OpenTelemetry rezervirati za Node runtime putanje.
Za odluku što treba ići na Edge ponovno pogledaj: Next.js Edge runtime vs Node.js runtime na Vercelu i Cloudflareu.
Praktično pravilo za timove#
| Workload | Preporučeni runtime | Zašto |
|---|---|---|
| Auth gating, redirecti, A/B routing | Edge | brzo, blizu korisniku |
| Plaćanja, teški DB pristup, generiranje PDF-a | Node | puna SDK podrška, dulje CPU vrijeme |
| Kompleksna observabilnost s OTel | Node | kompatibilnost exportera i instrumentacije |
# Korak 7: Strategija logiranja koja radi u serverlessu#
U serverlessu logovi su često najbrži “prvi pogled”, ali samo ako su strukturirani i korelirani.
Što logirati (i kako)#
Logiraj događaje na tri razine:
| Razina | Kada koristiti | Primjer polja |
|---|---|---|
| info | ključne poslovne prekretnice | event, orderId, durationMs |
| warn | neočekivano, ali oporavljivo | event, reason, retry |
| error | kvarovi i iznimke | event, requestId, traceId |
Koristi JSON logove kako bi platforma i log backend mogli parsirati polja. Na Node runtimeu možeš koristiti bilo koji logger, ali drži ga jednostavnim i izbjegavaj velike ovisnosti na Edgeu.
Minimalni JSON logger s poljima za korelaciju#
// app/lib/log.ts
export function log(level: "info" | "warn" | "error", message: string, data: Record<string, unknown> = {}) {
const payload = {
level,
message,
ts: new Date().toISOString(),
...data,
};
console.log(JSON.stringify(payload));
}Zatim u Route Handleru:
// app/api/health/route.ts
import { NextRequest, NextResponse } from "next/server";
import { log } from "@/app/lib/log";
export async function GET(req: NextRequest) {
const requestId = req.headers.get("x-request-id") || "missing";
log("info", "health_check", { requestId, surface: "route_handler" });
return NextResponse.json({ ok: true });
}Ako tvoj tracing backend izlaže trace ID, uključi ga u logove kao traceId. Za Sentry možeš uključiti i sentryTrace iz inbound headera.
# Korak 8: Poveži korisničke sesije s backend traceovima#
Korelacija je ono što observabilnost pretvara iz grafova u root cause analizu.
Strategija identifikatora sesije#
Koristi stabilan, ne-PII identifikator sesije:
- generiran na klijentu
- spremljen u first-party cookie
- proslijeđen kao header prema API rutama
Primjer: postavi cookie sid i šalji ga kao x-session-id na fetch pozivima.
Kreiranje session ID-ja na klijentu (lagano)#
// app/lib/session.ts
export function getOrCreateSessionId() {
const key = "sid";
const existing = window.localStorage.getItem(key);
if (existing) return existing;
const sid = `sid_${crypto.randomUUID()}`;
window.localStorage.setItem(key, sid);
return sid;
}Zatim ga dodaj requestovima:
// app/lib/http.ts
import { getOrCreateSessionId } from "@/app/lib/session";
export async function apiFetch(path: string, init: RequestInit = {}) {
const sid = getOrCreateSessionId();
const headers = new Headers(init.headers);
headers.set("x-session-id", sid);
return fetch(path, { ...init, headers });
}Uhvati isti session ID u Sentryju#
U klijentskom kodu postavi user context tako da uključuje session ID. Drži ga odvojenim od user.id ako imaš i autentificirane korisnike.
// sentry-session.ts
import * as Sentry from "@sentry/nextjs";
import { getOrCreateSessionId } from "@/app/lib/session";
export function attachSentrySession() {
const sid = getOrCreateSessionId();
Sentry.setUser({ id: sid });
Sentry.setTag("session_id", sid);
}Pozovi ovo jednom pri startu aplikacije u client komponenti.
Koristi session ID u backend spanovima i logovima#
U Route Handlerima i Server Actions, čitaj x-session-id iz headera kada je dostupan i dodaj ga:
- kao Sentry tag
- kao OTel attribute
- kao log polje
Ovo omogućuje upite poput “prikaži sve greške za sesiju X” i “otvori trace za sporu checkout sesiju”.
🎯 Ključna poruka: Korelacija zahtijeva da isti identifikator postoji na tri mjesta: browser telemetry, backend telemetry i logovi. Bez toga se ne možeš pouzdano prebaciti s “korisničke prijave” na “trace” u par minuta.
# Korak 9: Dashboardi koje tim stvarno koristi#
Dashboardi trebaju odgovoriti na operativna pitanja u manje od 30 sekundi. Napravi odvojene dashboarde za:
- dostupnost i greške
- latenciju i regresije
- runtime podjelu: Edge vs Node
- kritične tokove: login, checkout, search
Preporučeni widgeti za dashboard#
| Dashboard | Widget | Predloženi breakdown |
|---|---|---|
| Dostupnost | 5xx stopa, broj grešaka | po ruti, po releaseu |
| Performanse | P50, P95 latencija | po ruti, po runtimeu |
| Utjecaj na korisnike | pogođeni korisnici, sesije | po državi, po pregledniku |
| Regresija | greške uvedene u zadnjem releaseu | po releaseu, po endpointu |
| Ovisnosti | upstream latencija i greške | po hostnameu |
U Sentryju posebno konfiguriraj:
- Releases i commit tracking
- Performance prikaze s breakdownom transakcija
- Alerts vezane uz release verziju
Ako imaš background jobove, tretiraj ih kao zaseban servis s vlastitim dashboardom i alertima. Next.js projekti često skrivaju kritičan rad u cronu ili queueovima, pa instrumentiraj i to: Next.js background jobovi, queueovi i cron na Vercelu.
# Korak 10: Korisna upozorenja i pragovi (bez buke)#
Većina alert fatiguea dolazi iz dva problema: alertanje na sirove brojeve umjesto na stope, i neodvajanje “simptoma” od “utjecaja”.
Praktični pragovi upozorenja za početak#
| Upozorenje | Uvjet | Prozor | Akcija |
|---|---|---|---|
| Skok API 5xx | 5xx stopa veća od 1% | 10 min | page on-call |
| Regresija latencije | P95 veći od 1.5x baseline | 15 min | obavijesti dev kanal |
| Regresija grešaka na releaseu | broj novih issueva veći od 20 | 30 min nakon deploya | blokiraj rollout |
| Checkout failure | stopa neuspjeha POST /api/orders veća od 0.5% | 10 min | page on-call |
| Edge anomalija | Edge greške veće od 2x baseline | 15 min | istraži runtime konfiguraciju |
Koristi različite kanale po ozbiljnosti. Paging treba biti rezerviran za uvjete koji utječu na prihod ili osnovnu funkcionalnost.
Učini upozorenja operativno rješivima uz kontekst#
Svako upozorenje treba uključiti:
- okruženje
- release
- top 3 pogođene transakcije
- link na primjer tracea
- pogođene korisnike ili sesije
U Sentryju veži upozorenja na:
- Issue alerts za skokove grešaka i nove regresije
- Metric alerts za latenciju i throughput
# Česte zamke (i kako ih izbjeći)#
- 1Tagovi visoke kardinalnosti — Izbjegavaj tagiranje punim URL-ovima koji sadrže ID-jeve ili emailove. Normaliziraj rute i koristi sigurne identifikatore.
- 2Bez runtime podjele — Ako ne tagiraš
runtime=edgenaspramruntime=node, krivo ćeš dijagnosticirati performanse i okriviti pogrešan sloj. - 3Sampling bez strategije — Sampling svega ravno na 1% često promaši baš spore traceove koje trebaš. Zadrži veći sampling za spore i failing zahtjeve.
- 4Logiranje bez korelacije — Logovi koji ne uključuju
requestId,sessionIdi trace kontekst su skupa buka tijekom incidenta. - 5Tretiranje background jobova kao “sporednih” — neuspjeli cron runovi često uzrokuju incidente vidljive korisniku satima kasnije. Instrumentiraj jobove kao servise prvog reda.
# Ključni zaključci#
- Instrumentiraj Next.js App Router end-to-end pokrivajući Server Actions, Route Handlers i navigaciju na klijentu, a zatim razdvoji dashboarde po Edge vs Node runtimeu.
- Koristi Sentry za brzo trijažiranje grešaka i utjecaj na korisnike, a dodaj OpenTelemetry na Node runtimeu za vendor-neutral distribuirane traceove.
- Standardiziraj polja za korelaciju:
x-request-id,x-session-id, plus trace headere poputsentry-traceibaggage, pa ih stavi u logove, spanove i error eventove. - Izgradi dashboarde koji brzo odgovaraju na operativna pitanja: 5xx stopa, P95 latencija, regresije po releaseu i zdravlje ovisnosti po hostnameu.
- Kreni s pragovima upozorenja temeljenima na stopama i baselineu (ne na sirovim brojevima) i rutiraj upozorenja po ozbiljnosti kako bi izbjegao alert fatigue.
# Zaključak#
Dobra postavka “Next.js logging monitoring Sentry OpenTelemetry” nije u prikupljanju više podataka — nego u tome da kvarove možeš dijagnosticirati u minutama. Instrumentiraj Sentry kroz klijent, server i edge, dodaj OpenTelemetry na Node runtimeu za prenosiv tracing, standardiziraj korelacijske ID-jeve i isporuči dashboarde i upozorenja koja mapiraju na utjecaj na korisnike i poslovni rizik.
Ako želiš da Samioda ovo implementira end-to-end u tvojoj Next.js codebaseu, uključujući strategiju runtime podjele, dashboarde i fino podešavanje upozorenja, kontaktiraj nas i isporučit ćemo observability postavku koju tvoj tim stvarno može operativno koristiti u produkciji.
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 →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.
Kontrolna lista za React code review koju koristimo: performanse, pristupačnost i održivost
Praktična kontrolna lista za React code review usmjerena na performanse, pristupačnost i održivost, s primjerima, savjetima za automatizaciju i copy-paste predloškom.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Observabilnost web aplikacija: praktični vodič za logove, metrike i tracing za React i Next.js
End-to-end, produkcijski spremna observability postava za React i Next.js: praćenje grešaka, nadzor performansi, strukturirani logovi, tracing, nadzorne ploče i alerti koji hvataju stvarne probleme.
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.