← All case studies← Workshop

Personal project

This Portfolio — adityarawal.in

2026

Live — the site you are reading is the system described here.

Technology

Java 21Spring BootSpring AIPostgreSQLFlywayNext.jsTypeScriptDockerCloudflare Pages

Overview

A production web application rather than a static brochure: a statically exported Next.js frontend backed by a Spring Boot API that handles email-verified résumé requests, bot protection, rate limiting, privacy-conscious analytics, an admin dashboard, and an AI career assistant grounded in an approved knowledge base.

My role

  • Owned the product scope, architecture, and every design decision, working phase by phase with an explicit review gate before each next phase.
  • Built with AI pair-programming (Claude Code) under that review process — each phase had to pass lint, tests, and a production build before sign-off.
  • Chose and operate the hosting stack: Cloudflare Pages, Render, Neon PostgreSQL, Resend, and Cloudflare DNS.

Architecture

The frontend is a static Next.js export served from Cloudflare Pages, with no server-side rendering in production. All dynamic behavior lives in a Spring Boot (Java 21) API running as a Docker container on Render, with a Neon PostgreSQL database whose schema is versioned with Flyway migrations that run on startup. The API integrates Resend for transactional email, Cloudflare Turnstile for bot verification, and the Google Gemini API through Spring AI for the assistant.

Portfolio system architectureA visitor browser loads a static Next.js site from Cloudflare Pages and calls a Spring Boot API hosted on Render. The API uses Neon PostgreSQL, Resend for email, Cloudflare Turnstile for bot verification, and the Google Gemini API for the AI assistant.static filesREST / JSONVisitorBrowserCloudflare PagesStatic Next.js exportSpring Boot APIRender · Docker · Java 21Neon PostgreSQLFlyway-migrated schemaResendVerification & résumé emailCloudflare TurnstileServer-side token checkGoogle Gemini APIGrounded assistant replies
The frontend is static files only; every dynamic behavior lives behind the Spring Boot API.

Engineering challenges

  • Email syntax is not proof of ownership — the résumé flow sends a one-time link that expires after 20 minutes, is single-use, is stored only as a hash, and is capped at 5 verification attempts.
  • A bot-protection widget alone proves nothing — the Turnstile token is verified server-side before a request is accepted, and the feature degrades gracefully when keys are not configured.
  • Keeping an LLM honest about a real person's career — the assistant answers only from an approved knowledge base, is instructed to abstain rather than guess, and has no tool, SQL, filesystem, or URL-fetch access.
  • A production-only failure: résumé requests worked from the apex domain but failed from the www host, traced to the API's CORS allow-list missing the second origin.

Decisions & trade-offs

  • Static export plus a separate API, instead of a server-rendered Next.js app — the frontend can be hosted anywhere for free, and all server logic stays in one Spring Boot codebase.
  • Free-tier infrastructure throughout — the domain is the only paid item — accepting the limits that come with it.
  • Per-IP rate limits on public endpoints, plus separate daily caps on the assistant (20 messages per IP, 50 globally) so a free AI quota cannot be drained.
  • The AI model is configuration, not code — when the original Gemini model became access-restricted, the default was switched without touching the assistant's logic.
  • No third-party theming or dialog libraries — a small hand-rolled theme toggle and accessible dialog keep the dependency surface minimal.

Testing & quality

The backend has unit and integration tests covering the résumé verification flow, assistant chat and its rate limits, IP hashing, the security and CORS configuration, error handling, and the admin endpoints. The frontend is gated on ESLint, TypeScript type-checking, and a full static production build.

Outcomes & lessons

  • Live at adityarawal.in with the résumé flow, bot protection, and AI assistant confirmed working end to end against production.
  • Client names and internal product names are deliberately withheld across the site and the assistant's knowledge base — a privacy decision applied consistently rather than page by page.