Skip to content
Notes

Article · Building

How I built this blog in one evening with Claude Code

This site became a blog in English and Ukrainian in one evening. I don’t write code; I built the blog through Claude Code. The build checks every page and won’t publish one with an SEO error.

Max Kiriienko
Tech Lead SEO & Marketing · 7 min read

Читати українською

Until October 3 this site was two HTML pages, one in English and one in Ukrainian. Every change meant editing both files, and the sitemap too. That evening I started a blog. Just after two in the morning the site ran on Astro, with 12 pieces in English, 11 of them also in Ukrainian, and a build that refuses to publish a broken page.

I don’t write code. I work through Claude Code: I say what I want, read what the agent built and check the live page. How that works across nine projects is a longread of its own.

The evening, hour by hour

Ukrainian time, from the chat log, the commit history and Cloudflare’s deploy log.

TimeWhat happened
Oct 3, 21:42The idea, in one message: go through my projects and chats and see what could grow this site into a blog, case studies and more pages.
21:47Six research agents start. Five read my projects and my chats with Claude Code. One studies the market and what people search for.
22:19The plan and a prototype with four pilot pieces are saved. The plan gives the move to a new engine, the pilots and analytics about one working day.
23:26The site moves to Astro and gets a blog in English and Ukrainian: 73 files, 9,801 new lines.
23:34Fixes after proofreading. The new site goes live.
Oct 4, 00:13Charts and Tag Manager.
01:25Structured data, llms.txt, security headers, IndexNow, stricter checks.
02:01Seven more pieces. 12 in total.

The working day took an evening: the new site was live 75 minutes after the plan, and Tag Manager about 40 minutes after that. The 9,801 lines sound bigger than they are. Half of them are a lockfile: a list of library versions that npm, the package manager, writes by itself.

What the move’s 9,801 new lines were made of

Of 9,801 new lines: 5,012 are a lockfile npm writes by itself, 2,408 are site code and scripts, 1,116 are five pieces in English and Ukrainian, 1,265 are research notes, design drafts and other files.

A line is a rough measure: the lockfile is generated, and the pieces are text, not code. 73 files in one commit. Source: git history of mxkeey.com, the commit of Oct 3, 2026, 23:26

Two things weren’t part of that evening. The look, the fonts and the light and dark themes came from August, when I rebuilt the homepage, and the move kept them. And the agents had three months of my projects and chats to read.

Why Astro on Cloudflare

Astro turns Markdown files into plain HTML pages when the site is built. Cloudflare serves those files, and nothing runs on a server. Every push to the main branch on GitHub makes Cloudflare build the site and put it live. On my laptop a build of about 30 pages takes three seconds.

Each piece is one Markdown file, and its folder sets the language and the section. A file in en/blog/ becomes a page under /blog/, and the same name in uk/blog/ becomes its pair under /uk/blog/. The sitemap, an RSS feed for each language, the language links and a share picture for every piece are built from those files. Nothing is edited by hand twice.

Why this matters for SEO:

  • Every bot gets the full page. The text is in the HTML, so crawlers and AI assistants read what a person reads, without running JavaScript.
  • One address per page. Cloudflare’s test addresses for the site are switched off, so no copy lives on a second domain. Every URL ends with a slash, and the canonical tag uses the same form.
  • Honest dates. The last-modified date in the sitemap comes from the piece, not from the time of the build.

Two languages without a plugin

English lives at the root, Ukrainian under /uk/, at the same path. Two files with the same name are one piece in two languages. Each page names both versions in its head, with English as the default for everyone else. The sitemap and the structured data link the same pairs. A piece that exists in one language gets no language links at all, rather than a link to a page that doesn’t exist.

The build checks that every language link leads to a page that exists and links back. Google’s documentation explains why: “If two pages don’t both point to each other, the tags will be ignored.”

I write in English. A script translates each piece into Ukrainian with a model I chose in a blind test: three models, ten paragraphs, three judges. How that test went, and why a smaller one could have picked wrong. The script keeps Markdown and code as they are and points links to the Ukrainian pages. It writes numbers the Ukrainian way and carries the word rules the test produced. Then the Ukrainian version gets a proofread.

Two more checks keep each language in its language. The build fails if an English page shows Cyrillic, apart from the language switch and words marked as Ukrainian. It also fails if a Ukrainian piece is less than 60% Cyrillic letters, a sign that part of it stayed in English. Such a page is in the wrong language for its hreflang and almost a copy of the English one.

A build that refuses to ship: the SEO checks

After every build a script reads every finished page. Any problem fails the build. Then Cloudflare doesn’t deploy, and the live site stays on the last good version.

The build checksThe bug it catches
Title over 66 charactersGoogle may cut it off in search results.
Description over 162 charactersThe snippet may end mid-sentence.
Not exactly one H1None means the template lost the heading. Two blur the topic. Google may also take the title it shows from the heading.
Not exactly one structured-data graph, or an article whose author isn’t the site’s PersonTwo blocks can name two different authors. One graph ties every piece to one person across the site.
Canonical not equal to the page’s own addressThe page tells Google it’s a copy of another page.
No share imageA link shared in a chat or on LinkedIn shows no picture.
A language pair that’s missing or doesn’t point backGoogle ignores the language links.
Broken internal links and anchorsDead ends for readers and crawlers.
Cyrillic on an English page, or a Ukrainian piece under 60% CyrillicA page in the wrong language for its address.
Leftover draft markersUnfinished text goes live and gets indexed.
A Telegram announcement quotes a line that is not in the page word for wordThe announcement quotes a sentence the piece does not have.
Two sentences of an announcement say the same thingThe channel and LinkedIn get the same thought twice.
A noindex page in the sitemapMixed signals: the sitemap asks Google to index a page that asks not to be indexed.
llms.txt missing an English pageAI assistants get an outdated map of the site.
A missing RSS feed, English or UkrainianFeed readers miss new pieces.

Every page also needs a language, a title and a description. One check is personal: the site gives my location only as Ukraine, and the build fails if a page names my city.

I broke a copy of the build on purpose, with a long title and a draft marker left in the text. The build stopped:

✗ Site check failed — 2 problem(s):
  - /blog/five-times-the-text/index.html: title is 71 characters (max 66): I made the text five times longer and my rankings fell in Google search
  - /blog/five-times-the-text/index.html: draft marker left on the page

Most of these bugs are invisible on the page. A language link to nowhere or a canonical pointing elsewhere looks fine in the browser. You find it weeks later in Search Console. Here it stops the deploy.

The check has a limit: it reads the files before they go live. What Cloudflare actually serves, such as headers and redirects, exists only on the live site. A check of the live site after each deploy is next on my list.

Charts without JavaScript

Every chart on this site is drawn when the site is built, as HTML and CSS. A line chart adds a small inline SVG for the line itself. There’s no chart library and no script.

A piece carries one line where a chart goes, and the build swaps it for the chart in the page’s language. The numbers live in one data file, each chart with its source. In Ukrainian, the same chart writes 12,322 as 12 322.

A line chart of monthly searches with one spike of 12,322 and a dashed line at 1,200, on a light background The same line chart on a dark background, with the same text and lines in light colors
One chart from my Ahrefs article in the light and dark theme, at phone width. Screenshots of the live page, October 4.
  • Text stays text. Titles, labels and numbers are real text that search engines and AI assistants can read. Each chart also has a short summary for screen readers.
  • Dark mode and phones work. Colours come from the site’s theme, so a chart switches with the page. Labels aren’t part of a picture, so they don’t shrink with it.
  • Nothing extra to load. No script runs before the chart appears.

Five types cover everything so far: bars, columns, stacked bars, lines, and dumbbells, which show a before and an after on one row. By October 4 the site had 25 charts.

Analytics through the API

Apart from giving a service account access, I didn’t click through the Google Analytics and Tag Manager screens. A script created the GA4 property, the Tag Manager container and the Google tag inside it, through Google’s APIs. It runs a dry run first, and the measurement ID comes from the API’s answer, never typed by hand. The same evening the sitemap went to Search Console through the API.

Then the check that counts: a page view request leaving the live page. The setup, the permissions and the errors I hit are in the Search Console and GA4 playbook.

What comes next

The notes page shows filters by topic and format only from eight pieces up. With fewer, most combinations come back empty. The site passed eight on October 4, so they’re already on.

Next: the check of the live site after each deploy, and two new chart types, a timeline and a colour-coded table. The timeline above is a plain table because that type doesn’t exist yet. A page about my services will come later.

In the first two weeks, October 3 to 17, Search Console counted 141 impressions of the site in Google Search and 6 clicks. Eleven pieces already have impressions.

About the author

Max Kiriienko

For ten years I’ve worked in SEO and marketing. I draw up a growth plan, then build the tools and teams that carry it out every day.

Read next