Under the hood

How this site is built —
and why.

I built this site with Claude Code as my pair programmer. I set the architecture, made the calls and reviewed every change; Claude Code wrote the code. These are the decisions behind it — the same way I'd document a production system.

Lighthouse accessibility, best practices and SEO
100
Lighthouse accessibility, best practices and SEO
Servers to patch — every page is pre-built
0
Servers to patch — every page is pre-built
Testimonials verified verbatim at build time
26
Testimonials verified verbatim at build time
Data file per kind of content — pages are templates
1
Data file per kind of content — pages are templates

How I worked with Claude Code

  • I set the direction, the information architecture and the design principles.
  • Claude Code wrote the code; I reviewed every change before it was committed.
  • Every change was checked in a real browser, on desktop and on a phone, before it shipped.
  • Every commit message explains why, not just what — the history reads like a design log.

The decisions

  1. Decision 01

    Content as typed data, pages as templates

    Context
    During a job search the content changes almost daily — new numbers, reworded stories, extra roles.
    Decision
    All content lives in typed TypeScript data files (profile, case studies, testimonials, role value, articles). Pages are templates that read from them.
    Consequence
    Changing a sentence never touches layout, and the compiler catches a missing field before it reaches the site.
  2. Decision 02

    Trust, enforced by the build

    Context
    Testimonial quotes appear on several pages. A paraphrased quote presented as verbatim would undermine everything else.
    Decision
    Every quote is looked up in the recommendation data when the site is built. If it isn't an exact excerpt, the build fails.
    Consequence
    A misquote can never be published — and attribution (title, company, relationship) is filled in automatically.
    src/lib/career.tsView on GitHub ↗
    /** Attribute a verbatim excerpt to its recommendation; throws if not found. */
    export function attributeQuote(excerpt: string) {
      const t = testimonials.find(
        (t) => !t.hidden && normalise(t.paragraphs.join(" ")).includes(normalise(excerpt)),
      );
      if (!t) throw new Error(`Quote is not a verbatim excerpt of any testimonial: "${excerpt}"`);
      return { excerpt, title: t.title, company: t.company, relationship: relationshipLabels[t.relationship] };
    }
  3. Decision 03

    Privacy by design

    Context
    The LinkedIn export of recommendations includes every recommender's name.
    Decision
    A script generates the testimonial data without names. The source file is git-ignored, and when it was once committed, the repository history was cleaned before the repo went public.
    Consequence
    No recommender's name exists anywhere in the public code or on the site.
  4. Decision 04

    Everything pre-built, nothing left to chance

    Context
    A portfolio needs to be fast, cheap to run and impossible to break at 2am.
    Decision
    Every page — including each case study — is generated at build time. Unknown URLs return a 404 instead of being rendered on demand.
    Consequence
    Pages load instantly from the edge, there is no server to patch, and there is nothing to exploit at runtime.
    src/app/experience/[slug]/page.tsxView on GitHub ↗
    // Only the roles listed in portfolio.ts exist; anything else is a 404.
    export const dynamicParams = false;
    
    export function generateStaticParams() {
      return experience.map((j) => ({ slug: j.slug }));
    }
  5. Decision 05

    Drafts that can't leak

    Context
    Articles need a draft stage with editor's notes, but a half-finished draft must never reach the live site.
    Decision
    Drafts are visible only in local development. Production builds exclude them from pages, the sitemap and the navigation.
    Consequence
    I can draft in public code without publishing — and an article goes live only when I deliberately mark it published.
    src/data/insights.tsView on GitHub ↗
    export const showDrafts = process.env.NODE_ENV !== "production";
    
    export const visibleArticles = articles
      .filter((a) => a.status === "published" || showDrafts)
      .sort((a, b) => b.date.localeCompare(a.date));
  6. Decision 06

    Two languages, one answer

    Context
    The MDM playground runs in TypeScript in the browser; data teams work in Python. Two implementations of the same logic can quietly drift apart.
    Decision
    Both engines read one shared data file. The Python results are committed, and the site build re-runs the TypeScript engine and compares every pair score, golden record and household.
    Consequence
    If the two engines ever disagree, the build fails — so the live demo and the Python notebook always tell the same story.
    src/app/lab/mdm/two-ways/page.tsxView on GitHub ↗
    // Parity check, at build time: the TypeScript engine must reproduce the
    // committed Python results exactly, or the site does not build.
    const typescript = run(sampleRecords, defaultWeights, defaultThresholds, defaultSurvivorship);
    if (JSON.stringify(typescript) !== JSON.stringify(parity)) {
      throw new Error(
        "The TypeScript and Python MDM engines disagree. Run `python python/export_parity.py`, then fix whichever engine changed.",
      );
    }
  7. Decision 07

    Accessibility as an acceptance criterion

    Context
    A site about leadership should work for everyone — including people using screen readers or keyboards.
    Decision
    Every page was audited with Google Lighthouse, and the build was not considered done until the findings were fixed: list semantics, accessible names, colour contrast and link styling.
    Consequence
    Lighthouse scores of 100 for accessibility, best practices and SEO across the main pages.
  8. Decision 08

    Brand assets generated in code

    Context
    Links shared on LinkedIn, Teams or WhatsApp should show a branded preview, not a bare URL.
    Decision
    The social preview card and the site icons are generated in code at build time, with the site's own typeface bundled from an npm package rather than fetched over the network.
    Consequence
    Every share looks polished, and the build never depends on a third-party font service being up.

The stack

  • Next.js 16 (App Router)
  • React 19
  • TypeScript
  • Tailwind CSS v4
  • next/og
  • Vercel
  • GitHub
  • Claude Code
Read the source ↗

Contact

Let's talk.

I'm based in London, UK. Whether it's a leadership role, an advisory engagement or a transformation that needs shaping — my inbox is open.

srini.vankee@gmail.com