Personal portfolio and technical showcase built with Next.js 16 App Router, Sanity CMS, and full i18n support.
This is the source code for victormts.dev, the personal portfolio of Victor M. Santos. It serves a dual purpose: presenting projects, writing, and contact information to clients and recruiters, while demonstrating Next.js App Router patterns in a real production context — i18n with next-intl, ISR via Sanity webhooks, React Server Components, and a design system built on Tailwind v4 OKLCH tokens.
The site is fully bilingual (English and Brazilian Portuguese), statically generated, and deployed on Vercel with incremental revalidation.
- 🌍 Internationalisation —
next-intlv4 withenandpt-BRlocales,hreflangon all pages, bilingual Sanity content viacoalesce()GROQ fallbacks - 📝 CMS — Sanity v6 as headless CMS, Studio embedded at
/z-admin/studio, ISR revalidation triggered by Sanity webhooks - ⚡ Performance — Static generation + ISR via cache tags (
post,lab), React Compiler enabled, Vercel Analytics - 🎨 Design system — Monochromatic OKLCH palette, fluid
clamp()type scale, Instrument Serif + Outfit + JetBrains Mono, dark/light mode vianext-themes - 🔍 SEO — JSON-LD structured data (Person, WebSite, BlogPosting, BreadcrumbList), OG/Twitter cards, dynamic sitemap,
/llms.txtfor AI crawlers - 📬 Contact form — Resend transactional email, Zod v4 validation, in-memory rate limiting (3 req/hour per IP), honeypot anti-bot field
- 🧪 Lab section — YouTube Data API v3 with full pagination and duration batching, project roadmap, featured project driven by a Sanity settings singleton
- 🥚 Easter eggs — Konami code,
/voidinteractive terminal, console art, logo click sequence
| Technology | Version | Role |
|---|---|---|
| Next.js | 16 | Framework — App Router, SSG, ISR, API routes |
| React | 19 | UI library, React Compiler enabled |
| TypeScript | 5 | Type safety across client and server |
| Tailwind CSS | v4 | Utility-first CSS with OKLCH design tokens |
| Sanity | v6 | Headless CMS with embedded Studio |
| next-intl | v4 | i18n — routing, translations, locale detection |
| motion | v12 | Animations (Framer Motion v12) |
| Resend | — | Transactional email delivery |
| React Email | — | Email templates as React components |
| Zod | v4 | Schema validation for forms and API routes |
| Radix UI | — | Accessible UI primitives |
| nuqs | — | Type-safe URL state management |
| Biome | v2 | Linting and formatting (replaces ESLint + Prettier) |
| pnpm | — | Package manager |
| Vercel | — | Hosting, edge network, analytics |
src/
├── app/
│ ├── [locale]/ # All user-facing routes (en / pt-br)
│ │ ├── page.tsx # Home
│ │ ├── works/ # Devlog post list + individual post pages
│ │ ├── lab/ # Lab — featured project, roadmap, YouTube episodes
│ │ └── void/ # Easter egg — interactive terminal
│ ├── api/
│ │ ├── contact/route.ts # Contact form — Resend integration + rate limiting
│ │ └── revalidate/route.ts # ISR webhook — Sanity triggers revalidateTag
│ ├── llms.txt/route.ts # Plain-text description served to AI crawlers
│ └── z-admin/studio/ # Sanity Studio embedded at /z-admin/studio
│
├── components/
│ ├── lab/ # Lab-specific: roadmap, episode player, build status
│ ├── easter-eggs/ # Console, Konami, logo click, glitch-404
│ └── ui/ # shadcn/ui primitives
│
├── emails/ # React Email templates (notification + confirmation)
├── i18n/ # next-intl routing, request config, typed navigation
├── lib/
│ ├── sanity/ # Client, GROQ queries, TypeScript types, image helper
│ └── schemas/ # Zod schemas (contact form)
└── proxy.ts # next-intl middleware — handles i18n routing
| Variable | Scope | Description |
|---|---|---|
NEXT_PUBLIC_SANITY_PROJECT_ID |
Public | Sanity project ID |
NEXT_PUBLIC_SANITY_DATASET |
Public | Dataset name — production or development |
SANITY_API_TOKEN |
Server only | Read token for server-side Sanity fetches |
RESEND_API_KEY |
Server only | API key for contact form email delivery |
YOUTUBE_API_KEY |
Server only | YouTube Data API v3 key for Lab playlist |
REVALIDATE_SECRET |
Server only | Shared secret for the Sanity ISR webhook |
NEXT_PUBLIC_SITE_URL |
Public | Canonical base URL, e.g. https://victormts.dev |
The project uses five schemas defined in sanity.config.ts:
| Schema | Type | Description |
|---|---|---|
post |
Document | Devlog posts — title, slug, tags, reading time, Portable Text body with code blocks |
labProject |
Document | Lab projects — name, type, roadmap, tech stack, GitHub repo, live URL |
roadmapStep |
Object | Embedded in labProject — phase number, title, description, status |
labStatus |
Singleton | Free-text status of current Lab work, auto-dated on save |
labSettings |
Singleton | Featured project reference + YouTube playlist ID |
On every document publish, Sanity triggers a webhook to POST /api/revalidate?secret=<REVALIDATE_SECRET>. The endpoint calls revalidateTag("post") or revalidateTag("lab") based on _type, invalidating only the affected cache.