I rebuilt this site with Claude Code last week. Here's what happened.
Background
This site, oat-bot.com, was originally a static site I built by hand (i.e. without AI), about five years ago. It was hosted on S3 and CloudFront and had been sitting largely untouched since.
Last week I decided to rebuild it. The goal was to move to a server-side Roda application, which would give me the ability to embed small AI-powered demos and prototypes into the site over time. Ruby is my main language, and I chose Roda specifically because it is lightweight and fast, and because I have a lot of admiration for Jeremy Evans, who created it. He also authored Sequel, and his approach to Ruby libraries is one I find consistently thoughtful. The new site would preserve the existing visual identity exactly: colour scheme, typography, logos, and content.
This is a small personal site, and the work was largely front-end. There was not much backend complexity involved, though that is not to understate what Claude Code brought to it.
Why Claude Code
I had used Claude in the browser plenty, but I wanted to have the direct filesystem and terminal access experience using the desktop application. I was also curious to see what it felt like to work with an agent that could execute as well as advise, rather than one that hands code back for me to run myself.
Claude Code has access to the filesystem, can run shell commands, start servers, make commits, and interact with external tools like the Heroku and GitHub CLIs, all within the same session. The interaction felt less like asking a chatbot for help and more like working alongside someone who could also pick up the tools.
I had LazyVim running alongside the session throughout, so I could watch changes land in the codebase in real time, actively review what was being built, and occasionally make edits directly where needed.
How it started
I began by having Claude Code fetch the live site, its CSS, and all three pages. From that alone it identified the full visual identity, colour tokens, typography (Atkinson Hyperlegible), layout, and content. It also identified the original static site generator that was used to build it, without me mentioning it.
Before writing any code, it asked five clarifying questions: templating engine preference, how I wanted to handle the blog, email signup approach, asset sourcing, and deployment target. Answering those upfront meant no back and forth during the build itself.
The build
Once I had answered the scoping questions, it downloaded all the assets (SVGs, fonts, the open graph image), scaffolded the full Roda project, wrote the CSS from scratch by porting the original design tokens faithfully, and built all four ERB templates.
Two bugs appeared on first boot. An ERB encoding error where Tilt was reading templates as US-ASCII, and a missing rackup gem. Both were diagnosed and fixed before I had seen the app running. Normally I would have stopped, switched into debugging mode, worked through it, and returned to the bigger picture. Here the session stayed in flow.
One of the five scoping questions was how I wanted to handle the blog. I said Markdown was fine. Claude chose Kramdown with the GFM parser, which handles GitHub-flavoured Markdown and produces clean HTML without any additional configuration. Posts are stored as plain .md files with YAML front matter for title, date, and description. To verify it was all rendering correctly, Claude generated a dummy “Hello, World” post autonomously, covering headings, bold and italic text, links, lists, and a blockquote.
Content and copy
Once the structure was in place, I updated the copy on the About page and home page. I wrote the new content using Claude in the browser rather than in the Code session, keeping the two tools separate and the context window clean. Claude Code then applied the new copy correctly, including wiring up the Dino to About page link without any direction from me.
Typography and spacing
The original site had font sizes that were too large for what I wanted. I initially gave a vague instruction to make things smaller, which produced an adjustment that still was not quite right. From there I directed specific values: the footer text came down to 1rem, and the footer padding was reduced from 2rem to 1rem. I could have made these changes manually just as easily, but it felt natural and equally fast to stay conversational in the Code session.
The subscribe component and Buttondown integration
Buttondown is an email newsletter tool. The original site already had a Buttondown embed, but on review the code looked outdated, so I sourced the current embed code and provided it to Claude. It wired up the new code while keeping all the existing styles and copy intact.
Before that, I had asked for a bolder call to action on the subscribe component. Claude’s first attempt did not produce a visible change. I noticed that the existing stylesheet had a weight-bold utility class and suggested using that instead, which worked. Spotting that came from familiarity with the codebase.
Git, GitHub, and Heroku
With the site looking right locally, I initialised a git repository, flagged a missing .gitignore before pushing, and deployed to Heroku. I asked about hosting region before going further: Heroku defaults to the US, so the app was destroyed and recreated in the EU region. SSL not provisioning afterwards required real diagnostic work. Claude worked through it methodically, checking HTTP status, identifying that ACM (Amazon Certificate Manager, which Heroku uses to provision SSL certificates) was disabled, and enabling it, all without direction from me.
With the app live, I shared the three existing DNS records pointing at the old distribution and asked whether all three should be removed as part of the migration. Claude confirmed the correct approach and guided the new settings clearly.
The favicon
The favicon was the most iterative part of the session and the place where the limits of working without visual feedback were most apparent.
The site has two SVG assets: a full-body illustration and a head-only version. The first attempt generated the favicon from the full-body SVG, which was too faint at small sizes. Switching to the head SVG helped, but the next attempt came back with a white background, corrected with an ImageMagick flag. Then trimming caused non-square output dimensions, 16 by 10 and 32 by 21, which browsers rejected entirely. I shared a favicon audit report for this to become clear. The final solution required trimming, resizing, then extending back to a square canvas using -gravity center -extent.
That took six or seven rounds. Inspecting the results was harder than expected too: even hard refreshes did not reliably update the favicon in the browser tab, making it difficult to tell whether a change had actually taken effect. It would have been more useful to hear “I cannot verify how this looks at 16 by 16 in a browser tab” rather than each attempt being presented as likely correct.
Adding read time and a refactor that improved the architecture
After the site was live I came back to add a read time estimate to blog posts. Rather than going straight to implementation, I started with a product question: what is the lightest established pattern for this? Three options were presented with a clear recommendation for the simplest. That framing before writing any code saved a decision I would otherwise have had to make mid-implementation.
Before anything was written I also raised an architectural question. The post-loading logic in app.rb was already growing and was not the right place for it. That conversation led to lib/post.rb as the right home. Both the post index page and the individual post template got read time added inline with the date, and app.rb became noticeably thinner.
A direct “add read time” instruction would have produced a working feature bolted onto an increasingly cluttered app.rb. The architectural detour produced both the feature and a better structure. Claude did not initiate that detour. I did.
Reflections
I spent most of the session thinking about what I wanted rather than how to build it. Moving from idea to implementation usually means switching modes, opening the terminal, writing code, checking output, and finding your way back to the bigger picture. In this session that switch happened much less often. Having my editor open alongside meant I was not just issuing instructions and waiting: I could see the code changing, review it, and step in when needed.
There were moments where I had to bring something the tool could not. Noticing the missing .gitignore. Asking about the EU region. Specifying 1rem rather than “make it smaller”. Spotting the weight-bold class. Sharing the favicon audit. Knowing the Buttondown embed was outdated. These came from knowing the domain and the codebase.
Where this leaves me
The tradeoffs were not so different from working with Claude in the browser: you still need to direct, evaluate, and occasionally correct. The meaningful difference was having an agent making changes directly, which kept the session in flow in a way that copying and pasting does not.
What I am curious about now is how it performs with a larger, more complex codebase. Now I’ve set up the perfect environment to experiment further.
Get new posts straight to your inbox
Your email address will never be shared with anyone or used for anything other than receiving posts from this blog.