// Exemplu de pattern pe care l-am folosit pentru a evita N+1 în rapoartele de campanii
// Folosind Prisma, dar filtrând inteligent
export async function getCampaignStats(agencyId: string) {
const campaigns = await prisma.campaign.findMany({
where: { agencyId },
include: {
_count: {
select: { ads: true }
}
}
});
// Calculăm agregatele direct în memorie sau via un query separat pentru viteză
const stats = await prisma.dailyStat.groupBy({
by: ['campaignId'],
_sum: {
spend: true,
clicks: true,
conversions: true
},
where: {
campaignId: { in: campaigns.map(c => c.id) }
}
});
return campaigns.map(c => ({
...c,
stats: stats.find(s => s.campaignId === c.id) || { _sum: { spend: 0, clicks: 0, conversions: 0 } }
}));
}Recent am livrat un dashboard intern pentru o agenție de marketing cu vreo 25 de angajați. Oamenii ăștia își petreceau jumătate din viață copiind date din Facebook Ads și Google Ads în niște spreadsheet-uri gigantice care se încărcau în 30 de secunde. Am avut la dispoziție 3 luni să transform haosul ăsta în ceva utilizabil. Am mers pe stack-ul meu de încredere: Next.js (App Router), Prisma și NextAuth.
După 90 de zile de dev, am tras linie și am vrut să dau mai departe câteva chestii concrete pe care le-am învățat, că poate vă loviți și voi de ele.
De ce am ales stack-ul ăsta și unde m-am păcălit
Am ales Next.js pentru că aveam nevoie de Server Components. În marketing, datele sunt multe și grele. Să tragi 50MB de JSON pe client ca să randezi un grafic e sinucidere curată. Cu RSC (React Server Components), am mutat tot procesul de agregare pe server, lângă baza de date. Am reușit să scădem timpul de încărcare a unui raport lunar de la 2 minute (cât dura manual în Excel) la sub 2 secunde.
Prisma e dragostea mea veche, dar are un trade-off pe care mulți îl ignoră: N+1 query-urile sunt extrem de ușor de ignorat dacă nu ești atent la include. La un moment dat, aveam un query care făcea 150 de request-uri la DB pentru o singură listă de campanii. Am rezolvat asta cu un join manual via $queryRaw pentru secțiunile critice, restul au rămas pe ORM. Am economisit cam 40% din timpul de execuție pe query-urile complexe după optimizarea asta.
NextAuth și bătăile de cap cu Refresh Tokens
NextAuth e super dacă vrei doar „Login with Google”. Dar când agenția are nevoie să se lege la API-ul de Google Ads în numele userului, lucrurile devin dubioase. Gestionarea refresh token-urilor în baza de date via NextAuth e un pic „clunky”. Am pierdut vreo 4 zile bune încercând să prind momentul în care token-ul expiră fără să forțez userul să dea logout.
Sfatul meu: dacă aveți nevoie de acces persistent la API-uri externe, scrieți-vă propria logică de token management separat de sesiunea de auth a userului. E mai curat și nu riști să bușești login-ul dacă API-ul extern e jos.
UI: Shadcn/ui versus biblioteci „grele”
Am mers pe shadcn/ui (Radix + Tailwind). Arată brici și ai control total. Totuși, trade-off-ul e real: scrii mult mai mult cod de mână. Pentru un dashboard intern unde contează mai mult viteza de livrare decât dacă butonul are border-radius de 4 sau 8 pixeli, uneori o bibliotecă mai „opinioned” ca Mantine sau MUI te-ar putea scoate la liman mai repede. Eu am ales calea grea pentru că voiam un bundle size mic și flexibilitate pe termen lung, dar am simțit efortul suplimentar în a treia lună când trebuia să termin tabelele complexe cu filtrare.
Ce am câștigat după 3 luni
La final, am automatizat raportarea pentru 80 de clienți activi. Account managerii au estimat că salvează cam 15 ore pe săptămână, timp pe care înainte îl pierdeau cu copy-paste.
Cel mai important task n-a fost codul, ci să înțeleg cum gândesc ei datele. Degeaba e codul curat dacă userul nu înțelege de ce ROAS-ul (Return on Ad Spend) e calculat diferit față de ce vede el în platforma Facebook.
Voi ce folosiți pentru tool-uri interne? Mergeți pe low-code gen Retool sau preferați să scrieți totul de la zero pentru control maxim?