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

This is the architecture record for mepritam.dev. A portfolio is a small system. Small systems still deserve choices you can defend. I list what runs the site and why. Trade-offs sit beside the habits that keep change cheap.

Stack

Next.js 15 App Router. File-system routing and RSC. Static export too.

TypeScript in strict mode. The compiler is the cheapest test you can run.

Static export. No server to run or pay for. The CDN serves HTML.

Tailwind plus design tokens. One source of truth.

MDX and gray-matter, checked by Zod. Copy and templates stay apart.

CVA plus a small UI layer. Variants without a heavy UI kit.

lucide-react plus inline brand SVGs. Brand marks stay in-repo.

Schema and llms.txt as code. Machine-readable output is a build artifact.

Static export

The whole site uses output: "export". The build writes HTML for a CDN. Content changes on deploy. It does not change per visit. SSG is the right default here.

Performance is structural. The critical path has no server round-trip. Hydration does not block on a data fetch. TTFB is CDN latency.

The security surface is almost nil. The process has no runtime or server secrets. HTTP handlers do not exist.

Operations stay small. Rollback is an earlier immutable build. Capacity planning does not apply.

The trade-off is honest. Personalization per visit is out. A publish needs a full rebuild. For a portfolio those costs sit near zero. SSG beats SSR here.

The build also forces a split. Interactive behaviour lives at the client edges. Tools and theme live there, as does the contact form. Everything else stays static. A 90+ Lighthouse score on mobile in 2026 is the bar I hold.

TypeScript

strict: true stays on. On a one-person project the cheapest test is the compiler. Frontmatter flows through Zod schemas such as workFrontmatterSchema. Bad content fails the build. It never ships a broken page. Parse at the boundary, then trust the result. Templates consume Zod output. Content matches rendering.

Styling

Tailwind is the engine. design/tokens.ts is the source of truth. Colour lives as tokens. So do the scale and spacing. Radii and motion too. tailwind.config.ts imports them. UI parts compose utilities. They never invent raw hex. They never invent one-off pixel values.

A senior reviewer cares about two outcomes.

Theming is a variable swap. Light and dark are CSS custom properties. A second dark stylesheet cannot drift because it does not exist.

The system resists entropy. New UI parts inherit the scale. They do not add a 13th shade of grey. That gap separates a design system from a pile of class names.

Variants use CVA. See components/ui/button.tsx. Maps are exhaustive. Ad-hoc className strings are not.

Content

Copy is data. UI is presentation. Long-form lives in content/*.mdx. Page copy lives in modules such as data/home.ts. Site facts live in data/site.ts.

The pipeline in lib/content.ts reads MDX. It validates frontmatter with Zod. It renders through a unified remark and rehype chain. rehype-sanitize strips unsafe HTML. Even my own Markdown stays untrusted by default. That habit catches the one day the input is not mine.

Editing words never touches JSX. A new page is a new file. You do not refactor a UI part to add a route. That is how a codebase ages without rot.

SEO

Discoverability is a build product.

Structured data lives in lib/seo.tsx. Pages compose Person and WebSite. They also compose FAQPage and Article. A shared id graph lets entities resolve.

llms.txt ships at build time. So do llms-full.txt and per-page Markdown companions. scripts/generate-llms.ts writes them. Pages link them with rel alternate. Assistants get clean context. They do not need to scrape HTML.

Pages have a machine audience now. You compile machine-readable output. You do not hope a crawler infers it.

RAG

For conversational retrieval, such as the Nykaa chatbot, we built a small vectorless RAG pipeline. We did not route every query through a vector database.

A store such as pgvector is right when scale grows. For a small catalog we serialize OpenAI embeddings into JSON files. Cosine similarity runs in memory. That removes external database hops. Search latency stays under 5ms. Hosting fees drop by thousands of dollars.

LangGraph owns the conversation states. We avoid free-form loops. LangGraph classifies intent. Then it fetches embeddings. A reranker then formats context. Paths stay deterministic. Error boundaries stay clean.

Quality

Code hygiene and prose rules are build assertions.

prettier-plugin-tailwindcss sorts classes. Styles stay readable. Duplicate CSS stays down.

Vale runs in a prebuild script. It checks markdown rules and grammar. Naming too. Tech notes stay consistent.

Unit tests cover deterministic edges. Frontmatter schemas plus helpers. Sitemaps and search ranking. Critical client routes keep 95%+ coverage so UI parts fail closed.

Containers

We package the site with containers.

Next.js compiles to a standalone folder. The image includes the files the runtime needs. Image size falls from 1.2GB to 140MB.

The container sits behind Traefik. We do not publish host ports such as 80 or 443. Traefik maps traffic through isolated Docker networks. Container labels drive the routes.

Containers drop root at startup. They run on a layer that rejects writes. Unauthorized writes fail.

Patterns

Single source of truth. Tokens for design. Zod schemas for content. site.ts for facts. Each fact has one home.

Parse at the boundary. Untrusted input becomes a known value at the edge. The interior assumes correctness.

Composition over configuration. Small primitives such as Section and Card build pages. Reveal and Button too. No mega-blocks with twenty props.

Progressive enhancement. Copy renders without JS. Theme and reveals add on. Tools too. They degrade cleanly.

Performance is a constraint. Motion uses composites. Scripts load with lazyOnload. Fonts use swap. I defend the budget at design time.

Landmarks and focus management live in the UI parts. So does prefers-reduced-motion.

Later

Good architecture names its seams. If this became a multi-author publication I would add a content layer with incremental builds, such as a content collection API. Full rebuilds would no longer bound publish time. I would add visual regression tests and an OG-image step. A small e2e smoke suite would cover the interactive tools. None of that complexity pays today. Knowing when a pattern starts to pay is the job.