Logo
Back
Personal Portfolio repository on GitHub

Personal Portfolio

Open Source

A 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 interfaces
  • application — use cases and ports; depends only on core; no Prisma, no HTTP
  • infra — concrete implementations; the only layer that imports Prisma and Supabase
  • ui — shared React components; split into View (display) and Control (interactive) categories
  • utils — 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 with fetchpriority=high
  • SEO and Open Graph — every page exports localized generateMetadata; an Edge Runtime /og route generates branded 1200×630 cards per page, locale, and project using next/og; OG data is sourced from domain entities, never from static strings

Technologies

  • Next.js — App Router with SSG; generateStaticParams generates all localized routes at build time
  • Turborepo — monorepo orchestration with five shared packages and remote caching on Vercel
  • TypeScript — strict mode across all packages; any is 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

  • TC Representações B2B platform — gated wholesale storefront for construction materials

    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
    View Project