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 be able to 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. It is written the way I'd expect to defend a design in review.

The stack at a glance

ConcernChoiceOne-line reason
FrameworkNext.js 15 (App Router)File-system routing + RSC + first-class static export
LanguageTypeScript (strict)Types as the cheapest test you can run
RenderingStatic export (SSG)No server to run, attack, or pay for; CDN-fast
StylingTailwind + design tokensOne source of truth; no CSS entropy
ContentMDX + gray-matter, Zod-validatedContent/template separation with a schema guard
UI primitivesCVA + a tiny component layerVariants without a heavyweight UI framework
Iconslucide-react + inline brand SVGsTree-shakeable; brand marks under our control
SEO/LLMSchema + llms.txt as codeMachine 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 — push dynamic behaviour to the client edges (the tools, theme, contact form) and keep everything else static — is exactly the boundary you want anyway.

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 is parsed through Zod schemas (workFrontmatterSchema, etc.) so malformed content fails the build instead of shipping a broken page — parse, don't validate. 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 in typed modules like data/home.ts, site facts 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) — untrusted-by-default even for my own Markdown, because that's the habit that prevents the one time it isn't 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 treated as engineering output, not marketing dust:

  • Structured data as code (lib/seo.tsx): Person, WebSite, ProfessionalService, FAQPage, BreadcrumbList, ItemList, ProfilePage, Article — composed per page with a shared @id graph so entities resolve.
  • llms.txt, llms-full.txt, a dedicated profile, and per-page Markdown companions are generated at build (scripts/generate-llms.ts) and linked 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.

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, don't validate — untrusted input becomes a typed value at the boundary; 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 is 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.