Contents

Hugo + LoveIt Blog Optimization Notes: From Setup to a Reproducible Config

A practical recap of the optimizations applied to this site, so the setup can be reproduced against the current repository.

1. Background and goals

This personal blog runs on Hugo Extended + LoveIt, with these requirements:

  • Chinese as the default language, with an English switch
  • Draft in Obsidian / Typora, then auto-fill front matter, cover image, English version, and publish
  • Deploy on Cloudflare with a custom domain
  • Comments, on-page counters, and backend analytics each in place
  • Code blocks and videos that do not break the layout

Each section lists paths and config points that match the current repo.


2. Project layout and local commands

This chapter defines the repository layout, local commands, and ignore rules—the baseline for template overrides, styles, and deploy.

2.1 Key directories

.
├── archetypes/          # new-post templates
├── assets/css/          # _custom.scss theme overrides
├── content/posts/       # posts (zh .md / en .en.md)
├── drafts/              # local drafts (gitignored)
├── layouts/             # theme overrides (do not edit themes/LoveIt)
├── static/              # images, videos, favicon
├── themes/LoveIt/       # Git submodule
├── .cursor/skills/      # hugo-blog-publish / loveit-theme
├── build.sh / wrangler.jsonc / package.json  # Cloudflare build
├── hugo.toml
└── Makefile

2.2 Common commands

make dev      # hugo server -D
make build    # HUGO_ENV=production hugo --minify
make clean
Note
LoveIt loads comments, CDN, and some analytics scripts only when hugo.Environment == production. To verify comments locally, run: HUGO_ENV=production hugo server

2.3 .gitignore

public/, resources/, .hugo_build.lock, .cache/, and drafts/ are ignored.

  • Does not block deploy: Cloudflare rebuilds public/ with hugo build in CI; the repo does not need build artifacts.
  • Keep drafts/ local; publish into content/posts/ after review.

3. Fixing Hugo deprecation warnings

Newer Hugo warns about .Site.LanguageCode, .Language.LanguageName, .Sites, and the languageCode config key.

Rule: do not edit the theme submodule; override templates under project layouts/.

Override fileChange
layouts/baseof.htmluse .Site.Language.Locale for lang
layouts/_partials/header.htmllanguage switch via hugo.Sites and .Language.Label
layouts/_partials/head/seo.htmlLocale as above
layouts/_partials/init.htmldrop noisy dev warnings; attach comment / analytics in production
layouts/home.rss.xml and other RSSLocale as above

On the config side, use locale (this site: Chinese zh-CN, English en) instead of deprecated languageCode.


4. Chinese default and bilingual content rules

Default language and file naming must stay consistent, or the language switch shows an empty post list.

4.1 Site languages

In hugo.toml:

  • defaultContentLanguage = "zh-cn"
  • [languages.zh-cn] with a smaller weight (primary)
  • [languages.en] for the English site

4.2 File naming (important)

When Chinese is the default:

LanguagePath
Chinesecontent/posts/{slug}.md (do not use .zh-cn.md)
Englishcontent/posts/{slug}.en.md

About pages follow the same pattern: content/about/index.md + index.en.md.

If English lives in bare .md and Chinese in .zh-cn.md, switching to /en/ will hide English posts—an early footgun on this site.

Set [params.home.profile] social = true and configure [params.social] (GitHub, Email, RSS, etc.). Icons appear on the home profile only then.


5. Writing and publish workflow

Conventions from draft to live: front matter, covers, media paths, and how Cursor skills help publish.

5.1 Minimal publish flow

  1. Write Chinese Markdown in Obsidian / Typora / drafts/
  2. Publish with Cursor skill hugo-blog-publish (default: front matter, cover, English translation, hugo build, commit + push)
  3. Skip push when only local verification is requested; skip .en.md when Chinese-only is requested

5.2 Front matter convention

---
title: ""
date: 2026-07-13T12:00:00+08:00
draft: false
description: ""
categories: []
tags: []
featuredImage: "/images/posts/{slug}/featured.jpg"
featuredImagePreview: "/images/posts/{slug}/featured.jpg"
---
  • Insert <!--more--> after the first paragraph for the summary
  • Chinese and English share the same cover / media paths

5.3 Media paths

TypeDirectoryIn-content URL
Cover / imagesstatic/images/posts/{slug}//images/posts/{slug}/...
Videosstatic/videos/posts/{slug}//videos/posts/{slug}/...

Recommended video markup:

<video controls playsinline preload="metadata" src="/videos/posts/{slug}/demo.mp4"></video>

External image URLs and Bilibili / YouTube embeds stay as-is; Bilibili can use LoveIt’s bilibili shortcode.

5.4 LoveIt writing skill

There is also a loveit-theme skill (linked from publish): quick reference for admonition, image, mermaid, math, Giscus, visit counters, and more. Publish may lightly upgrade tips to admonition or Bilibili links to shortcodes.

Official docs: LoveIt.


6. Layout fixes: code and video

Production issues: long code lines overflow the code box; <video> width did not match content width. Fixed in assets/css/_custom.scss:

  • Code blocks: max-width: 100%, overflow-x: auto, pre-wrap when needed
  • video: width/max-width: 100%, display: block, height: auto

Still wrap extremely long command lines in the source for readability.


7. Deploy to Cloudflare Workers

Static hosting follows the official Hugo Cloudflare guide; this site uses Workers static assets.

7.1 Repo files

FileRole
wrangler.jsoncWorker name (e.g. simons-blog), assets.directory = ./public
build.shInstall Hugo Extended, update submodule, HUGO_ENV=production hugo build
package.jsonHelps enable build cache

The Cloudflare project name should match name in wrangler.jsonc.

7.2 Dashboard notes

  • Static-only Worker: do not set HUGO_BASEURL under runtime Variables and Secrets
  • Put build-related vars under Build variables, or hardcode baseURL in hugo.toml (this site uses https://blog.leeissonba.com)
  • Bind the custom domain under Workers → Domains

7.3 Submodule

The theme is a submodule. Cloudflare build must run git submodule update --init --recursive; local clones need the submodule too.


8. Comments: Giscus

Valine (LeanCloud) was considered; the site settled on Giscus (GitHub Discussions).

8.1 Prerequisites

  1. Repository is Public
  2. Discussions enabled
  3. Install the giscus GitHub App and grant this repo

8.2 Site config (hugo.toml)

[params.page.comment]
  enable = true
  [params.page.comment.giscus]
    enable = true
    repo = "simonlee-hello/MyBlog"
    repoId = "R_kgDOTVNnWg"
    category = "Announcements"
    categoryId = "DIC_kwDOTVNnWs4DBC6N"
    mapping = "pathname"

repoId / categoryId are public GitHub GraphQL node IDs. They appear in page source and are not secrets—safe to publish with the site config.

Disable per page with comment: false.
Loaded only in production builds; locally use HUGO_ENV=production.

Comment data lives in the repo’s Discussions.


9. Analytics: two complementary layers

PurposeToolNotes
Owner dashboardCloudflare Web AnalyticsZone covers leeissonba.com; blog.* traffic already shows up—filter by Host
On-page numbersSelf-hosted Worker + D1 (services/web-analytics)Footer site PV/UV + post meta page views; you own the data

Cloudflare Web Analytics cannot render counts on the public page (no public front-end API). Busuanzi/Vercount were tried earlier and dropped because third-party counters can reset.

This site follows analytics_with_cloudflare, with site-wide spv / suv as in Hugo self-hosted visit counters. Reproduce against the repository.

9.1 Architecture

  • Separate Worker web-analytics (not the blog static Worker myblog / simons-blog)
  • D1 database web_analytics stores visits
  • Browser POST /api/visit: insert one row; optionally return page pv and site spv / suv
  • Hugo loads the script only in production (same gate as Giscus)

9.2 Deploy the counter API (Cloudflare)

Work in the subdirectory (do not confuse with the root Hugo wrangler.jsonc):

cd services/web-analytics
npm install
npx wrangler login
npx wrangler d1 create web_analytics
# Write the returned database_id into this directory's wrangler.jsonc
npm run initSql
npm run deploy

Notes:

  • npm run deploy passes --config wrangler.jsonc. Using the repo-root config will try Hugo’s build.sh and fail
  • Dashboard → Workers → web-analytics → add a custom domain such as analytics.leeissonba.com (workers.dev is blocked in some regions)
  • Smoke test: POST https://analytics.your-domain/api/visit with hostname, url, spv: true, etc.; expect {"ret":"OK","data":{...}}

9.3 Wire Hugo front end

Key files:

PathRole
services/web-analytics/Worker + D1 source and wrangler.jsonc
assets/js/analytics.jsCall API; fill #page_pv / #site_pv / #site_uv
layouts/_partials/plugin/web-analytics.htmlInject fingerprinted script in production only
layouts/_partials/footer.htmlSite PV/UV placeholders + load script once
layouts/posts/single.htmlPost meta “views” #page_pv
hugo.toml[params.webAnalytics]Toggle and baseURL

Config example:

[params.webAnalytics]
  enable = true
  baseURL = "https://analytics.leeissonba.com"  # must match custom domain

Footer placeholders:

Site visits <span id="site_pv">-</span> · Visitors <span id="site_uv">-</span>

Post meta placeholder:

Views <span id="page_pv">-</span>

Implementation notes:

  • Load the script once in the footer to avoid double counting
  • Do not use the upstream demo’s document.head.innerHTML += … (rebuilds <head> and can break theme scripts)
  • Local check: HUGO_ENV=production hugo server; plain hugo server does not load or count
  • Push the blog repo and wait for the static rebuild before numbers appear in production

9.4 Acceptance

  1. Open any post: meta views show a number; refresh should increase it
  2. Footer site PV / UV show numbers
  3. In Cloudflare, check request volume on the web-analytics Worker (not the zero-request static myblog app)

10. Config switch cheat sheet

# comments
[params.page.comment]
  enable = true

# on-page counters (self-hosted Worker + D1)
[params.webAnalytics]
  enable = true
  baseURL = "https://analytics.leeissonba.com"

# home social icons
[params.home.profile]
  social = true

Keep theme overrides under project layouts/ and assets/ so LoveIt submodule upgrades stay smaller.


11. Reproduction checklist

When aligning a fresh copy with this site:

  1. Hugo Extended + git submodule update --init
  2. hugo.toml: baseURL, default language, social, Giscus, webAnalytics
  3. Cloudflare blog project name matches root wrangler.jsonc name
  4. layouts/ deprecation overrides + footer / posts/single / web-analytics partial
  5. assets/css/_custom.scss for code and video; assets/js/analytics.js in place
  6. Cloudflare blog: connected repo, build script, custom domain, HUGO_ENV=production
  7. Giscus App installed; Discussions enabled
  8. (Optional) Web Analytics filter for blog. host
  9. Deploy services/web-analytics: D1 + Worker + custom domain matching baseURL
  10. Cursor skills: hugo-blog-publish + loveit-theme
  11. Smoke-test one post: bilingual, featuredImage, local media paths, <!--more-->; verify views in production

12. References