# The mepritam.dev tech stack — choices, trade-offs, and architecture

URL: https://mepritam.dev/tech-stack/

A senior engineer's rationale for the stack behind mepritam.dev: Next.js static export, MDX content, a token-driven design system, structured-data-as-code, 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

| Concern       | Choice                               | One-line reason                                             |
| ------------- | ------------------------------------ | ----------------------------------------------------------- |
| Framework     | **Next.js 15 (App Router)**          | File-system routing + RSC + 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 tiny 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 — 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.
