These few days were mostly spent tuning this blog.
At first I only wanted to make the Hugo blog feel a little more like the Claude Blog. Then the work expanded from theme, fonts, covers, mobile proportions, navigation, and tags all the way to multilingual pages, GitHub submodules, and server sync. Looking back, “blog optimization” was not about one button, one CSS block, or one cover image. It was about slowly making the whole writing and publishing experience feel smoother.
Starting with the Theme
The first direction was to find a theme closer to the tone of the Claude Blog. I could keep imitating that style on top of PaperMod, and it would work, but it was easy for the site to turn into a pile of patches. Later I found the LoveClaude theme. Its visual structure was already close to what I wanted, so the blog moved from PaperMod to LoveClaude, with site-level overrides for nouo details.
The biggest lesson here was to separate theme source from my own override styles. The theme should provide the base structure; custom files should define the site’s personality. Otherwise, every small visual change touches theme files, and later it becomes hard to tell whether a problem comes from the upstream theme, my overrides, or Hugo build output.
When changing the default view of the post list, I also ran into submodule permission issues. themes/LoveClaude originally pointed to the upstream repository. I could edit it locally, but I could not push directly to someone else’s repo. The final solution was to fork it to my own GitHub account, point .gitmodules to the writable fork, then commit theme changes and the main repository pointer separately. It was a little tedious, but it opened up the future maintenance path.
Fonts Are Not a Small Detail
Fonts were the most troublesome part of this round.
At first I thought writing Anthropic Sans, Anthropic Serif, and Anthropic Mono in CSS would be enough to get the Claude feeling. That was wrong. Without actually loading the font files, the browser only sees names that do not exist and falls back to system fonts. So I added @font-face and made English text, numbers, punctuation, and code actually use Anthropic Sans, Serif, and Mono.
Chinese text is a different problem. Anthropic fonts do not contain Chinese glyphs, so Chinese must continue to fall back. I went through several versions: first I wanted the whole site to use the Claude fonts, then realized Chinese falling back to sans-serif did not match the reading tone; then I switched to serif, only to find that many mobile devices do not have Songti SC, so the CSS does not actually take effect there.
The steadier solution became:
English and code: Anthropic Sans / Serif / Mono
Chinese reading: Songti or Source Han Serif fallback
Mobile Chinese: self-hosted Noto Serif SC as a fallback
There was also a cache trap. When the font files first went online, Cloudflare cached a 404 for the bare path. The files existed on the origin, but user requests still hit the old 404. Adding a version parameter to the font URL made the new CSS request a new address, which finally closed the loop.
This reminded me that fonts are not finished by writing one font-family. Whether the font file exists, whether cross-origin loading is allowed, whether it includes Chinese glyphs, whether the mobile device has the system font, and whether the CDN cached an old response can all make the visual result differ from what the CSS suggests.
Images and Covers
Another focus was cover images.
I initially wanted to build a cover system similar to the Claude Blog. I even tried generating a few minimalist SVG card samples. They were not unusable, but they were clearly not mature enough. They looked more like ordinary flowcharts than article hero images. I am pausing that direction for now. If I continue later, I should first break down the reference style and then build templates, instead of drawing a one-off placeholder.
The more practical solution now is: posts use a local image pool by default, and the post-creation script randomly selects a cover and writes it into front matter. This keeps images in the repository together with posts, so migration and rollback are traceable.
This round also confirmed one principle: for ordinary personal blog covers, local files are steadier by default. An image host can be a fallback, but article content should not depend on an external service that may enable hotlink protection, return 403, or change rules at any time. The HostDZire review was an example: direct image-host downloads returned a 403 placeholder unless the correct Referer was sent. In the end, I saved the four test images locally and updated the article to local paths before trusting the online result.
On June 20, I added one more small fix: the random cover script now avoids covers that were used recently, reducing the chance that several consecutive posts show the same image. It is not a visual-system rewrite, but for a blog with more posts, this kind of small automation removes a surprising amount of repeated checking.
Mobile Is Not Just a Smaller Desktop
The mobile layout also changed a lot in this round.
Some issues looked like “not synced”, but were actually old mobile breakpoint rules still taking effect. For example, the homepage hero still filled an entire screen on the phone, pushing the latest posts below the first viewport, so it felt like the desktop changed but mobile did not. Later the opposite happened: mobile looked right, but desktop felt too high. In the end I used PERSONAL BLOG as an anchor and adjusted against viewport height:
Mobile: move the hero down so the first screen feels fuller
Desktop: move the hero up, then let it settle back a little
The interesting part here was svh and lvh. Mobile browsers have address bars, so 100svh and 100lvh are not the same thing. To make the homepage really fill a phone screen, it is not enough to look at desktop browser simulation; the real mobile viewport decides where the next section starts.
I also reorganized the homepage navigation. On desktop, it keeps the original theme placement. On mobile, “Posts / Timeline / Tags” become a vertical group to fill the first screen. Later I fixed a few related issues: desktop accidentally becoming horizontal, position drift, button groups not aligning to the right, footer badges wrapping badly. Each issue was small, but together they made the blog feel much steadier on a phone.
Today I also finished the post-list pagination behavior. Filtering and pagination used to keep separate state, so after moving to page two, filtering by tag could leave stale page state behind. Now filtering recalculates pagination and returns to page one, while page changes scroll back to the top of the list. I also cleaned up the active navigation mask on secondary-page headers, so the current-page highlight no longer creates an awkward dark layer over the header.
Language Switching and Static English Pages
The largest June 20 change was multilingual support.
The first idea was to add a language switcher that could move a page between Chinese and English. The initial version leaned on client-side translation: the frontend called a same-origin /api/translate endpoint, and nginx proxied the request to the translation service. That kept the browser connected only to my own domain and made CSP easier to contain. But that path is better for temporary reading than long-term post maintenance: translation output is not controlled, and SEO and sharing are much steadier with real static pages.
The final shape uses Hugo’s static multilingual structure. Chinese remains the default path, while English lives under /en/. Posts, archives, search, homepage copy, menus, previous/next links, related posts, table-of-contents labels, reading time, and word counts now all have English versions. The head also includes hreflang and x-default, so search engines can understand the relationship between Chinese and English pages.
Language choice now has several layers working together:
Hugo generates Chinese and English static pages
The language button links to the matching translation page
nginx picks a default language for first-time visitors from browser language
manual user choices are remembered in a cookie
a prepaint script in head provides an early fallback and reduces first-paint language flash
The lesson is similar to the publishing flow: language is not finished by adding a button. URL structure, static content, browser language, manual selection, cache behavior, SEO metadata, and first paint all need to close together before the site feels genuinely bilingual instead of temporarily translated.
Content and Tags
Besides the theme itself, I also reorganized the content structure.
These few days I added and cleaned up several posts: common VPS scripts, HostDZire reviews, a Hong Kong Lightlayer VPS review, an Estonia eSIM guide, and home media library automation. After that I tightened the tag system into a few groups:
Blog
Tinkering
VPS Review
Hosting VPS
Route VPS
This looks like just front matter, but it matters for long-term maintenance. If tags are too casual, they become a pile of similar words. If tags are too few, they lose search value. The current grouping is clearer: the site-building process goes under “Blog”; tool scripts, eSIM notes, and home media library work go under “Tinkering”; HostDZire reviews go under “VPS Review” and “Hosting VPS”; and the Lightlayer review goes under “VPS Review” and “Route VPS”.
Today I also normalized several VPS review posts. Units, extra commentary, and paragraph length were tightened so the reviews read more like one reference series instead of separate one-off drafts.
The Publishing Flow Also Needs Verification
Another class of problems was neither code nor style, but “I clearly pushed, so why does it look unchanged?”
This round I repeatedly checked several layers:
local HEAD
origin/main
GitHub remote reference
server systemd sync
online HTML/CSS fingerprints
Cloudflare or browser cache
Sometimes GitHub had already updated but the server timer had not pulled yet. Sometimes the server had published but the browser cache was still old. Sometimes the online HTML had already switched to a new CSS fingerprint, but a bare font path was still cached as an old 404 by the CDN. There was also one case where a normal Chrome window redirected the bare nouo.net address to another domain, while an incognito window was fine. It turned out Chrome had cached a bad redirect for the exact URL.
All of this says one thing: publishing does not end at git push. For a static site, the real finish line is when the online page loads the new HTML, new CSS, new images, and all key resources return 200.
Summary
After this round, the blog is closer to what I wanted:
- the theme moved to LoveClaude, while keeping my own override layer;
- Anthropic fonts are connected, and Chinese fallback is steadier;
- the post script can assign local covers randomly;
- the mobile homepage, navigation, footer, post-list pagination, and secondary-page headers feel more like one complete design;
- static Chinese and English pages, language selection, nginx default language, and
hreflangrelationships have an initial closed loop; - the tag system and review-post formats have been tightened;
- submodules, remotes, server sync, and online verification are clearer.
Tuning a blog can easily become a loop: change a little, look at it, feel unsatisfied, change again. It does not have a clean finish line like a business feature. But every time one small issue is traced to the end, the site loses a little roughness. This blog is still not in its final form, but it has at least moved from “usable” toward “smooth, pleasant, and maintainable”.



