# Što ćete izgraditi#
Ovaj vodič prikazuje postav spreman za produkciju za Next.js Stripe pretplate uz tri neupitna zahtjeva: pouzdano stanje naplate, samostalno upravljanje korisnika i determinističku kontrolu pristupa.
Implementirat ćete:
- Tok kupnje pretplate koji veže Stripe objekte uz zapise korisnika.
- Stripe Billing Portal za nadogradnje, downgradeove, otkazivanja i preuzimanje računa.
- Verificirane webhookove s idempotentnošću kako biste izbjegli dvostruku obradu.
- Čisto mapiranje stanja pretplate u prava pristupa (entitlements) na značajke.
- Strategiju testiranja sa Stripe CLI-jem, uz česte načine kvara i sigurnosne aspekte.
Ako želite širi API obrazac za integracije s vanjskim sustavima, pogledajte i naš vodič za integraciju API-ja.
# Preduvjeti i arhitektura#
Trebate jedan “izvor istine” za pristup. U produkciji to mora biti vaša baza podataka, a ne Stripe API pozivi na svaki zahtjev i ne provjere na klijentu.
Pretpostavke o stacku#
- Next.js 14 ili 15 (App Router)
- Node runtime za webhook rutu (provjera potpisa treba raw body)
- Stripe Node SDK
- Baza (preporučeno PostgreSQL) preko Prisma ili slično
Što ide gdje#
| Briga | Stripe | Vaša aplikacija |
|---|---|---|
| Cijene (proizvodi, cijene, trial) | Definirano u Dashboardu ili preko API-ja | Referencirate Price ID-eve u konfiguraciji |
| Identitet korisnika | Customer objekt | Mapirate userId na stripeCustomerId |
| Životni ciklus pretplate | Subscription, Invoice, PaymentIntent | Pohranjujete trenutni status, datume obnove, plan |
| Samostalno upravljanje | Billing Portal | Dajete link na Portal session |
| Kontrola pristupa | — | Prava pristupa izvedena iz zapisa pretplate |
| Ažuriranje istine | Webhookovi | Verificirani + idempotentni upisi |
🎯 Ključna poruka: Stripe tretirajte kao engine za naplatu, a vašu bazu kao autoritet za pristup koji se ažurira putem webhookova.
# Korak 1: Modelirajte planove, probne periode i prava pristupa#
Izbjegnite kodiranje logike “Pro”, “Team” i “Enterprise” po cijeloj bazi koda. Držite jednu mapu planova koja referencira Stripe Price ID-eve i definira prava pristupa.
Konfiguracija planova#
Price ID-eve spremite u env varijable kako biste čisto razdvojili test i live mod.
| Plan | Stripe Price ID env var | Trial dana | Primjer prava pristupa |
|---|---|---|---|
| Starter | STRIPE_PRICE_STARTER | 7 | Osnovne značajke, 1 workspace |
| Pro | STRIPE_PRICE_PRO | 14 | Napredne značajke, 5 workspaceova |
| Team | STRIPE_PRICE_TEAM | 14 | SSO, 20 workspaceova, prioritetna podrška |
Prava pristupa definirajte u kodu kao kompaktan “capabilities objekt”. Neka bude stabilan i verzionabilan.
// lib/billing/plans.ts
export type PlanKey = "starter" | "pro" | "team";
export const PLANS: Record<PlanKey, {
priceId: string;
trialDays: number;
entitlements: {
workspaces: number;
sso: boolean;
prioritySupport: boolean;
advancedExports: boolean;
};
}> = {
starter: {
priceId: process.env.STRIPE_PRICE_STARTER!,
trialDays: 7,
entitlements: { workspaces: 1, sso: false, prioritySupport: false, advancedExports: false },
},
pro: {
priceId: process.env.STRIPE_PRICE_PRO!,
trialDays: 14,
entitlements: { workspaces: 5, sso: false, prioritySupport: false, advancedExports: true },
},
team: {
priceId: process.env.STRIPE_PRICE_TEAM!,
trialDays: 14,
entitlements: { workspaces: 20, sso: true, prioritySupport: true, advancedExports: true },
},
};Tablice u bazi koje su vam stvarno potrebne#
Držite billing podatke minimalnima, ali dovoljnima za provjere pristupa, korisničku podršku i usklađivanje (reconciliation).
| Tablica | Ključna polja | Zašto je bitno |
|---|---|---|
User | id, email, stripeCustomerId | Stabilno mapiranje korisnika na Stripe Customer |
Subscription | userId, stripeSubscriptionId, status, priceId, currentPeriodEnd, cancelAtPeriodEnd | Brzo rješavanje prava pristupa |
WebhookEvent | stripeEventId, type, processedAt | Idempotentnost i audit trail |
EntitlementSnapshot (opcionalno) | userId, json, updatedAt | Brza čitanja, feature flagovi, reporting |
Dobro pravilo: prava pristupa računajte iz polja Subscription u runtimeu, a snapshot spremite samo ako su čitanja ekstremno učestala ili trebate povijesni reporting.
# Korak 2: Napravite Checkout za nove pretplate#
Za većinu SaaS timova Stripe Checkout je najbrži put do usklađenog, lokaliziranog toka kupnje koji podržava poreze.
Ruta za kreiranje Checkout Sessiona#
Koristite server-side rutu koja prima planKey, učitava odgovarajući priceId i kreira Checkout Session u subscription modu.
// app/api/billing/checkout/route.ts
import Stripe from "stripe";
import { NextResponse } from "next/server";
import { PLANS, PlanKey } from "@/lib/billing/plans";
import { getCurrentUser } from "@/lib/auth/getCurrentUser";
export const runtime = "nodejs";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2025-02-24.acacia",
});
export async function POST(req: Request) {
const user = await getCurrentUser();
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { planKey } = await req.json();
const plan = PLANS[planKey as PlanKey];
if (!plan) return NextResponse.json({ error: "Invalid plan" }, { status: 400 });
const session = await stripe.checkout.sessions.create({
mode: "subscription",
customer: user.stripeCustomerId ?? undefined,
customer_email: user.stripeCustomerId ? undefined : user.email,
line_items: [{ price: plan.priceId, quantity: 1 }],
subscription_data: { trial_period_days: plan.trialDays },
allow_promotion_codes: true,
success_url: `${process.env.APP_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/pricing`,
metadata: { userId: user.id, planKey },
});
return NextResponse.json({ url: session.url });
}Ova ruta namjerno ne dodjeljuje pristup. Pristup se mijenja tek nakon obrade webhookova.
⚠️ Upozorenje: Ne otključavajte značajke u
success_urlna temelju query parametara. Korisnici mogu doći na tu stranicu bez uspješne naplate, a ponovni pokušaji mogu stvoriti stanje izvan redoslijeda. Webhookovi su izvor istine.
Vežite customer ID uz korisnika#
Nakon prvog checkouta Stripe će kreirati Customer ako ste poslali customer_email. Uhvatite customer iz checkout.session.completed webhooka i spremite ga u User.stripeCustomerId.
# Korak 3: Dodajte Stripe Billing Portal (Self-Serve)#
Billing Portal smanjuje opterećenje podrške i povećava zadržavanje jer nadogradnje i ispravci plaćanja postaju bez trenja. Stripe je objavio da su neuspjela plaćanja veliki izvor nenamjernog churn-a; dati korisnicima self-serve način za ažuriranje kartice jedna je od billing promjena s najvećim ROI-jem.
Ruta za kreiranje Portal sessiona#
// app/api/billing/portal/route.ts
import Stripe from "stripe";
import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth/getCurrentUser";
export const runtime = "nodejs";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2025-02-24.acacia",
});
export async function POST() {
const user = await getCurrentUser();
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (!user.stripeCustomerId) {
return NextResponse.json({ error: "No Stripe customer" }, { status: 400 });
}
const session = await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url: `${process.env.APP_URL}/billing`,
});
return NextResponse.json({ url: session.url });
}U Stripe Dashboardu konfigurirajte Portal postavke za dopuštene promjene. Tipična produkcijska postava:
| Postavka Portala | Preporuka | Zašto |
|---|---|---|
| Otkazivanje pretplate | Dopustiti | Smanjuje broj tiketa |
| Ažuriranje načina plaćanja | Dopustiti | Smanjuje churn zbog neuspjelih obnova |
| Promjena plana | Dopustiti, ali ograničiti na vaš proizvod | Sprječava neusklađene cijene |
| Povijest računa | Dopustiti | Manje pitanja o naplati |
| Proration | Odluka prema poslovnom modelu | Izbjegnite iznenadne račune |
# Korak 4: Webhookovi koji su verificirani, idempotentni i deterministični#
Webhookovi su mjesto gdje se većina billing implementacija “radilo je lokalno” raspadne. Morate pokriti: retry, dostavu izvan redoslijeda i parcijalne kvarove.
Odaberite evente koji su vam stvarno potrebni#
Minimalan, robustan set:
| Event | Koristite za | Napomene |
|---|---|---|
checkout.session.completed | Povezivanje korisnika s customerom, čitanje subscription ID-a | Nije garancija plaćenog invoicea za neke tokove |
customer.subscription.created | Kreiranje lokalnog zapisa pretplate | Korisno kad se pretplata kreira izvan Checkouta |
customer.subscription.updated | Promjene statusa, otkazivanja, promjene plana | Većina promjena stanja prolazi ovdje |
customer.subscription.deleted | Hard delete ili završetak pretplate | Odmah označite kao otkazanu |
invoice.payment_succeeded | Označite plaćeni period, obrade obnove | Jak signal da je pretplata financirana |
invoice.payment_failed | Dunning tokovi, politika ograničenja pristupa | Odlučite grace period |
Možete krenuti s manjim brojem, ali morate pokriti životni ciklus koji utječe na pristup.
Implementirajte webhook rutu s provjerom potpisa#
Stripe zahtijeva korištenje raw body-ja za provjeru potpisa. U Next.js App Routeru često se koristi req.text().
// app/api/stripe/webhook/route.ts
import Stripe from "stripe";
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import { upsertSubscriptionFromStripe } from "@/lib/billing/sync";
import { markWebhookEventProcessed, wasWebhookEventProcessed } from "@/lib/billing/idempotency";
export const runtime = "nodejs";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2025-02-24.acacia",
});
export async function POST(req: Request) {
const sig = (await headers()).get("stripe-signature");
if (!sig) return NextResponse.json({ error: "Missing signature" }, { status: 400 });
const rawBody = await req.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
}
if (await wasWebhookEventProcessed(event.id)) {
return NextResponse.json({ received: true });
}
try {
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
await upsertSubscriptionFromStripe({ checkoutSession: session });
break;
}
case "customer.subscription.created":
case "customer.subscription.updated":
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription;
await upsertSubscriptionFromStripe({ subscription: sub });
break;
}
case "invoice.payment_succeeded":
case "invoice.payment_failed": {
const invoice = event.data.object as Stripe.Invoice;
await upsertSubscriptionFromStripe({ invoice });
break;
}
default:
break;
}
await markWebhookEventProcessed(event.id, event.type);
return NextResponse.json({ received: true });
} catch (e) {
// Stripe will retry on non-2xx responses
return NextResponse.json({ error: "Webhook handler failed" }, { status: 500 });
}
}Strategija idempotentnosti koja radi pod retry-jevima#
Stripe će pokušavati ponovno slati webhookove danima ako vaš endpoint vraća non-2xx. Duplikate morate obraditi čak i kad vraćate 200, jer se dostava može dogoditi dvaput.
Praktičan pristup:
- Kreirajte
WebhookEventredak ključan postripeEventId. - Stavite unique constraint na
stripeEventId. - Ako insert padne zbog unique, preskočite obradu.
Ako trebate i semantiku “exactly once” preko više upisa, koristite DB transakciju koja uključuje:
- 1Insert u
WebhookEvent - 2Upsert stanja pretplate
Sync logika: normalizirajte Stripe objekte u svoju shemu#
Vaše provjere pristupa ne bi trebale ovisiti o desecima Stripe polja. Mapirajte u stabilan lokalni model.
// lib/billing/normalize.ts
import Stripe from "stripe";
export function normalizeSubscription(sub: Stripe.Subscription) {
return {
stripeSubscriptionId: sub.id,
stripeCustomerId: sub.customer as string,
status: sub.status,
priceId: (sub.items.data[0]?.price?.id ?? null) as string | null,
cancelAtPeriodEnd: sub.cancel_at_period_end,
currentPeriodEnd: new Date(sub.current_period_end * 1000),
trialEnd: sub.trial_end ? new Date(sub.trial_end * 1000) : null,
};
}Zatim upsertSubscriptionFromStripe može:
- Razriješiti
userIdizstripeCustomerId(mora biti spremljen na useru). - Upsertati
SubscriptionpostripeSubscriptionId. - Ažurirati
User.stripeCustomerIdkad ga prvi put vidite.
ℹ️ Napomena: Neki eventi uključuju samo ID-eve osim ako ne expandate objekte. U webhookovima Stripe tipično uključuje pune objekte za primarni objekt eventa, ali budite konzervativni i obradite i slučajeve kad nedostaju ugniježđena polja.
# Korak 5: Izračunajte prava pristupa iz stanja pretplate#
Vaša aplikacija treba brzo odgovoriti na jedno pitanje: “Što ovaj korisnik smije raditi upravo sada?”
Definirajte politiku pristupa#
Česta produkcijska politika:
- Pristup je aktivan kad je
statusactiveilitrialing. - Opcionalan grace period kad je
statuspast_duekraće od N dana. - Nema pristupa kad je
canceled,unpaidiliincomplete_expired.
Također uzmite u obzir: otkazivanja s cancel_at_period_end i dalje dopuštaju pristup do current_period_end.
Funkcija za rješavanje prava pristupa#
Ovu funkciju trebaju koristiti API rute i server komponente. Ne smije zvati Stripe.
// lib/billing/entitlements.ts
import { PLANS, PlanKey } from "@/lib/billing/plans";
const PRICE_ID_TO_PLAN: Record<string, PlanKey> = Object.fromEntries(
Object.entries(PLANS).map(([k, v]) => [v.priceId, k as PlanKey])
);
export function resolveEntitlements(subscription: {
status: string;
priceId: string | null;
currentPeriodEnd: Date;
trialEnd: Date | null;
cancelAtPeriodEnd: boolean;
}) {
const now = Date.now();
const periodEnd = subscription.currentPeriodEnd.getTime();
const isActive =
subscription.status === "active" ||
subscription.status === "trialing" ||
(subscription.status === "canceled" && now < periodEnd);
const planKey = subscription.priceId ? PRICE_ID_TO_PLAN[subscription.priceId] : null;
if (!isActive || !planKey) {
return { isActive: false, planKey: null, entitlements: null };
}
return {
isActive: true,
planKey,
entitlements: PLANS[planKey].entitlements,
};
}Provedite prava pristupa u proizvodu#
Prava pristupa koristite u točkama odluke, ne rasuto po UI-ju.
Primjeri:
- Kreiranje workspacea: usporedite
currentWorkspaceCountsentitlements.workspaces. - SSO rute: zahtijevajte
entitlements.sso === true. - Export: dopustite napredne exporte samo za Pro i više.
To nadogradnje čini mjerljivima. Možete logirati evente “feature blocked due to plan” kako biste vidjeli što potiče konverziju i povezati to s praksama observabilityja iz našeg vodiča za observability web aplikacija.
# Korak 6: Lokalno testiranje sa Stripe CLI (i što treba provjeriti)#
Stripe CLI je najbrži način da validirate webhook handler, uključujući provjeru potpisa i retry-jeve.
Instalacija i prijava#
stripe login
stripe --versionProsljeđivanje webhookova u Next.js#
Pokrenite aplikaciju na http://localhost:3000, zatim:
stripe listen --forward-to localhost:3000/api/stripe/webhookKopirajte webhook signing secret koji CLI ispiše i postavite ga kao STRIPE_WEBHOOK_SECRET u lokalni env.
Okidanje relevantnih eventa#
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger invoice.payment_succeeded
stripe trigger invoice.payment_failedKako izgleda dobar testni checklist#
| Test | Kako | Očekivani rezultat |
|---|---|---|
| Provjera potpisa | Pošaljite request bez potpisa | Endpoint vraća 400 |
| Idempotentnost | Ponovno pošaljite isti event ID | Nema duplih upisa u DB |
| Eventi izvan redoslijeda | Okidajte update prije created | Upsert i dalje daje konzistentan redak |
| Retry nakon greške | Jednom forsirajte DB grešku | Stripe retry-a i na kraju uspije |
| Mapiranje plana | Koristite različite Price ID-eve | Točan planKey i prava pristupa |
| Ponašanje otkazivanja | Postavite cancel_at_period_end | Pristup ostaje do current_period_end |
💡 Savjet: U stagingu konfigurirajte zaseban webhook endpoint i Stripe “test mode” ključeve. Nikad ne usmjeravajte Stripe test mode na produkcijske podatkovne storeove.
# Korak 7: Načini kvara za koje morate dizajnirati#
Billing kvarovi nisu rubni slučajevi. Oni su normalne operacije u većem opsegu: istek kartice, nedovoljno sredstava, bankovna autentifikacija i mrežni problemi.
Česti načini kvara i mitigacije#
| Način kvara | Što se događa | Mitigacija |
|---|---|---|
| Webhook handler vraća 500 | Stripe retry-a satima ili danima | Handler neka bude brz, transakcijski i observable |
| Duplikati webhook dostave | Isti event dođe dvaput | Unique constraint na stripeEventId |
| Dostava izvan redoslijeda | Update dođe prije create | Upsert po Stripe ID-evima, ne po lokalnim pretpostavkama |
| Nedostaje mapiranje customer→user | Pretplata postoji, ali nema usera | Koristite metadata userId i uskladite backfill jobom |
| Neusklađen plan | Price ID nije u configu | Default na no access i alert |
| Neuspjelo plaćanje | Pretplata postane past_due | Odlučite grace period; vodite korisnika u Billing Portal |
| Aplikacija zove Stripe na svaki request | Latencija i rate limitovi | Prava pristupa rješavajte iz DB-a, osvježavajte webhooksima |
Observability zahtjevi za billing#
Minimalno logirajte:
- Stripe
event.id,event.typei vrijeme obrade - Ishode upisa u bazu
- Incidente “Unknown Price ID”
- Incidente “No user for stripeCustomerId”
Zatim izložite metrike:
- Stopa uspješnosti obrade webhookova
- Prosječna latencija obrade webhookova
- Broj korisnika u
past_duestanju - Broj webhook retry-jeva
Ako želite praktičan setup za logove, metrike i tracing, koristite naš vodič za observability.
# Korak 8: Sigurnosni aspekti (produkcijski checklist)#
Billing dotiče identitet, novac i kontrolu pristupa. Tretirajte ga kao sigurnosno kritičnu površinu.
Ključna sigurnosna pravila#
| Rizik | Loš ishod | Mitigacija |
|---|---|---|
| Neverificirani webhookovi | Napadač si dodijeli pristup | Uvijek validirajte Stripe potpis |
| Povjerenje u status s klijenta | Korisnici zaobiđu paywall | Pristup ograničite server-side iz DB-a |
| Curenje secret ključeva | Potpuni kompromis naplate | Koristite server-only env varove, rotirajte ključeve |
| Previše permisivan Portal | Neželjene promjene planova | Ograničite Portal konfiguraciju |
| Manipulacija metapodacima | Pogrešno mapiranje usera | Ne vjerujte isključivo client metadata; provjerite ownership customera |
| SSRF i injection | Lateralno kretanje | Validirajte inpute; least privilege |
Također provedite širu provjeru prema našem sigurnosnom checklistu za web aplikacije, posebno oko rukovanja tajnama i granica autorizacije.
⚠️ Upozorenje: Ne izlažite Stripe secret ključeve u Next.js public env varijablama. Sve s prefiksom
NEXT_PUBLIC_može završiti u pregledniku.
# Korak 9: Obrasci za “hardening” u produkciji#
Kad osnove rade, ovi obrasci uklanjaju zadnjih 10% billing boli.
1) Reconciliation job#
Webhookovi mogu zakazati zbog privremenih ispada. Pokrenite dnevni job za usklađivanje:
- Query korisnika s “aktivno-izgledajućim” stanjima
- Dohvat Stripe pretplate po spremljenom ID-u
- Popravak neslaganja
Neka bude friendly prema rate limitovima tako da radite batching i dohvaćate samo nedavne promjene.
2) Snapshot prava pristupa za ultra-brza čitanja#
Ako svaki request provjerava prava pristupa i DB je “vruć”, spremite EntitlementSnapshot JSON koji se ažurira obradom webhookova.
To olakšava edge middleware i caching strategije, jer čitate jedan jedini redak.
3) Admin alati#
Dajte podršci siguran način da:
- Vidi link na Stripe customera
- Vidi trenutni status i datum obnove
- Pokrene “resync from Stripe” za jednog korisnika
To smanjuje “ne možemo reproducirati” cikluse i spušta vrijeme podrške po tiketu.
# Ključne poruke#
- Koristite Stripe Checkout za kupnju i Billing Portal za promjene; izbjegnite gradnju vlastitog UI-ja za upravljanje planovima osim ako vam stvarno treba.
- Verificirajte webhookove koristeći raw request body, obrađujte ih idempotentno preko unique constrainta na
stripeEventIdi tretirajte bazu kao jedini izvor istine za pristup. - Normalizirajte Stripe podatke pretplate u mali lokalni model, pa prava pristupa računajte iz
status,priceIdi period datuma. - Testirajte lokalno sa Stripe CLI-jem provjeru potpisa, retry-jeve, duplikate i evente izvan redoslijeda prije isporuke.
- Dizajnirajte za kvar: reconciliation jobovi, alerti za nemapirane cijene i jasno rukovanje
past_duete otkazivanjem na kraju perioda. - Primijenite sigurnosne osnove: higijena secret ključeva, stroga autorizacija i nikad povjerenje u client-side billing stanje.
# Zaključak#
Čvrsta implementacija Next.js Stripe pretplata je većinom pitanje ispravnosti u realnim uvjetima: retry, parcijalni kvarovi i promjene stanja koje niste direktno okinuli. Ako kontrolu pristupa usidrite u bazi podataka, webhook obradu držite verificiranom i idempotentnom te pretplate mapirate u eksplicitna prava pristupa, izbjeći ćete najskuplje billing bugove.
Ako želite da Samioda pregleda vašu billing arhitekturu ili implementira kompletan sustav pretplata uz observability i sigurnosno hardening, kontaktirajte nas i pomoći ćemo vam da brže isporučite postav spreman za produkciju.
FAQ
Više iz kategorije Web razvoj
Sve →Objašnjene strategije cacheiranja u Next.js-u: SSR, SSG, ISR, Route Cache i SWR
Praktičan vodič kroz Next.js strategije cacheiranja u eri App Routera — kako se SSR, SSG, ISR, Route Cache, Data Cache i SWR uklapaju u cjelinu, uz tablice za odluke, primjere koda i česte zamke poput zastarjelih auth i tenant podataka.
Next.js multitenant SaaS arhitektura: modeli tenancije, rutiranje, autentikacija i izolacija podataka (Vodič za 2026.)
Praktičan vodič za Next.js multitenant SaaS arhitekturu: modeli tenancije, tenant-aware rutiranje uz App Router i middleware, obrasci autentikacije te učvršćivanje izolacije podataka kako bi se spriječila curenja.
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.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Next.js multitenant SaaS arhitektura: modeli tenancije, rutiranje, autentikacija i izolacija podataka (Vodič za 2026.)
Praktičan vodič za Next.js multitenant SaaS arhitekturu: modeli tenancije, tenant-aware rutiranje uz App Router i middleware, obrasci autentikacije te učvršćivanje izolacije podataka kako bi se spriječila curenja.
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.
Objašnjene strategije cacheiranja u Next.js-u: SSR, SSG, ISR, Route Cache i SWR
Praktičan vodič kroz Next.js strategije cacheiranja u eri App Routera — kako se SSR, SSG, ISR, Route Cache, Data Cache i SWR uklapaju u cjelinu, uz tablice za odluke, primjere koda i česte zamke poput zastarjelih auth i tenant podataka.