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:
- 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.
- 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.
- 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."
- 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. - 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.
- 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.
- Code blocks are an isolated dark island. The
one-dark-proShiki theme contrasts hard against the light page. Visually, the code sat in a different room from the prose. - 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.
- 404 and error pages were throwaways. "Oh no, something went wrong... maybe refresh?" is the actual error message. A missed personality moment.
- No search, no tag filter, no /tags page, no visible RSS. The taxonomy existed; the IA didn't surface it.
- SEO and structured data were partial.
BlogPostingJSON-LD per post, dynamic OG via@vercel/og, but no sitemap, norobots.txt, noPerson/WebSite/BreadcrumbListschema, nollms.txtfor 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 (
#FAF9F6light,#0E0D0Cdark — 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.xmlviaapp/sitemap.ts. - Robots at
/robots.txtviaapp/robots.ts. - JSON-LD structured data: extend
BlogPostingwithPersonandPublisher. AddWebSitewithSearchActionon the home. AddBreadcrumbListon post and tag pages. /llms.txtat 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.txtwith 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-authdependency. Auth surface area for a feature that wasn't earning its keep. - The Kaisei Tokumin font files (replaced by Newsreader).
- The
one-dark-proShiki theme (replaced byvitesse-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/ogdynamic 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-balancerfor 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.