import { prisma } from "@/lib/db";
import { CardMetrics } from "@/types/dashboard";
// Query raw direct în Postgres pentru a evita overhead-ul Prisma pe 200k+ rânduri
export async function getClientPerformance(
clientId: string,
startDate: Date,
endDate: Date
): Promise<CardMetrics[]> {
return await prisma.$queryRaw<CardMetrics[]>`
SELECT
DATE_TRUNC('day', date) as period,
SUM(impressions)::int as total_impressions,
SUM(clicks)::int as total_clicks,
ROUND(SUM(spend)::numeric, 2) as total_spend,
CASE
WHEN SUM(clicks) > 0 THEN ROUND((SUM(spend) / SUM(clicks))::numeric, 2)
ELSE 0
END as avg_cpc
FROM "CampaignMetric"
WHERE "clientId" = ${clientId}
AND date >= ${startDate}
AND date <= ${endDate}
GROUP BY DATE_TRUNC('day', date)
ORDER BY period DESC;
`;
}În ultimele 3 luni am construit de la zero un dashboard intern pentru o agenție de marketing din Cluj. Aveau nevoie să agreneze date din Meta Ads, Google Ads și TikTok Ads pentru vreo 40 de clienți activi, fără să mai plătească mii de euro pe lună pe soluții SaaS rigide. Am mers pe Next.js (App Router), Prisma, PostgreSQL (pe Supabase) și NextAuth.js.
Nu a fost un proiect gigantic, dar a avut suficiente provocări tehnice încât să merite o discuție despre arhitectură și decizii luate pe parcurs.
Stack-ul și de ce am renunțat la no-code
Băieții foloseau înainte Looker Studio legat la diverse wrappere de API-uri. Problema? Se încărca în 12-15 secunde per pagină și se bloca constant când încercau să compare perioade mari de timp.
Am ales Next.js 14 cu App Router pentru Server Components — e absolut perfect când vrei să randezi tabele mari de date pe server fără să trimiți megabiți de JavaScript în browser. Prisma ne-a ajutat să mișcăm repede schema de date (care s-a schimbat de cel puțin 10 ori în prima lună), iar Postgres ne-a oferit flexibilitatea necesară pe partea de agregări.
Am economisit cam 40% din timpul de dezvoltare folosind Shadcn UI pentru componente. Nu a trebuit să pierdem vremea redesenând la infinit dropdown-uri și modale.
Unde ne-am lovit cu capul de prag
Prima problemă gravă a fost Prisma la query-uri de agregare. Când am ajuns la peste 200.000 de rânduri de date zilnice de ad-uri, query-urile ORM-ului standard au început să scârțâie. prisma.dailyMetric.groupBy() consuma memorie aiurea și dura peste 3 secunde.
Soluția a fost să renunțăm la abstracție acolo unde contase și să scriem query-uri SQL naive cu $queryRaw. Am adăugat indexuri compuse pe (clientId, date) și am scăzut timpul de execuție de la 3.2 secunde la sub 180ms.
A doua chestie neplăcută: NextAuth.js v5 (Auth.js). TypeScript support-ul pe middleware și extinderea tipurilor de sesiune pentru role-based access control (RBAC) a fost un adevărat chin. Am pierdut 3 zile doar să fac tiparea de Session să recunoască corect user.role și user.organizationId fără să primesc erori la build-ul de producție.
Trade-off-urile reale după 3 luni
Soluția custom bate orice SaaS la viteză și flexibilitate, dar vine cu costuri de mentenanță.
- Ce a ieșit bine: Rapoartele lunare se generează instant (PDF export direct din React server-side). Echipa economisește cam 5 ore pe săptămână per Account Manager.
- Ce e nasol: API-ul de la Meta schimbă versiunile la fiecare câteva luni. Când schimbați un endpoint în Graph API, noi trebuie să actualizăm cron job-urile de sync. Dacă erai pe un SaaS, era problema lor; acum e problema mea.
Din punct de vedere financiar, proiectul s-a amortizat în 3 luni doar din economiile abonamentelor la uneltele terțe, însă necesită un minim de 2-4 ore de mentenanță lunară pentru pipeline-urile de date.
Voi cum abordați agregarea de date din API-uri terțe când ORM-ul devine lent? Mergeți direct pe SQL nativ sau folosiți query buildere gen Kysely?