Skip to content
About

Redesigning the blog: from a leerob.io fork to an editorial garden

A senior-designer review of the current site, four aesthetic dialects compared, and the hybrid I picked to hold software, garden, family, and photos in one home.

9 min readEvergreen

The blog has been quietly running on a fork of leerob.io since I started writing here. It works. It deploys. It handles the Obsidian sync workflow that pushes notes from this vault into vercel_blog and out to steven.ocampo.io. But last week I cloned it locally, opened it in a fresh editor, and asked Claude to do a senior-designer review. The first sentence back was the one I needed to hear:

Instantly recognizable as a leerob.io fork. There is no editorial point of view, and that's fatal for a personal site.

That's the post. The rest of this is the reasoning behind a redesign that's about to land on main.

What was wrong with the old design

The old site has real strengths. Type contrast is correct: serif headlines (Kaisei Tokumin), system body, monospace metadata. The reading width is right (max-w-4xl for chrome, max-w-[650px] for post bodies). Dark mode is wired everywhere. The MDX components — callouts, pros/cons cards, embedded tweets — are componentized properly, not glued in. The Framer Motion sidebar pill is genuinely polished.

The weaknesses, in the order they jumped out:

  1. Generic template smell. A reader who has seen leerob.io, delba.dev, or any of a dozen other forks of the same template can spot it in two seconds. There's nothing on the page that says whose blog it is.
  2. The sidebar is dated and steals prime real estate. Vertical left sidebar layouts read as 2022-era SaaS chrome. Modern editorial layouts use a top nav or no nav.
  3. Only one font weight loaded. Kaisei Tokumin at weight 700 only. Real type systems use three or four weights with intentional pairings. What I had was "headings + everything-else."
  4. The color story is anemic. A green logo is the only accent color, and even that was accidental — a holdover from the upstream template, not a brand decision. Fifty shades of neutral-* make the site feel like a wireframe.
  5. The blog index is a flat title list. Twenty-three posts, no descriptions, no dates surfaced, no tags, no project grouping. The frontmatter has all of that — the design ignored it. With fifty posts the index becomes unreadable.
  6. The post header is sparse. Date pill + view counter, nothing else. No tags, no description, no reading time, no series indicator. The dev-log content is structured around features and PRs and the design didn't acknowledge that at all.
  7. Code blocks are an isolated dark island. The one-dark-pro Shiki theme contrasts hard against the light page. Visually, the code sat in a different room from the prose.
  8. No information scent for the dev-log nature of the content. These are PR write-ups across two distinct projects (honest-cam and vercel-blog itself). There's no project chip, no series thread, no "what shipped" badge.
  9. 404 and error pages were throwaways. "Oh no, something went wrong... maybe refresh?" is the actual error message. A missed personality moment.
  10. No search, no tag filter, no /tags page, no visible RSS. The taxonomy existed; the IA didn't surface it.
  11. SEO and structured data were partial. BlogPosting JSON-LD per post, dynamic OG via @vercel/og, but no sitemap, no robots.txt, no Person / WebSite / BreadcrumbList schema, no llms.txt for AI crawlers, no canonical-URL discipline, no per-page Twitter Card metadata.

That's a lot. But the brief I'd have to write to fix it was simple: hip and clean. SEO and AI friendly. Accessible. Converts.

Four flavors of "hip and clean"

"Hip and clean" looks completely different depending on which dialect you mean. I sketched four with real reference data, and a fifth as a hybrid:

A. Editorial / serif-forward

Reference: press.stripe.com.

Modern geometric sans for body, sober serif at 32–48px for headlines. Per-section accent colors. Multi-column responsive grids on index pages. Color-blocked feature cards instead of illustration. Tone: "A small press publishing serious work."

Strong fit for dev-log content (each project becomes a "book"), but the strongest moves require commissioned illustration or 3D book covers, which is out of scope.

B. Modern SaaS / mono-geometric

Reference: rauno.me.

System sans for body, JetBrains Mono for metadata, near-monochrome with a single accent. Eight-pixel baseline grid, hairline borders, almost no ornament. Detail comes from microinteractions: copy-to-clipboard feedback, smooth cubic-bezier transitions. Tone: "A craftsperson who ships."

Best fit if the audience is engineers reading dev-logs. Cleanest accessibility track record. Wins on every constraint... until you remember the site also has to hold photos, family, and gardening.

C. Personal / warm idiosyncratic

Reference: maggieappleton.com.

Light-weight serif headings, sans body, cream/off-white background, crimson and sea-blue accents. Hand-drawn leaf elements that animate on hover. Tone: "A digital garden cultivated with care."

Closest tonal match for the broader content scope, but the core moves are illustration craft — hand-drawn leaves, per-essay illustrative headers, view-transition animations. Without those (which are out of mid-scope budget), you get a cream-colored blog with a light serif. Fine, not transformative.

D. Brutalist / zine

Monospace everywhere, hairline borders forming a visible frame, generous gutters, content pinned to a corner. Tone: "I do not care if you like this."

Cool, distinctive, but actively works against conversion. Brutalist sites signal; they don't sell. Skip.

The hybrid: editorial garden

Closest live references: Tom MacWright (writes about software and gardening), Craig Mod, Robin Sloan.

The trick was realizing the brief shifted halfway through. I started thinking about a developer blog and ended up planning a personal publication with six verticals: programming, writing, garden, photos, family, projects. Pure dialect B is the right aesthetic for engineers reading dev-logs and the wrong aesthetic for everything else. Pure dialect C is the right tone for personal content but doesn't ship without illustration craft. The hybrid steals the right things from each:

  • From A (Editorial): generous serif display, sectioned IA where each content type has its own index style, magazine-grade type hierarchy.
  • From C (Personal warm): cream/paper background instead of pure white (#FAF9F6 light, #0E0D0C dark — warmer than #09090B), restrained but warm accent palette.
  • From B (Modern SaaS): clean technical discipline applied only on programming content — hairline code blocks in vitesse-light/vitesse-dark, mono metadata rows, eight-pixel baseline grid, accessible focus states.

The engineering content reads as engineering. The garden reads as garden. Both live in the same chrome.

Type system: Newsreader (Google Fonts variable, weights 400 / 600 / 800) for both display and body — Newsreader is designed to do both, which is rare and exactly what this brief needs. Inter for UI / metadata / nav. JetBrains Mono for code and dates. One face per role. No more.

Color: warm paper. Light is #FAF9F6 background with #1A1814 text; dark is #0E0D0C with #F5F0E8. Single accent: #068176 deep green (the existing logo color, now also reads as "garden"). Contrast tested for WCAG AA at 4.5:1 body text and 3:1 large text in both modes.

Tone in one sentence: "A craftsperson's notebook — software, garden, family, words — kept seriously."

The information architecture

The current single /blog URL space turns into six top-level sections:

  • /programming — technical dev-logs and PR write-ups. The existing 23 posts live here.
  • /writing — personal essays and philosophy.
  • /garden — gardening journal, in field-notes layout.
  • /photos — photo sets, masonry grid + lightbox.
  • /family — family trips, casual public, photo-heavy.
  • /projects — project landing pages with metadata for honest-cam and vercel_blog.

The implementation cost is one frontmatter field — section — with default: 'programming' so the existing 23 posts auto-route without needing edits. A new garden post adds section: garden. Routing is one dynamic route app/[section]/[slug]/page.tsx that reads the section param and renders the matching layout. The Obsidian sync workflow doesn't change at all: the vault keeps writing to Published/*.md, contentlayer reads from content/md/, the section field handles the rest.

The home page becomes a unified "recent across everything" feed: latest items from each section, mixed chronologically, capped at eight. Each card shows a section chip so the mix stays legible.

The conversion layer

A redesign that doesn't think about conversion is just a paint job. The brief said "accessible" and "converts" in the same sentence, so I picked one primary CTA and refused to add five secondary ones.

Primary: email subscription via Beehiiv. The integration is a plain HTML form posting to Beehiiv's public subscribe endpoint. No JavaScript widget, no third-party iframe, no API key in env. Three placements: post footer, site footer, home hero secondary CTA.

The clever part is the RSS-to-email automation. Beehiiv polls the blog's RSS feed and auto-composes newsletters from new items. The full pipeline:

write a post in Obsidian
  → vault sync (rsync to vercel_blog)
  → blog deploy (Vercel)
  → RSS feed updates
  → Beehiiv detects new item
  → email goes out to subscribers

Zero manual newsletter work. The blog is the newsletter.

Secondary: Instagram follow @stevieismagic. Footer + small icon in the top-right of the header. No prominent "Follow me" button on the home page — it's secondary, not primary.

The SEO + AI + accessibility layer

This is where the brief got interesting. "SEO friendly + AI friendly" in 2026 means more than meta tags. It means:

  • Per-page metadata via Next.js App Router generateMetadata — title, description, canonical, OG, Twitter Card on every route.
  • Sitemap at /sitemap.xml via app/sitemap.ts.
  • Robots at /robots.txt via app/robots.ts.
  • JSON-LD structured data: extend BlogPosting with Person and Publisher. Add WebSite with SearchAction on the home. Add BreadcrumbList on post and tag pages.
  • /llms.txt at the public root — an emerging convention listing every post with title, date, description, and full URL, designed for LLM crawlers that want a structured site index. Optionally /llms-full.txt with the concatenated text of every post.
  • Semantic HTML throughout: <article>, <header>, <nav>, <main>, <aside>, <time datetime="...">. LLMs and assistive tech both win.
  • Performance for ranking: fonts loaded via next/font/google (self-hosted, zero CLS). Targets — LCP < 2.0s, CLS < 0.05, INP < 200ms.

Accessibility against WCAG 2.2 AA: contrast tested, focus states visible (2px outline at 2px offset), skip-to-content link on focus, hit targets at 44×44 minimum, prefers-reduced-motion respected, alt text required on every image at the TypeScript-component level, keyboard nav verified through every interactive surface.

What gets deleted

  • The left sidebar (components/sidebar.tsx).
  • The guestbook page, components, API routes, and the next-auth dependency. Auth surface area for a feature that wasn't earning its keep.
  • The Kaisei Tokumin font files (replaced by Newsreader).
  • The one-dark-pro Shiki theme (replaced by vitesse-light / vitesse-dark).
  • The placeholder error boundary message ("Oh no, something went wrong... maybe refresh?"). It's getting a custom 404 with personality.

What stays

  • The contentlayer pipeline. The Obsidian sync workflow depends on it; swapping it out would mean coordinating a vault PR for no real benefit.
  • The view counter API and component (de-emphasized but kept).
  • The @vercel/og dynamic OG image generator (template restyled to match the new system).
  • The MDX component library: Callout, ProsCard, ConsCard, RoundedImage, YouTubeEmbed, Tweet. All restyled, none rewritten.
  • react-wrap-balancer for serif h1 line balancing.

Why I'm writing this down

There's a small irony in writing a blog post about redesigning a blog before the redesigned blog exists. But the documentation is part of the work, not separate from it. When the redesign ships, this post becomes an artifact of why — the design decisions, the trade-offs, the dialects considered and the dialect picked. Future me, or a future engineer working on the site, can read this and understand the brief that shaped the code.

The plan also lives in ~/code/vercel_blog/docs/design-system.md (the reference doc, kept next to the code) and ~/code/vercel_blog/docs/architecture.md (the system overview). This post is the human-readable companion — the version with the reasoning and the regret and the dialect that almost won.

Back to building.

Enjoyed this? Join the newsletter.

New essays and notes straight to your inbox.

Newsletter coming soon — stay tuned.