My articles were on Blogger and LinkedIn, my projects were on GitHub, and the rest of my professional background was somewhere between them. Each platform held part of the story, but I wanted one place that brought it together.
The idea had been in the back of my mind for a long time. So I decided to build a website with a domain that carries my name: ahmedmoniem.dev.
I still use those platforms. I just wanted a place where I could decide how my work is presented, how people find it, and how I maintain it.
I was tired of working around Blogger
My old Blogger site held years of articles and thoughts. It gave me a place to write, and that mattered. But as I kept writing technical content, the publishing workflow became frustrating.
Changing the design took more effort than I wanted. Keeping headings, code examples, and spacing consistent was difficult. I wanted to write in Markdown, but my workflow around the editor still needed extra work.
I also wanted to manage an article the way I manage code: make a change, review the difference, and keep its history in Git. I did not want another workaround just to connect writing with version control.
Yes, I was frustrated with Blogger. But the useful question was not whether Blogger was bad. It was whether it still suited the way I wanted to work.
For me, it no longer did.
A content website does not need to become a big application
Most of this website is text, code examples, images, and links. Readers should be able to open an article and read it. I did not need a custom backend and a database just to serve those pages.
I chose Astro and a static build. The article files stay in the repository, and the build turns them into pages that Cloudflare Pages serves.
Astro’s content collections let me define the metadata each article needs, such as its title, summary, publication date, and category. That gives the content a consistent structure without making me format every page separately.
The shared layout handles the presentation. If I improve the way a code block or a table looks, I can apply that improvement across the articles instead of editing each one.
There is a trade-off, of course. I now maintain the code, dependencies, and build process as well as the content. For someone who only wants to open an editor and publish, a hosted blogging platform may still be the better choice. I wanted this level of control, so that maintenance is part of the decision.
Bringing the old articles with me
Starting a new website did not mean I wanted to leave the old writing behind.
I added an importer that reads the Blogger export and converts posts into Markdown. It carries over metadata such as dates, labels, and the original post paths. But converting a file is not the same as finishing the migration. Code blocks, images, summaries, and formatting still need review.
I extracted it into a separate public repository: Blogger to Markdown. It includes the importer, a sample export, tests, and a README explaining how to use it. The standalone version also adds safeguards around validation and writing files; it is not just a copy of the original website script.
The flow is straightforward. First, it reads the XML export and selects the post entries, leaving comments out. Then it sanitizes the HTML, converts it to Markdown, and adds metadata above the article body. Drafts stay marked as drafts. Before writing anything, it checks the destination filenames so it does not replace articles you have already edited. It also writes a manifest that connects the old paths with the new filenames.
With Node.js 22.12 or newer installed, you can try the included sample without using your own blog data:
git clone https://github.com/ahmedamoniem/blogger-to-markdown.git
cd blogger-to-markdown
npm ci
npm run import -- --input fixtures/blogger-export.sample.xml --dry-run
npm run import -- --input fixtures/blogger-export.sample.xml --output tmp/sample
The dry run shows what the importer plans to create without writing files. The last command writes one Markdown article and a manifest under tmp/sample; the comment in the sample export is skipped. If you run it again, choose a fresh output directory rather than overwriting the first result.
The tested source revision also includes an AI recreation prompt. If you use it, review and test the generated version before running it against your own export.
The old URLs also needed care. The new site generates redirects from the old Blogger-style paths to the new article routes. Those redirects work on my domain; they do not take control of links on blogspot.com.
That distinction matters. Having a redirect file does not automatically mean every link shared over the years now points to the new website.
Making articles easier to find, without hiding them behind controls
Once the articles were in place, I added categories, topics, year groups, and search. Pagefind builds a search index from the generated site, so I did not need to build and host a separate search service.
Then I ran into another problem: the archive had too much going on before readers reached an article.
There were large category cards, headings, descriptions, and more descriptions explaining the headings. Some information cards looked clickable even though they did nothing. The controls meant to help people find content were taking space away from the content itself.
I reduced the category cards, removed repeated labels and counts, and moved the topics under the search box. I also tried filtering topics on the same page, then returned to ordinary topic links after the interaction became troublesome.
That was a useful reminder: adding more ways to navigate does not always make navigation easier.
And yes, dark mode was a must for me. But checking light mode exposed problems too, including a footer whose text was hard to read. Liking one theme is not a reason to ignore the other.
Using AI did not remove the need to check the result
I used AI while building this website, including implementation, code review, and content editing. I also used GitHub issues to break the work into smaller changes instead of treating the whole website as one task.
But I still had to look at what those changes actually did.
One review suggestion was to add React-style key attributes to list items. That sounded reasonable, except these were Astro templates, and the change failed type-checking. In another case, the search results appeared inside the narrow search column while the full archive remained visible underneath.
Those are different failures. A build can catch an invalid attribute, but it will not tell me that the results look squeezed or that the footer feels wrong.
The process became a repeated cycle of making a change, running checks, opening the page, and trying it. That included phone-sized layouts, both themes, and simple actions such as typing a search and clearing it again.
AI could help with the next change. I still needed to decide whether that change solved the problem.
Publishing should be the repeatable part
The site uses Cloudflare Pages’ Git integration. Changes go through a pull request and a preview deployment before reaching the production branch. The build runs checks and tests, generates the pages, and creates the search index and redirects.
Wrangler, Cloudflare’s command-line tool, is useful for checking the built site locally in its Pages development environment. That matters for behavior such as redirects, which should not be judged only through Astro’s development server.
The website now also has RSS, social preview images, and comments. But not every planned section needs to be visible immediately. I hid the Talks navigation link until there is content worth putting behind it.
That is the direction I want to keep: improve what helps readers, and leave the rest until there is a reason to build it.
The point of this website was to make it easier to publish and connect my work. If maintaining it takes all the time I meant to spend writing, I have only replaced one frustrating workflow with another.
Discussion
Questions or a different approach? Join the discussion with your GitHub account. Comments are public and stored on GitHub. View discussions on GitHub.
Comments could not load. You can use the GitHub link above instead.