01
Custom web applications
I don't start from the screens. I start from who the actors are, what they may see and what happens when they get it wrong: the data model, the permissions and — last — the interface follow from there.
- Next.js
- React
- TypeScript
- Tailwind
- Prisma
- PostgreSQL RLS
- i18n
- E2E tests
Permissions live in the database
In a multi-tenant platform the real question is not how the client list looks, but what happens the day a query forgets the company filter. If the check lives only in the code, that day one customer sees another customer's data and nobody notices.
So isolation goes into PostgreSQL as Row-Level Security policies, and the application connects with a role that has no BYPASSRLS. Code can be wrong; the database cannot. The permission becomes readable, revocable data instead of a branch scattered across thirty files.
Rendered on the server, state only where needed
Next.js App Router: pages arrive already drawn — which matters for SEO, because crawlers don't run JavaScript, and for perceived speed. The client only takes over the parts that genuinely need to react.
Preferences that change the very first pixel (menu open or closed, theme, language) live in cookies read by the server while rendering, not in localStorage: otherwise the first paint is always the wrong one and the page jumps on every load.
Multilingual without rebuilding the site
i18n with next-intl: clean URLs per language, reciprocal hreflang, content actually translated rather than Italian strings sitting under a different flag. When a translation is missing we decide explicitly whether to show the fallback or hide the page.
Portfolio
What I did on this, project by project.
SOS Bollette
Multitenant CRM for a network of energy agencies
- A three-level model — network, company, person — where the tool perimeter is data owned by the network: a menu entry the partner doesn't have is never drawn.
- Database-level isolation through RLS policies and an application role without BYPASSRLS; the head office is a company with a different kind, not a privileged code path.
- Passwordless access: email links and WebAuthn passkeys, with sessions that remember which company you are working on.
- Full Italian tax profile (VAT number, tax code, SDI, certified email), company sheet read-only for operators who are not owners.
- Next.js 16
- React 19
- Prisma 7
- PostgreSQL RLS
- Tailwind v4
- shadcn/ui
- next-intl
- Playwright
BigFishIta
This site: showcase, blog and private area
- Cinematic canvas hero: 300 WebP frames following the pointer, progressive idle preloading and a fallback for people who ask for reduced motion.
- Bilingual blog on PostgreSQL (invariant article plus per-language translations), block editor in the private area with SEO fields, tags, scheduling and XSS sanitising.
- The /admin area behind better-auth with mandatory 2FA and a forced password change on first login.
- Next.js 16
- Drizzle
- PostgreSQL
- better-auth
- next-intl
- Docker
Orbitas
Customer portal of the private cloud
- Turborepo monorepo with four applications (customer portal, operator console, API, marketing site) and shared packages for types, API client and UI library: one contract, used by everyone.
- Compliance dashboard for the customer: security score, uptime against target, incident history and a downloadable monthly report.
- Next.js
- TypeScript
- Turborepo
- Tailwind
- Radix
- Zod
- next-intl
Need this?
Tell me the problem and I'll tell you how I would tackle it — and if it isn't worth doing, I'll tell you that too.
Let's talk