Why I Built This Blog with Next.js Instead of a No-Code Tool
ISR, GraphQL, Redis cache, and a Contentful webhook that auto-publishes to Hashnode. The technical decisions behind Setpoint — and what I learned building it.
The honest reason
I could have used Hashnode directly. Or Ghost. Or even Notion with a custom domain. They're good tools — fast to set up, zero infrastructure to maintain. But I'm a developer. And if my portfolio is going to say "I build production-grade Next.js applications," then my blog had better be one.
Setpoint is that proof of work.
What I actually built
The stack is not simple by accident. Every decision maps to something a real engineering team would care about:
Contentful as the CMS gives me a proper GraphQL API instead of filesystem markdown. That means normalized queries, typed responses, and the ability to model relationships — articles belong to series, series have ordered parts, tags carry hex color codes that propagate into the UI. It's the kind of data modeling you'd do in a real product.
Apollo Client handles the GraphQL layer with a normalized cache. When the home page fetches the featured article and the latest grid in parallel via Promise.all, those results are cached. If the same article appears in both queries, Apollo deduplicates it at the object level — not at the request level. That distinction matters at scale.
Upstash Redis sits in front of Contentful. Every page that fetches content goes through withCache() — a thin wrapper that checks Redis first, falls back to Contentful if it misses, and writes the result back with a TTL. The home page revalidates every 60 seconds. Article pages every 5 minutes. TTFB drops from ~400ms to under 50ms on cache hits.
ISR handles the rest. Pages are pre-rendered at build time and revalidated on demand when Contentful fires a webhook. Readers get static-page performance. Editors get live updates without a manual redeploy.
The part that actually took time
The syndication pipeline. When I publish an article in Contentful, a webhook hits /api/webhooks/contentful. The handler checks whether that article has syndication enabled, fetches the full content, sends it to Gemini 2.5 Flash (or the most recent free gemini api model) with a platform-specific prompt, and publishes the adapted version to Hashnode — with a canonical URL pointing back to Setpoint so search engines know where the original lives.
Getting the prompt right took more iterations than the API integration itself. The model needed to preserve technical accuracy, adapt the intro for a Hashnode audience, and not sound like it was written by an AI. The structured output format — JSON with title, body, and tags fields — made the parsing deterministic. The fallback to OpenRouter's free tier made the whole thing resilient.
What no-code tools would have cost me
Not money. Time, in a different direction. A no-code blog would have been live in two hours. This one took three weeks of evenings. But those three weeks produced something a resume cannot: a system I can point to and say "I made every decision here, and I can explain each one."
The Redis cache strategy. The cursor-based pagination on the comment moderation queue. The ReadingListProvider that fetches saved slugs once on mount and shares them via context so every ArticleCard across every page shows the correct saved state without a single prop-drilling chain. The OG images generated dynamically with @vercel/og. The proxy.ts middleware that replaces Next.js 16's deprecated middleware.ts.
These are the details that don't fit in a bullet point on a CV. They fit here.
Who this is for
Setpoint is a technical publication about Industry 4.0 — PLCs, SCADA, IIoT, OPC-UA — written by someone who also ships production software. The industrial automation world and the modern web development world almost never talk to each other. That gap is interesting to me, and this is where I write about it.
If you work in one world and are curious about the other, you're in the right place.
The full architecture diagram and metrics are in the README on GitHub. The source is public — feel free to dig into how any of this works.
Discussion
Sign in to join the discussion.
Sign in →No comments yet. Be the first.
