Rebuilding My Portfolio with Next.js
How this portfolio uses shared project data, server-rendered pages, local MDX, and a validated contact route, with the tradeoffs behind those choices.
Revision note: This November 2026 revision describes the current repository. Earlier versions of this article described Next.js 14; the Next.js 16 details below apply to this revision. The original recorded publication date is retained.
The useful part of this rebuild is how the content, navigation, and delivery paths fit together. A portfolio needs to let someone understand my role, inspect evidence, and make contact. This walkthrough explains the implementation choices that support those jobs.
Why Rebuild?
The site has several kinds of content: experience, consulting services, research, case studies, and writing. Repeating that information across page markup, navigation, and search makes updates harder to keep consistent.
The current structure keeps project records in src/data/systems.ts. The case-study pages and search index read that shared data. A project can describe its stage, my contribution, constraints, outcome, and available evidence without requiring a separate content model for every view.
This does not make the content correct automatically. It gives me fewer places to correct it when a project's status or supporting evidence changes.
Tech Stack Choices
Next.js 16 with App Router
The current application uses Next.js 16 and the App Router. The writing index and article routes are server components; browser interaction is kept in components such as the theme toggle, search dialog, and contact form.
That boundary matters because reading files and parsing article metadata do not require browser JavaScript. The article route uses generateStaticParams for published slugs and generateMetadata for each article's title, canonical URL, and social metadata.
The tradeoff is framework maintenance. I need to check changes against the installed Next.js version and verify the build when upgrading. Using a modern framework alone is not evidence of faster pages; that requires measurement.
TypeScript
The project types make content requirements visible before a page is rendered. For example, a case study has separate fields for myRole, coreConstraint, and outcome. That encourages a useful distinction between what I built and what the whole project achieved.
TypeScript cannot validate an outcome claim or protect a public endpoint by itself. The contact route also validates incoming data at runtime.
Tailwind CSS
Shared components such as PageIntro, SectionHeader, and PostCard carry recurring layout patterns. Tailwind utilities handle responsive spacing and layout, while theme variables describe surfaces, borders, and text colors.
The useful decision is to share the pattern without forcing every page into the same density. A wide desktop introduction and a readable article column serve different purposes. Mobile layouts need their own spacing and wrapping checks.
Framer Motion
Framer Motion is used in interactive components, including the contact form and scroll-to-top control. The writing cards use CSS transitions instead of requiring a motion component for every card.
Animation is a maintenance and accessibility consideration. The global stylesheet reduces CSS motion when a visitor requests it, and the scroll-to-top action checks that preference. JavaScript animations still need review; a CSS rule does not establish that every animated interaction respects reduced motion.
Key Features
Dynamic Dark Mode
Dark mode and the theme toggle are already implemented. The theme provider uses next-themes with the class attribute, defaultTheme="system", and system preference support. The toggle reads the resolved theme after mounting.
The remaining work is checking actual components in both themes, including focus states and text contrast when a shared token changes.
MDX Blog
Posts live in src/content/blog. src/lib/mdx.ts reads frontmatter with gray-matter, calculates reading time, and excludes drafts or explicitly unpublished posts. The article route renders the source with next-mdx-remote.
Keeping content in the repository lets prose and rendering changes go through the same review. The cost is that publishing requires a repository change and deployment; this setup does not provide a CMS editing workflow.
Search and article rendering share a content-destination transform so a search result can point to the section or passage it describes. When I rewrite a section, preserving its published anchor is part of the edit.
This revision also separates the original date from an optional updated date. Readers see both dates, and the article metadata and sitemap use the revision date where appropriate. That prevents a later framework upgrade from appearing to have happened at the original publication date.
API-Powered Contact Form
The form posts to a Next.js route at /api/contact; Resend is the external email delivery provider. The route handler checks the request origin, applies rate limits, bounds the request body, validates fields, and escapes user text before building email HTML.
The route reports success only after the email provider accepts the request. That response does not prove inbox delivery or that the message has been read. Configuration errors, provider failures, and duplicate submissions need explicit handling alongside the visible form.
Lessons Learned
- Share content deliberately. A typed record can keep a case study, its listing, and search in agreement. Editorial judgment is still needed to decide what belongs on each surface.
- Preserve the public contract. A rewrite can break a section link even when the page URL remains unchanged. Navigation targets, publication dates, and form responses deserve review alongside visual changes.
- Test the behavior that matters. The repository includes unit and integration tests, browser tests, accessibility checks, and a bundle-size check. Passing those checks supports specific behaviors; it does not establish that every visitor will find the site clear or that every accessibility issue is covered.
What's Next?
My maintenance priorities are to keep case-study evidence current, explain consequential engineering decisions in the writing, and check desktop and mobile reading paths after layout changes. Dark mode is an existing feature, not a future item.
The repository makes these implementation choices inspectable. It is also where the details should be checked as the site evolves beyond this revision.