I started this migration because I wanted to remove the GitHub sign-in barrier from the comment box. Once I began, it quickly grew into a wider job involving reply emails, historical pageviews, and the ownership of imported comments. This post records what changed and the parts that needed more care than I expected.
Every post now uses a new Waline comment area. Readers no longer need a GitHub account to join a discussion: they can comment as guests and optionally provide an email address if they want to be notified about replies.

Why Move Away from Giscus?
Giscus remains an excellent option for developer-focused sites. It stores comments in GitHub Discussions, requires little infrastructure, and fits naturally into open-source communities. Its main limitation for this site is that every commenter must sign in with GitHub.
The audience here is no longer limited to developers. Posts about tools, life in Malta, travel, and site updates reach readers with very different backgrounds. Requiring a GitHub account creates an unnecessary barrier to leaving a simple response.
Waline brought the things I wanted into one system:
- guest comments without mandatory registration;
- optional email addresses and reply notifications;
- comment likes, article reactions, and comment counts;
- pageview tracking through the same counter service;
- a dedicated moderation interface;
- bilingual UI text and language-aware notification emails.
A reader does not need to sign in, but the service still processes limited technical information for rate limiting, security, and moderation. Nicknames and comments are public; email addresses are not displayed in the comment list.
The New Hosting Architecture
The main blog remains a static Hugo site built by GitHub Actions and published on GitHub Pages. Sveltia CMS is the content interface, while posts and pages continue to live in the GitHub repository.
The comment system has two hosted components:
- Waline Server runs on Vercel and provides the API, administration interface, email notifications, and counters.
- Comments, users, reactions, and pageview records are stored in Neon PostgreSQL.
This does not turn the entire site into a dynamic application. GitHub Pages still serves the articles, and the browser requests Waline only when comments or counters are needed. The articles therefore remain available even if the comment service has a bad day, which was important to me.
The dashboard management menu now links separately to Sveltia CMS for content and Waline for comments. Its system panel also identifies GitHub as the content host and Vercel plus Neon as the comment infrastructure.
Matching Waline to the Existing Theme
Waline already provides an editor, comment list, sorting, Markdown, image uploads, and an emoji picker. I added a theme layer so it feels consistent with the Stack-based design:
- existing card backgrounds, borders, radii, and shadow variables;
- light and dark colour schemes;
- pageview and comment totals inside the comment notice;
- responsive behaviour for narrow screens;
- UI text selected from the current article language;
- Twitter Emoji assets for consistent rendering across systems.
The article reaction row currently offers Love, Funny, Thought-provoking, Surprising, and Sad. These use the Waline Twitter Emoji image preset rather than local emoji files from a visitor’s computer, which keeps their appearance consistent across browsers.
Migrating Pageviews from Cloudflare
The previous pageview service ran on Cloudflare Workers and D1 and already included historical GA4 data. I did not want the new comment system to reset every page to zero, so the existing counters were migrated into Waline.
The Cloudflare snapshot contained 82 raw counter keys. Some Chinese paths existed in both percent-encoded and Unicode forms, even though they referred to the same page. After URL normalization and merging, the migration produced 76 unique paths. The site-wide counter stood at 1,607 views at the time of migration.
Waline stores these values in wl_counter. A subtle issue appeared during the first API-based import: the default table uses a numeric primary key but does not enforce a unique URL. Concurrent updates could therefore create multiple rows for the same path.
The final database transaction:
- retained one counter record for each URL;
- consolidated and removed duplicates;
- added a unique URL index;
- imported historical counts with a never-decrease rule;
- checked for missing, duplicate, and under-counted paths.
The final verification reported 76 target records, no duplicate URLs, no missing URLs, and no counter below the Cloudflare source snapshot. The never-decrease rule also preserved visits that arrived while the migration was running.
Counting Every Site Page
The new counter is not limited to articles. Every non-404 page loads Waline’s lightweight pageview module in the footer. It increments the current path and a reserved path used for the site-wide total.
When an article comment area displays its pageview number, it performs a read-only request. This prevents the footer and the comment component from counting the same page load twice.
The homepage, archives, categories, tags, gallery, links, dashboard, and both language versions now use the same counter service. The old Cloudflare endpoint no longer powers the front-end count.
Email Notifications and Bilingual Templates
Email was the part of this migration most likely to look finished before it actually was. A complete set of environment variables does not guarantee delivery, and a delivered message does not necessarily look right in a real inbox.
During the first test, the comment reached the database but no message arrived. Vercel’s function logs eventually showed Gmail returning 535-5.7.8 Username and Password not accepted. The template was not the problem; SMTP authentication was. After I replaced the credential with a Gmail app password and redeployed the production service, delivery started working.
Two details are easy to miss:
- Gmail SMTP still authenticates with the primary mailbox. A plus address such as
+blogservice-noreplyis useful for labelling mail, but it is not a separate login. - Changing a Vercel environment variable is not enough on its own. The production deployment must be rebuilt before its functions receive the new value.
The sender is now displayed as Jingyuan’s Blog Comment Service. Subjects and bodies follow the language of the page: Chinese messages refer to “小景的博客”, while English messages use “Jingyuan’s Blog”. This keeps the service recognisable in the inbox without forcing both languages into one notification.
Waline templates can read the path of the page where a comment was posted. Since Chinese content is located under /zh/, one conditional template can select the language:
|
|
Comments on Chinese pages receive Chinese email, while other pages use English. The templates include only the relevant nickname, comment text, and article link; system credentials never appear in the message body.
The first delivered version exposed another problem: the comment appeared with literal <p>...</p> tags around it. Waline had already produced sanitised comment HTML, but the notification template escaped it a second time. Rendering those specific fields as the already-sanitised HTML fixed the visible tags and restored normal paragraphs.
I then rebuilt the message for email clients rather than treating it like a small web page. The result uses a conservative single-column card, table-based structure, inline styles, two clearly separated quote panels, and one prominent “View and reply” button. There are no external fonts, scripts, or tracking images. It stays readable in Gmail, Outlook, and on narrow screens; if Outlook drops a rounded corner, the content order still holds.
Mail settings and templates are supplied through Vercel environment variables. SMTP credentials, the app password, and database connection details remain sensitive hosting variables; they are not committed to the Hugo repository or exposed in front-end code. SMTP uses the app password rather than the account’s everyday login password.
Repairing Comment Ownership During Import
Comments were exported to Waline JSON before being imported into the new database. The migration retained comment text, article paths, reply relationships, timestamps, and moderation status.
Some older comments written by the site owner were missing their user association in the export. I restored only the administrator user ID and public profile metadata. The original text, page, relationships, IDs, and timestamps were not changed.
Without that repair, old owner comments could appear as ordinary guest messages, with inconsistent avatars, author labels, and moderation ownership.
Privacy and the New Comment Notice
The comment notice now says:
Guest comments are welcome. If you would like to receive reply notifications, add your email address and the system will notify you automatically when someone replies.
An email address is not required to post. When supplied, it is used for reply notifications and avatar matching and is not displayed in the public comment list.
The site’s Privacy Policy has also been updated to explain the roles of GitHub Pages, Vercel, Neon, and Waline and the information involved in posting a comment.
What Comes Next
The migration only felt complete when a clean test reply appeared in Outlook. A successful database write is one milestone; the comment box and message a reader actually sees are the system that matters.
I will keep watching delivery and spam placement, mobile editing, anti-spam behaviour, and counter consistency. If a comment fails to load, a reply does not arrive, or a counter looks wrong, leave a note below—no GitHub account required.