About this blog: a minimal, AI-friendly portfolio in Next.js 16

May 2, 2026

I've rewritten my personal site more times than I'd like to admit. This version is the first one I want to keep — small, fast, and honest about what it is: a place to publish what I'm building and what I'm learning. This post is a tour of how it's put together and the choices I made along the way.

The stack

Nothing exotic, on purpose:

  • Next.js 16 with the App Router and React 19
  • Tailwind v4 alpha for styling — no design system, just utility classes and restraint
  • MDX for blog posts, parsed with a tiny in-repo frontmatter reader (no gray-matter, no contentlayer)
  • Geist for the typeface
  • Vercel for hosting, analytics, and speed insights

The whole repo is intentionally short. I wanted to be able to read every file in an afternoon and understand exactly what's happening — no hidden plugin, no opaque generator step. If I forget how a piece works in six months, I want a 50-line file to re-read, not a framework.

Posts as MDX, not a CMS

Each post is an .mdx file under app/blog/posts/. The slug is the filename, the metadata lives in frontmatter, and the parser is about thirty lines:

const FRONTMATTER_REGEX = /---\s*([\s\S]*?)\s*---/;

function parseFrontmatter(fileContent: string, filePath: string) {
  const match = FRONTMATTER_REGEX.exec(fileContent);
  if (!match) throw new Error(`Missing frontmatter in ${filePath}`);
  // ...split on lines, trim quotes, validate required keys
}

I don't need a CMS. I write in my editor, commit, and push. The build picks up the new file and Next does the rest. If the day ever comes that I want drafts, scheduled publishing, or a richer authoring flow, I'll add it then — not before.

Projects as a JSON feed

The Projects page is driven by a single projects array in TypeScript. The same data is served as projects.json so any tool — including LLMs — can fetch a structured view of my work without having to scrape the page.

That fed into one of my favourite small features on the site.

"Ask AI" as a first-class affordance

At the top of the projects page there's a pair of buttons: Ask Claude and Ask ChatGPT. They open a new chat in the respective product, pre-filled with a prompt that points the model at my site and the JSON feed:

const promptTemplate = (siteUrl: string, jsonUrl: string) =>
  `I'm exploring Eryet Chen's web developer portfolio at ${siteUrl}.\n\n` +
  `For structured project data, fetch: ${jsonUrl}\n` +
  `For anything the JSON doesn't cover (about, blog, links, project details),` +
  ` browse ${siteUrl} directly.\n\n` +
  `Give me a short overview — main tech stacks, project types, and 2–3` +
  ` standout projects. After that, I'll ask follow-ups.`;

The thinking: people don't read portfolios. They scan, decide in ten seconds, and leave. If a recruiter or a fellow engineer would rather just ask a model "what's this person actually good at?", I'd rather meet them where they already are than try to out-design that behaviour. The button is a cheap, deferential way to say here's the structured data, here's the URL, go.

The WebGL shimmer (because why not)

The Ask-AI buttons have a subtle WebGL shimmer behind them — a slow diagonal sweep when idle, a cursor-tracked glow on hover, brand-tinted per provider (warm orange for Claude, teal for ChatGPT). It's about 150 lines of raw WebGL with no library, one canvas per button, and it honours prefers-reduced-motion.

The shader is mostly two ideas added together:

float sweep = band(diag, sweepPos, 0.18) * (0.35 + 0.65 * u_hover);
float glow  = exp(-distance(p, m) * 4.5) * u_hover;
gl_FragColor = vec4(u_color * (sweep * 0.55 + glow * 0.85 + base), intensity);

A diagonal band that breathes through space, plus a soft radial falloff anchored to the mouse. Strictly speaking, the site didn't need it. But I wanted one thing on the page that quietly says "a person made this" — something a Tailwind preset can't reproduce. Pick your spot, then keep the rest of the surface calm.

What's next

Thanks for reading. If you'd like to reach me, the Get in touch section on the home page has the channels I check.