Engineering

The tech stack

A senior engineer's rationale for what runs mepritam.dev — the choices, the trade-offs taken on purpose, and the patterns that keep it fast and maintainable.

Why write this down

A portfolio is a small system, but small systems are where you can afford to be principled: to make every choice on purpose and defend it. This is the architecture record for mepritam.dev: what runs it, why those tools, the trade-offs taken, and the patterns that keep it cheap to change. I wrote it the way I'd expect to defend a design in review.

The stack at a glance

Here is the stack in one view.

  • Framework: Next.js 15 (App Router): file-system routing, RSC, and first-class static export.
  • Language: TypeScript (strict): types as the cheapest test you can run.
  • Rendering: Static export (SSG): no server to run, attack, or pay for; CDN-fast.
  • Styling: Tailwind + design tokens: one source of truth; no CSS entropy.
  • Content: MDX + gray-matter, Zod-validated: content/template separation with a schema guard.
  • UI primitives: CVA + a small component layer: variants without a heavyweight UI framework.
  • Icons: lucide-react + inline brand SVGs: tree-shakeable; brand marks under our control.
  • SEO/LLM: Schema + llms.txt as code: machine legibility is a build artifact, not an afterthought.

Rendering: static export, and why

The whole site is output: "export": pre-rendered HTML to a CDN. For content that changes on deploy, not per request, SSG is the correct default, not a limitation.

  • Performance is structural, not tuned. There's no server round-trip or hydration-blocking data fetch on the critical path; TTFB is CDN latency.
  • The security surface is almost nil. No runtime, no server secrets, no request handlers to harden.
  • Operations are trivial. Rollback is a previous immutable build; there is no capacity to plan.

The trade-off is honest: no per-request personalization and a full rebuild to publish. For a portfolio those costs are ~zero, so SSG dominates SSR here.

The discipline it forces matters too: push interactive behaviour to the client edges (the tools, theme, contact form) and keep everything else static. A 90+ Lighthouse score on mobile in 2026 is the baseline I hold the build to.

TypeScript in strict mode: types as a test budget

strict: true is non-negotiable. On a one-person project the cheapest test that exists is the compiler. Frontmatter flows through Zod schemas (workFrontmatterSchema, etc.) so malformed content fails the build instead of shipping a broken page. Parse at the boundary, then trust the types. The type that comes out of Zod is the type the templates consume, so content and rendering can't disagree.

Styling: a token-driven system, not utility soup

Tailwind is the engine; design/tokens.ts is the source of truth. Colour, type hierarchy, spacing, radii, and motion live as tokens that tailwind.config.ts imports. Components compose utilities but never invent raw hex or one-off pixel values. Two consequences a senior reviewer cares about:

  • Theming is a variable swap. Light/dark is CSS custom properties; there is no duplicated "dark stylesheet" to drift.
  • The system resists entropy. New components inherit the scale instead of adding a 13th shade of grey. This is the difference between a design system and a pile of class names.

Variants use class-variance-authority (see components/ui/button.tsx): typed, exhaustive variant maps instead of ad-hoc conditional className strings.

Content architecture: separation with a schema

Content is data; components are presentation. Long-form lives in content/*.mdx. Page copy lives in typed modules like data/home.ts. Site facts live in data/site.ts.

The pipeline (lib/content.ts) reads MDX, validates frontmatter with Zod, and renders through a unified/remark/rehype chain with sanitisation (rehype-sanitize). Even my own Markdown stays untrusted-by-default, because that habit prevents the one time the input is not mine.

Why this matters: editing words never touches JSX, and adding a page is adding a file, not refactoring a component. That's the property that lets a codebase age without rotting.

SEO and LLM visibility as build artifacts

Discoverability is engineering output, not marketing dust.

  • Structured data as code (lib/seo.tsx): Person, WebSite, ProfessionalService, FAQPage, BreadcrumbList, ItemList, ProfilePage, and Article compose per page with a shared @id graph so entities resolve.
  • llms.txt, llms-full.txt, a dedicated profile, and per-page Markdown companions ship at build time (scripts/generate-llms.ts) and link via <link rel="alternate" type="text/markdown">. Assistants get clean, authoritative context instead of scraping rendered HTML.

This is a deliberate bet: the page now has a machine audience, and machine legibility is something you compile, not something you hope for.

Applied AI & Context Retrieval (The Vectorless RAG Choice)

For conversational retrieval (such as Nykaa's chatbot), we built a lightweight, vectorless RAG pipeline instead of standard database routing.

  • The Tradeoff: A database like pgvector is the correct choice when scale grows. But for a small catalog, serializing OpenAI text-embedding-3-small vector coordinates into flat JSON files and performing cosine similarity in memory is faster. It eliminates external database hops, reduces search latency to <5ms, and saves $1,000s in hosting fees.
  • LangGraph State Loops: Rather than free-form loops, LangGraph organizes conversational states. An intent node classifies query boundaries, a tool router fetches contextual embeddings, and a custom reranker formats context. This guarantees deterministic paths and clean error boundaries.

Quality Pipelines: Vale, Jest, and class sorting

We treat code cleanliness and content consistency as automated build assertions:

  • Tailwind Class Order: prettier-plugin-tailwindcss formats class ordering. This keeps styles readable and prevents CSS size duplicates.
  • Vale Prose Linting: The Vale linter validates writing in a prebuild script. Vale checks markdown guidelines, grammatical mistakes, and naming consistency so tech notes stay professional.
  • Boundary Testing: Unit tests target deterministic code boundaries: frontmatter schemas, utility functions, custom sitemaps, and search rankings. We maintain 95%+ coverage on critical client routes to ensure components fail-safe.

Containerized Edge Deployment (Docker & Traefik)

We build and package the site using modern container patterns:

  • Standalone Node Build: Next.js compiles to a standalone folder, packing only files required for runtime. This reduces the Docker image size from 1.2GB to 140MB.
  • Dynamic Edge Routing: The container deploys behind a central Traefik gateway. Instead of publishing host ports (like 80 or 443), Traefik maps traffic via isolated Docker network namespaces using container labels read at runtime.
  • Unprivileged Execution: Containers drop root privileges immediately at startup, running on a read-only layer to block unauthorized filesystem writes.

Patterns and principles applied

  • Single source of truth: tokens for design, Zod types for content, site.ts for facts. Every fact has exactly one home.
  • Parse at the boundary: untrusted input becomes a typed value at the edge; the interior assumes correctness.
  • Composition over configuration: small primitives (Section, Card, Reveal, Button) compose into pages; no mega-components with twenty props.
  • Progressive enhancement: content renders without JS; interactivity (theme, reveals, tools) is additive and degrades cleanly.
  • Performance as a constraint, not a phase: motion composites only, scripts load lazyOnload, fonts swap. The budget gets defended at design time.
  • Accessibility by construction: semantic landmarks, focus management, and prefers-reduced-motion are part of the components, not a later audit.

What I'd revisit at scale

Good architecture also names its own seams. If this grew into a multi-author publication I'd add: a typed content layer with incremental builds (e.g. a content collection API) so full rebuilds don't bound publish time; visual regression tests on the component library; an OG-image generation step; and a small e2e smoke suite on the interactive tools. None are worth their complexity today, and knowing when a pattern starts paying for itself is the actual job.