
Personal Portfolio
Open SourceA full-stack portfolio built with Next.js, DDD, and Clean Architecture in a Turborepo monorepo.
- TypeScript
- React
- Next.js
- Node.js
- PostgreSQL
- Tailwind CSS
- CI/CD
- Prisma
- Supabase
After six years in software engineering, this portfolio was the first chance to own an entire product — from architecture to deployment. Built to go beyond LinkedIn for an international audience, to target opportunities abroad, and to demonstrate that Domain-Driven Design and Clean Architecture hold up on a solo greenfield project — not just in teams where the process is enforced.
The Constraints
The architecture was self-imposed. With no team, no deadline, and no external pressure, the constraint came from a deliberate decision: treat this as a real product with an MVP scope, a post-MVP roadmap, and no shortcuts in the domain layer.
Three requirements shaped every technical decision:
- Performance — the site had to score well on Lighthouse; a slow portfolio sends the wrong message
- Content without code changes — updating project entries or experience data couldn't require a deploy; all content is driven by a seeded database and rendered as markdown
- i18n at every layer — supporting English, Portuguese, and Spanish meant solving internationalization at the domain level, not patching it into the UI
The visual design was built entirely in Figma by Milena Kawai, a designer friend who delivered the full specification from scratch.
Engineering Process
Managed the entire backlog with Task Master, divided into sprints tracked as GitHub milestones. GitHub Projects provided a Kanban board for issue tracking; each issue followed a structured template with context, acceptance criteria, relevant files, and dependencies. Custom labels organized work by sprint tag, priority, and type.
The project shipped five numbered PRDs — one per architecture layer:
- Sprint 0 — domain foundation (
core): Either pattern, Value Objects, entities, repository interfaces - Sprint 1 — application layer: use cases, ports, DTOs
- Sprint 2 — infrastructure: Prisma repositories, Supabase gateway, DI container
- Sprint 3 — public site: Next.js App Router, SSG, i18n routing, UI components
- Sprint 4 — CI/CD: type checking, linting, and test suite on GitHub Actions
A dedicated accessibility sprint resolved 88 WCAG issues, followed by a Lighthouse-driven performance pass that targeted the critical bundle, RSC preload hints, and LCP image loading.
A docs/ folder holds 12 numbered architecture documents — bounded contexts, validation strategy, i18n approach, testing strategy, code patterns, and a domain glossary — written as the system was built, not after.
Architecture
The domain is organized into three bounded contexts — portfolio (projects, experiences, skills, profile), identity (authentication and user), and contact (message sending) — each with its own entities, value objects, and repository interfaces, sharing only the Shared Kernel.
Each package has a single, enforced responsibility:
core— domain model; zero framework dependencies; entities, value objects, Either pattern, repository interfacesapplication— use cases and ports; depends only oncore; no Prisma, no HTTPinfra— concrete implementations; the only layer that imports Prisma and Supabaseui— shared React components; split intoView(display) andControl(interactive) categoriesutils— pure TypeScript utilities:Validator, formatters, browser hooks; no React dependency
Portfolio Site
Server Components call use cases directly at build time — no REST API layer exists between the domain and the generated HTML. For a content-driven static site, an HTTP boundary would be pure overhead.
Internationalization is a domain concern: LocalizedText is a value object in core. The site renders in English, Portuguese, and Spanish — resolved at the domain layer before any React component touches the data.
Every page has a generateMetadata export with localized title, description, and openGraph fields. Project pages derive their OG data directly from domain entities — title, caption, and cover image — so metadata is never out of sync with content. A custom OG image route built with next/og on the Edge Runtime generates branded 1200×630 cards per page, locale, and project.
Project detail routes are driven by Slug — a value object in core — with generateStaticParams resolving every published project slug across all three locales at build time. No slug, no route.
The contact form runs against rate limiting via Upstash Redis and delivers email through Resend — both behind port interfaces, swappable and testable without touching infrastructure.
This portfolio is the first public technical presence I've built and owned entirely — from domain model to deployment pipeline. The admin app for content management and the blog are scoped as post-MVP, keeping the current site focused and shippable.
Technical Highlights
- No API layer — Server Components consume use cases at build time; a static site has no need for an HTTP boundary between domain and HTML
- ESLint-enforced dependency direction — layer violations are caught at lint time, not review time; the boundary is mechanical, not a convention
- Either pattern — no exceptions thrown for domain errors;
Left<ValidationError>propagates through use cases to the UI, making all error paths explicit and testable - LocalizedText as a VO — i18n is a domain concern; components receive resolved strings, not translation keys
- Accessibility as a sprint — 88 WCAG findings tracked, scoped, and shipped as individual issues with acceptance criteria
- Lighthouse-driven performance — critical bundle trimmed by lazy-loading Zod-heavy forms, removing unnecessary
'use client', and preloading the LCP image withfetchpriority=high - SEO and Open Graph — every page exports localized
generateMetadata; an Edge Runtime/ogroute generates branded 1200×630 cards per page, locale, and project usingnext/og; OG data is sourced from domain entities, never from static strings
Technologies
- Next.js — App Router with SSG;
generateStaticParamsgenerates all localized routes at build time - Turborepo — monorepo orchestration with five shared packages and remote caching on Vercel
- TypeScript — strict mode across all packages;
anyis disallowed - Prisma — ORM and migration layer, isolated to
packages/infra - Supabase — PostgreSQL database and JWT-based authentication
- Tailwind CSS — shared design tokens via
packages/tailwind-config - next-intl — locale routing and message resolution for EN, PT-BR, and ES
- Vitest — unit and integration tests across all packages; ~100 test files
- Upstash Redis — serverless rate limiting on the contact form
- Resend — transactional email for contact form submissions
- Vercel — deployment with Turborepo remote cache
Other projects

B2B E-Commerce Platform
Full-stack B2B platform for construction materials built with Clean Architecture, React, and Node.js — from greenfield to production.
- TypeScript
- React