Building zerofocus.dev From Scratch in One Session
The Starting Point
I wanted a home for Zero Focus Dev — somewhere to dump blog posts, videos, and project logs as I build stuff. Nothing fancy. No CMS, no database-driven blog engine, just a dark-themed Laravel site that feels like me: code, coffee, and chaos.
The goal was to go from laravel new to a shipped homepage with a working blog system in one sitting. And then — because I'm apparently that kind of person — build a Claude Code skill that documents what I build so I never have to write blog posts from scratch again.
What Actually Happened
The homepage came together fast
Tailwind CSS 4 with a custom color palette — bark for the dark backgrounds, ember for the orange accents, forest for the green status indicators. Built a full landing page with a hero section featuring my 3D avatar, an about section, content cards, a fake terminal showing git status (because of course), and social links. The avatar image had to be manually copied from OneDrive because Claude can't access uploaded conversation images as files. Minor friction.
The blog config caching trap — twice
First version of the blog stored posts as PHP arrays in config/blog.php. It worked locally, shipped it, then hit the /blog page and got foreach() argument must be of type array|object, null given. Classic Laravel config caching — the new config file existed but Laravel was reading a cached version that didn't include it. php artisan config:clear fixed it.
Then I upgraded the blog to read markdown files instead of config arrays. Created a BlogService, updated the routes, deleted config/blog.php. Hit the blog page again — same error. This time it was the view cache holding onto the old compiled Blade template that still referenced config('blog.posts'). Had to clear both view and config cache. Same trap, different layer.
Symfony YAML wasn't there
The BlogService needed to parse YAML frontmatter from markdown files. I reached for Symfony\Component\Yaml\Yaml assuming it was bundled with Laravel (it usually is as a dependency). Nope — not in this install. Got Class "Symfony\Component\Yaml\Yaml" not found. Rather than adding a dependency for parsing five simple key-value pairs, I wrote a lightweight parser that handles strings, quoted values, and bracket arrays. Thirty lines, zero dependencies, does the job.
The meta move — building a skill that documents builds
The last piece was creating a /document Claude Code skill. It's a personal skill that lives in ~/.claude/skills/document/ so it works from any project. When you run it, it digs through git history looking for the real story — not just what changed, but the fixup commits, the reverts, the stashed experiments. It checks for error logs, issue references, dependency changes. Then it writes a structured markdown draft to content/drafts/ on this site.
The draft format is designed for honest storytelling: "The Starting Point", "What Actually Happened", "What Worked", "What I Learned". Not a changelog. Not a PR description. The messy middle that actually helps people.
What Worked
The final stack:
- Homepage — dark theme, custom Tailwind palette, Inter + JetBrains Mono fonts, responsive with mobile nav
- Blog system — markdown files with YAML frontmatter in
content/posts/, parsed byBlogService, rendered withStr::markdown()and Tailwind Typography prose classes - Draft workflow —
/documentskill writes tocontent/drafts/, move tocontent/posts/when ready to publish - No database needed for blog content — just files on disk
The blade templates don't care whether posts come from config or markdown files — they consume the same array shape (title, date, excerpt, tags, content). That made the migration painless.
What I Learned
- Laravel config caching will get you every time you add a new config file. Always
config:clearafter adding files toconfig/. - View caching is a separate layer — clearing config doesn't clear compiled Blade templates. When your template references change, clear both.
- Don't assume Symfony components are bundled — check
composer showbefore reaching for them. A 30-line parser beats an unnecessary dependency. - The
/documentskill is meta in the best way — this very blog post draft was generated by the thing I just built. It's turtles all the way down. - Tailwind CSS 4 uses
@plugininstead of the oldpluginsarray in config for things like typography.
Tech Notes
- Stack: Laravel 13.4, Tailwind CSS 4, Vite 8, PHP 8.4
- Key files:
app/Services/BlogService.php,content/posts/*.md,~/.claude/skills/document/SKILL.md - Dependencies added:
@tailwindcss/typography - Commands worth remembering:
php artisan config:clear && php artisan view:clear— the nuclear option when things aren't updating/document <notes>— run from any project to create a blog draft
Raw Notes
Built out the entire zerofocus.dev site from scratch in one session. Started with a blank Laravel install, ended with a full homepage, blog system, and a meta skill that documents what I build. The config-based blog broke because of Laravel config caching — had to clear cache after adding the new config file. Then when upgrading to markdown files, Symfony YAML wasn't bundled so I had to write a simple parser instead. Classic "it should just work" moments.
Zero Focus Dev
Breaking things, learning fast, sharing it all.