[{"content":"If you want a personal blog that is fast, beautiful, and completely free — no ads, quick loading, and a full-featured comment system (emoji, reactions, anonymous commenting\u0026hellip;) — this combo is the perfect choice in 2026:\nHugo (a blazing-fast static site generator) Stack Theme (modern, minimalist design with a gorgeous dark mode) GitHub Pages (free hosting) Waline (open-source comment system, self-hosted on Vercel + Neon Postgres, supports anonymous comments that appear instantly) Prerequisites A GitHub account (public repo) A Vercel account (free, sign in with GitHub) A Neon account (free, serverless Postgres) A machine with Git + Hugo installed (extended version recommended) Step 1: Clone the Hugo Stack Template and Set Up the Basic Site Go to the official starter repo:\nhttps://github.com/CaiJimmy/hugo-theme-stack-starter\nClick Use this template → Create a new repository\nName your repo: For a root domain: username.github.io (e.g., pngocthach.github.io) For a subpath: any name (e.g., my-blog) → your domain will be username.github.io/my-blog Clone the repo to your machine:\ngit clone https://github.com/username/username.github.io.git cd username.github.io Install Hugo if you haven\u0026rsquo;t already:\nmacOS: brew install hugo Windows: use Scoop/Chocolatey or download the extended binary from https://github.com/gohugoio/hugo/releases Linux (varies by distro): sudo apt install hugo (or extended) Verify: hugo version (should be ≥ 0.120)\nRun the site locally:\nhugo server Open http://localhost:1313 — if you see the Stack demo page, you\u0026rsquo;re good to go.\nDeploy to GitHub Pages:\nGo to your repo\u0026rsquo;s Settings → Pages Source: Deploy from a branch → Branch: main → Folder: / (root) → Save The template already includes a GitHub Actions workflow (.github/workflows/hugo.yml), so every push will automatically trigger a build. Your site will be live within 1–3 minutes at https://username.github.io (or /my-blog)\nStep 2: Set Up the Waline Comment Backend (Vercel + Neon Postgres) Waline is a lightweight, open-source comment system that supports anonymous commenting and beautiful emoji/reactions.\nDeploy Waline on Vercel (free):\nVisit: https://waline.js.org/en/guide/deploy/vercel.html Click the Deploy button (blue) Sign in to Vercel with GitHub → Name your project (e.g., my-waline-backend) → Create Wait for deployment (1–2 minutes) → Copy your backend URL: https://my-waline-backend.vercel.app Create a Neon Postgres database (free tier):\nIn Vercel Dashboard → Storage tab → Create Database → Select Neon Region: Singapore (aws-ap-southeast-1) (lowest latency from Southeast Asia) Create the DB → Open in Neon console In the Neon SQL Editor → Paste the init script from:\nhttps://github.com/walinejs/waline/blob/main/assets/waline.pgsql\n→ Run to create the tables Connect Neon to Vercel:\nIn Vercel → Storage → Select the Neon DB you just created → Connect Check all Environments: Development, Preview, Production Check Preview for branching (uncheck Production if not needed) Custom Prefix: Leave blank Connect → Vercel will automatically inject the environment variables Create the admin account:\nVisit: https://my-waline-backend.vercel.app/ui/register Register the first user → this account becomes the admin Log in at /ui to manage comments later (e.g., review spam) Step 3: Integrate Waline into Hugo Stack Edit the config file (config/_default/params.toml):\n[comments] enabled = true provider = \u0026#34;waline\u0026#34; [comments.waline] serverURL = \u0026#34;https://waline-backend-iota.vercel.app\u0026#34; # Replace with your Vercel URL lang = \u0026#34;en\u0026#34; reaction = true emoji = [ \u0026#34;https://unpkg.com/@waline/emojis@1.0.1/tw-emoji\u0026#34;, \u0026#34;https://unpkg.com/@waline/emojis@1.0.1/weibo\u0026#34; ] Override the template to display full URLs (easier to manage in the admin panel):\nDuring deployment, you may notice that Waline stores comments using only the relative path (e.g., /p/post-name) instead of the full URL (https://yourdomain.com/p/post-name), making it very hard to identify which post a comment belongs to.\nTo fix this, create the file: layouts/partials/comments/provider/waline.html to override the theme\u0026rsquo;s default.\nFile content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 \u0026lt;script src=\u0026#39;//unpkg.com/@waline/client@v2/dist/waline.js\u0026#39;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;link href=\u0026#39;//unpkg.com/@waline/client@v2/dist/waline.css\u0026#39; rel=\u0026#39;stylesheet\u0026#39;/\u0026gt; \u0026lt;div id=\u0026#34;waline\u0026#34; class=\u0026#34;waline-container\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;style\u0026gt; .waline-container { background-color: var(--card-background); border-radius: var(--card-border-radius); box-shadow: var(--shadow-l1); padding: var(--card-padding); --waline-font-size: var(--article-font-size); } .waline-container .wl-count { color: var(--card-text-color-main); } \u0026lt;/style\u0026gt; {{- $permalink := .Permalink -}} {{- with .Site.Params.comments.waline -}} {{- $config := dict \u0026#34;el\u0026#34; \u0026#34;#waline\u0026#34; \u0026#34;dark\u0026#34; `html[data-scheme=\u0026#34;dark\u0026#34;]` \u0026#34;path\u0026#34; $permalink -}} {{- $replaceKeys := dict \u0026#34;serverurl\u0026#34; \u0026#34;serverURL\u0026#34; \u0026#34;requiredmeta\u0026#34; \u0026#34;requiredMeta\u0026#34; \u0026#34;wordlimit\u0026#34; \u0026#34;wordLimit\u0026#34; \u0026#34;pagesize\u0026#34; \u0026#34;pageSize\u0026#34; \u0026#34;imageuploader\u0026#34; \u0026#34;imageUploader\u0026#34; \u0026#34;texrenderer\u0026#34; \u0026#34;texRenderer\u0026#34; \u0026#34;turnstilekey\u0026#34; \u0026#34;turnstileKey\u0026#34; -}} {{- range $key, $val := . -}} {{- if ne $val nil -}} {{- $replaceKey := index $replaceKeys $key -}} {{- $k := default $key $replaceKey -}} {{- $config = merge $config (dict $k $val) -}} {{- end -}} {{- end -}} \u0026lt;script id=\u0026#34;waline-config\u0026#34; type=\u0026#34;application/json\u0026#34;\u0026gt; {{ $config | jsonify | safeJS }} \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; /// Waline client configuration see: https://waline.js.org/en/reference/client.html const walineConfig = JSON.parse(document.getElementById(\u0026#39;waline-config\u0026#39;).textContent); Waline.init(walineConfig); \u0026lt;/script\u0026gt; {{- end -}} Using .Permalink will automatically include the full URL (with domain), so the Waline admin panel displays comments as https://yourblog.com/p/post-name/ instead of a relative path — making them much easier to identify. Step 4: Writing New Posts and Updating Your Blog Once the scaffolding is set up, writing new posts is incredibly simple:\nCreate a new post: Open a terminal and run:\nhugo new content post/your-post-title/index.md This creates a new folder inside content/post/ along with an index.md file — this is where you\u0026rsquo;ll write your post content.\nWrite your content: Open the newly created index.md. You\u0026rsquo;ll see a section at the top (between +++) called the Front Matter — this is where metadata like title, date, description, and cover image goes. Everything below is your post content, written in standard Markdown.\nPreview locally: Run hugo server to see how your post looks before publishing.\nPublish (This is all it takes): When you\u0026rsquo;re happy with your post, run these 3 basic Git commands:\ngit add . git commit -m \u0026#34;Add new post: Your Post Title\u0026#34; git push origin main As soon as you push, GitHub Actions will handle everything else. Within about a minute, your new post will appear live on your blog!\nConclusion Total cost: $0 (the free tiers of GitHub Pages, Vercel, and Neon are more than sufficient for a personal blog). Advantages: A full-featured comment system with anonymous support, instant display, emoji, and reactions. Automation: Once the setup is done, your only job is to write in Markdown and push to GitHub. GitHub Actions takes care of building and deploying — your post goes live automatically. Drawback: If you experience a sudden traffic spike, you may need to upgrade your Neon/Vercel plan — but this is extremely rare for a personal blog. In just a few simple steps and at zero cost, you have a sleek, professional blog to write on freely. Now it\u0026rsquo;s just a matter of making time to publish! Thanks for reading, and see you in the next post. If you run into any issues or have questions, feel free to leave a comment below!\nReferences Hugo Theme Stack Starter Template (official repo to clone and get started quickly):\nhttps://github.com/CaiJimmy/hugo-theme-stack-starter\nHugo Theme Stack Official Documentation (comments config, Waline, and general customization):\nhttps://stack.jimmycai.com/\nComments section: https://stack.jimmycai.com/config/comments\nHugo Official Guide: Host and Deploy on GitHub Pages (deployment with GitHub Actions):\nhttps://gohugo.io/host-and-deploy/host-on-github-pages/\nWaline Official Documentation (Vercel deployment, client config, ecosystem):\nhttps://waline.js.org/en/\nDeploy on Vercel: https://waline.js.org/en/guide/deploy/vercel.html\nClient config (path, lang, reaction): https://waline.js.org/en/reference/client/props.html\nNeon Docs: Integrate with Vercel (connecting Neon Postgres with Vercel, env vars, branching):\nhttps://neon.com/docs/guides/vercel-overview\nSource code of this blog (for a real-world reference):\nhttps://github.com/pngocthach/pngocthach.github.io\n","date":"2026-03-08T19:25:00+07:00","permalink":"/p/how-i-set-up-my-personal-blog-fast-beautiful-and-free/","title":"How I Set Up My Personal Blog — Fast, Beautiful, and Free"}]