Skip to content
Sakchote
← Back to projects

Portfolio Website

A performance-engineered portfolio built on Next.js 15, self-hosted on a hybrid Kubernetes cluster with Cloudflare edge caching — achieving sub-15ms image loads from 300+ global PoPs.

Portfolio Website

Why Build From Scratch

Most developer portfolios are either template-based or over-engineered. This one is purpose-built to demonstrate real engineering decisions: a custom content system that scales without a CMS, performance optimizations that produce measurable results, and a deployment pipeline that runs through production-grade infrastructure I built and maintain myself.

Performance Engineering

Lighthouse audit — 100 Performance, 96 Accessibility, 96 Best Practices, 100 SEO. FCP 0.3s, LCP 0.6s, TBT 0ms, CLS 0.
Lighthouse audit — 100 Performance, 96 Accessibility, 96 Best Practices, 100 SEO. FCP 0.3s, LCP 0.6s, TBT 0ms, CLS 0.

Lighthouse audit — 100 Performance, 96 Accessibility, 96 Best Practices, 100 SEO. FCP 0.3s, LCP 0.6s, TBT 0ms, CLS 0.

Lighthouse audit — 100 Performance, 96 Accessibility, 96 Best Practices, 100 SEO. FCP 0.3s, LCP 0.6s, TBT 0ms, CLS 0.

Performance wasn't an afterthought — it drove architectural decisions from the start. The site went through a systematic optimization pass that targeted every layer of the stack: image pipeline, rendering strategy, font loading, animation performance, and CDN caching.

The biggest win was the image pipeline. The site originally shipped raw multi-megabyte PNGs directly to browsers. After enabling Next.js image optimization with sharp, images are now converted to WebP on demand and served through responsive breakpoints. Source images were batch re-processed from 47 MB of raw PNGs down to 25 MB of high-quality JPGs — using q95 deliberately because two lossy passes (source → sharp → WebP) compound quality loss.

Custom device sizes (640, 1080, 1920 — down from 8 defaults) reduce the number of cache variants, meaning the CDN warms faster and more visitors get cache hits. Combined with a 30-day minimumCacheTTL, this produces sub-15ms image loads from Cloudflare's edge after the first request.

Cloudflare CDN Configuration

Cache Rule — custom filter expression matching /_next/image* requests
Cache Rule — custom filter expression matching /_next/image* requests

Cache Rule — custom filter expression matching /_next/image* requests

Cache Rule — custom filter expression matching /_next/image* requests
Cache eligibility & TTL — respecting origin Cache-Control headers for 30-day edge caching
Cache eligibility & TTL — respecting origin Cache-Control headers for 30-day edge caching

Cache eligibility & TTL — respecting origin Cache-Control headers for 30-day edge caching

Cache eligibility & TTL — respecting origin Cache-Control headers for 30-day edge caching

Zero-Render Animation System

All 60fps visual effects bypass React's reconciliation entirely using useRef and direct DOM writes. The cursor-tracking glow on project cards, the parallax scroll on the hero background layers, and the contact form border glow all write directly to element.style — no useState, no virtual DOM diffs, no re-renders on every mouse move or scroll event. Passive scroll listeners ensure the main thread is never blocked.

Section-level animations use IntersectionObserver with a single CSS attribute toggle — when a section scrolls into view, a data-animate attribute fires, and CSS nth-child selectors handle the staggered reveal. The observer disconnects after firing once, so there's zero ongoing JavaScript cost for already-visible sections.

View Transitions

The site uses the View Transitions API for page navigations. When clicking a project card, the card image morphs into the detail page hero and the title smoothly transitions into the page heading — both coordinated through matching viewTransitionName attributes. Non-shared elements cross-fade with a tuned asymmetric timing (0.2s out, 0.3s in with 0.1s delay). The film grain overlay is excluded from transitions via viewTransitionName: none to prevent z-index artifacts.

Content Architecture

All portfolio content lives as typed TypeScript arrays — no CMS, no database, no API layer. A ContentBlock discriminated union type powers a rich content system that renders headings, paragraphs, captioned images with lightbox, photo galleries, and attachment lists with typed icons. Every entity (projects, awards, experience, education) uses this same system, and cross-references between entities are resolved by slug at render time.

This approach means content is version-controlled, type-checked at compile time, and statically generated — the entire site builds to static HTML with zero runtime data fetching. Adding a new project is just adding an object to an array.

Deployment Pipeline

The site deploys through the same hybrid Kubernetes cluster documented in the Homelab project. A three-stage Docker build produces a minimal Alpine image (~50MB) running as a non-root user. ArgoCD watches the repository — pushing a change to the Kubernetes manifest automatically triggers a rolling update with health checks. The container image is published to GitHub Container Registry with the git commit SHA baked into the footer for traceability.

Traffic flows through Cloudflare's edge (DDoS protection, WAF, SSL termination), into a Cloudflare Tunnel (outbound-only, no open ports), through Traefik on the cloud node, over the Tailscale mesh, and into the pod running on the Proxmox home node. The portfolio is its own proof that the infrastructure works — it serves production traffic through every layer of the stack.

Design Details

The visual language is intentionally restrained — a near-black palette (#0C0B0A base) with warm gold accents, DM Sans for UI text, and Instrument Serif for decorative headings. A full-viewport SVG film grain overlay (feTurbulence fractalNoise, opacity 0.035, mix-blend-mode overlay) adds tactile texture without any image file. Interactive elements use a magnetic cursor effect on CTAs and a radial glow that follows the mouse across cards.

Technical Highlights

  • -Lighthouse 100/96/96/100 — FCP 0.3s, LCP 0.6s, TBT 0ms, CLS 0
  • -Sub-15ms image loads via Cloudflare edge caching with custom cache rules for /_next/image*
  • -47 MB → 25 MB source images, auto-converted to WebP with reduced breakpoints for faster CDN warming
  • -Zero-render animation system — useRef + direct DOM writes for 60fps glow, parallax, and hover effects
  • -View Transitions API for seamless page navigations with morphing images and titles
  • -ContentBlock discriminated union — type-safe rich content without a CMS
  • -Three-stage Docker build producing a ~50MB Alpine image running as non-root
  • -GitOps deployment via ArgoCD with commit SHA traceability in the footer
  • -Full traffic path: Cloudflare Edge → Tunnel → Traefik → Tailscale → K3s pod (zero open ports)
  • -IntersectionObserver-driven section reveals with CSS-only staggered animations
  • -SVG film grain overlay, magnetic buttons, and cursor-tracking radial glow effects