Live — the site you are reading is the system described here.
Technology
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.
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.