❯ Web · 2024
Professional Portfolio
A content-driven Next.js 14 portfolio, the predecessor to this site, rendering markdown and JSON Schema-typed data as a static export.
The problem
Before this site existed, the resume PDF carried everything, and it did not scale. Recruiters had to download a file to see the work. Projects, experience, and writing needed a browsable home, not an attachment.
The approach
I built a content-driven Next.js 14 site with TypeScript and Tailwind: About, Experience, Projects, Blog, Skills, and Contact, all rendered from markdown via gray-matter and remark/remark-html, statically exportable. Site data was modeled as JSON Schema 2020-12 entities (Experience, Project, BlogPost, SkillCategory) so every section rendered from typed, organized data instead of hand-placed JSX.
Architecture
- Markdown Content
- JSON Schema Models
- Next.js Static Build
- Page Components
- Tailwind CSS
FLOWS
- Markdown Contentvalidated typesJSON Schema Models
- Markdown Contentgray-matter + remarkNext.js Static Build
- JSON Schema Modelstyped dataNext.js Static Build
- Next.js Static Buildrender routesPage Components
- Page Componentstoken classesTailwind CSS
Content files are the source of truth: markdown with frontmatter, parsed at build time, typed by the schema models, rendered by page components. There is no database and no server to operate; a static export deploys anywhere. Tailwind provides styling as utility classes, and ESLint keeps the codebase honest.
Key decisions
Content files over a CMS
Git-native, versionable, no lock-in. A one-person site does not need an admin panel.
JSON Schema for the data model
Typed sections mean a missing field fails loudly instead of rendering a hole.
Static export
No server to babysit, no hosting bill beyond static files.
Markdown pipeline over a rich-text editor
The authoring experience is a text file.
What I'd do differently
This site is the predecessor to the one you are reading, and the lessons are visible in it. Validation existed but lived in the render path; the current site validates content at build time so a bad file fails the build by name. The blog was scaffolded but barely fed; I would either commit to writing or leave the section out. And the design system was assembled as I went; defining tokens first would have saved a refactor.