[{"content":"","date":null,"permalink":"https://chrisvoo.github.io/","section":"","summary":"","title":""},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/tags/blog/","section":"Tags","summary":"","title":"Blog"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/tags/github/","section":"Tags","summary":"","title":"Github"},{"content":" As the ending part of the series about publishing a blog on GitHub, we\u0026rsquo;ll explore how to set up GitHub for publishing our blog and new content as soon as it\u0026rsquo;s available.\nGitHub Actions Workflow #Create .github/workflows/deploy.yml:\nname: Deploy Blog on: push: branches: [main] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 # full history for .Lastmod - uses: actions/setup-go@v6 with: go-version: \u0026#34;stable\u0026#34; # required for Hugo Modules - name: Setup Hugo (Extended) uses: peaceiris/actions-hugo@v3.2.1 with: hugo-version: latest extended: true # Congo uses SCSS - name: Build run: hugo --minify --baseURL \u0026#34;https://chrisvoo.github.io/\u0026#34; --buildFuture - uses: actions/upload-pages-artifact@v5 with: path: public deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/deploy-pages@v5 id: deployment You may have noticed we run Hugo with --buildFuture: this may be something you don\u0026rsquo;t want, since it also considers posts in the future. Since I\u0026rsquo;m not interested in scheduling content, I bumped into an issue about timezones that leads posts with the current date to be considered in the future remotely.\nEnable GitHub Pages #You just need to go to your blog repo and then Settings → Pages → Source: GitHub Actions.\nConclusion #You should now have all the basic information for publishing your content on GitHub Pages. Of course, you can also use this information (especially in this last post) for publishing other projects not related to a blog. Enjoy!\n","date":"31 May 2026","permalink":"https://chrisvoo.github.io/posts/part-4-deployment-on-github/","section":"Posts","summary":"Setting up a GitHub pipeline for deploying your blog","title":"Part 4: Deployment on GitHub"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/tags/pipeline/","section":"Tags","summary":"","title":"Pipeline"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/tags/cms/","section":"Tags","summary":"","title":"Cms"},{"content":" Sveltia CMS is the git-based headless CMS I use for writing my blog posts (this one too). Of course, you\u0026rsquo;re still free to use Visual Studio Code (or even Vim) for writing your content: Hugo and its watching preview feature will allow you to immediately see what you\u0026rsquo;re writing (or even better, you can see the preview directly is VS Code).\nHowever, using this CMS has other advantages:\nYou manage publishing date, tags, comment enabling, summary, related posts and content directly from a web interface that shows you a preview Image providers integration like Unsplash: you just need to register an application and you\u0026rsquo;ll get an API key You can manage your posts locally and you can even post directly on your live instance on GitHub: Sveltia CMS supports a GitHub Personal Access Token (PAT) flow: you click \u0026ldquo;Sign in with Token\u0026rdquo; on the admin page and paste your PAT. The token is stored in the browser\u0026rsquo;s localStorage Generate a GitHub PAT (one-time setup) # github.com → Settings → Developer Settings → Personal access tokens → Fine-grained tokens Generate a new token choosing your favorite name and the expiration Limit the repository access to only your blog repo Permissions: Contents → Read and Write Configuration #The CMS lives entirely in static/admin/ — Hugo copies it verbatim to public/admin/, served at: \u0026lt;username\u0026gt;.github.io/admin.\nstatic/admin/index.html #\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34; /\u0026gt; \u0026lt;meta name=\u0026#34;robots\u0026#34; content=\u0026#34;noindex\u0026#34; /\u0026gt; \u0026lt;title\u0026gt;Blog Admin\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- Sveltia CMS --\u0026gt; \u0026lt;script src=\u0026#34;https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; static/admin/config.yml #backend: name: github repo: chrisvoo/chrisvoo.github.io branch: main # No base_url needed — Personal Access Token auth requires no proxy media_folder: \u0026#34;static/images/uploads\u0026#34; public_folder: \u0026#34;/images/uploads\u0026#34; collections: - name: \u0026#34;posts\u0026#34; label: \u0026#34;Blog Posts\u0026#34; folder: \u0026#34;content/posts\u0026#34; create: true slug: \u0026#34;{{slug}}\u0026#34; fields: - { name: \u0026#34;title\u0026#34;, label: \u0026#34;Title\u0026#34;, widget: \u0026#34;string\u0026#34; } - { name: \u0026#34;date\u0026#34;, label: \u0026#34;Date\u0026#34;, widget: \u0026#34;datetime\u0026#34; } - { name: \u0026#34;draft\u0026#34;, label: \u0026#34;Draft\u0026#34;, widget: \u0026#34;boolean\u0026#34;, default: false } - { name: \u0026#34;tags\u0026#34;, label: \u0026#34;Tags\u0026#34;, widget: \u0026#34;list\u0026#34;, required: false } - { name: \u0026#34;summary\u0026#34;, label: \u0026#34;Summary\u0026#34;, widget: \u0026#34;text\u0026#34;, required: false } - { name: \u0026#34;comments\u0026#34;, label: \u0026#34;Enable comments\u0026#34;, widget: \u0026#34;boolean\u0026#34;, default: true } - { name: \u0026#34;body\u0026#34;, label: \u0026#34;Content\u0026#34;, widget: \u0026#34;markdown\u0026#34; } - label: \u0026#34;See Also (Related Posts)\u0026#34; name: \u0026#34;see_also\u0026#34; widget: \u0026#34;relation\u0026#34; collection: \u0026#34;posts\u0026#34; # Targets this same collection search_fields: [\u0026#34;title\u0026#34;] value_field: \u0026#34;/posts/{{slug}}/\u0026#34; # Saves the URL format you wanted multiple: true required: false The \u0026ldquo;See also\u0026rdquo; section #The docs explain all the possible fields types that can be used. I wanted to be able to show a list of related post at the bottom of a post.\nStart defining the section in the admin panel like in the config above. You\u0026rsquo;ll see a see_also field of type relation that will hold for us a list of post. Sveltia CMS tracks how many entries are available in your target collection. To optimize data entry speed, it changes the UI layout dynamically: 5 or fewer items: Sveltia assumes it\u0026rsquo;s faster for you to just look at a quick list, so it renders them as checkboxes (for multiple selections) or radio buttons (for single selections). 6 or more items: Sveltia realizes a list would get messy. It automatically morphs the field into a dropdown menu with a live autocomplete search box. Create a new partial named ./layouts/_partials/see-also.html. This will hold the style of our section Include the partials in ./layouts/_partials/comments.html ./layouts/_partials/see-also.html #{{ with .Params.see_also }} \u0026lt;div class=\u0026#34;see-also-container\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;see-also-heading\u0026#34;\u0026gt;See also:\u0026lt;/span\u0026gt; \u0026lt;ul class=\u0026#34;see-also-list\u0026#34;\u0026gt; {{ range . }} {{ $page := $.Site.GetPage . }} {{ if $page }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;{{ $page.RelPermalink }}\u0026#34;\u0026gt;{{ $page.Title }}\u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{ else }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;{{ . | relURL }}\u0026#34;\u0026gt;{{ . }}\u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{ end }} {{ end }} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; {{ end }} ./layouts/_partials/comments.html #{{ if .Params.comments | default true }} {{ partial \u0026#34;see-also.html\u0026#34; . }} \u0026lt;script src=\u0026#34;https://giscus.app/client.js\u0026#34; data-repo=\u0026#34;chrisvoo/chrisvoo.github.io\u0026#34; data-repo-id=\u0026#34;\u0026lt;REPO_ID\u0026gt;\u0026#34; data-category=\u0026#34;Blog comments\u0026#34; data-category-id=\u0026#34;\u0026lt;CATEGORY_ID\u0026gt;\u0026#34; data-mapping=\u0026#34;pathname\u0026#34; data-strict=\u0026#34;0\u0026#34; data-reactions-enabled=\u0026#34;1\u0026#34; data-emit-metadata=\u0026#34;0\u0026#34; data-input-position=\u0026#34;bottom\u0026#34; data-theme=\u0026#34;preferred_color_scheme\u0026#34; data-lang=\u0026#34;en\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; async\u0026gt; \u0026lt;/script\u0026gt; {{ end }} ","date":"22 May 2026","permalink":"https://chrisvoo.github.io/posts/part-3-setting-up-sveltia-cms/","section":"Posts","summary":"How to use a markdown editor and other advances features for writing your posts","title":"Part 3: Setting up Sveltia CMS"},{"content":" In this part we\u0026rsquo;ll enable the possibility for the user with a Github account to comment our posts. We\u0026rsquo;ll use Giscus for this.\n💡 Read Part 1: building a blog with GitHub - Configuring Hugo if you\u0026rsquo;ve missed it.\nSince Congo, the Hugo template used in the first part of this tutorial, does not have built-in Giscus config keys, we\u0026rsquo;ll wire it via a custom partial.\nEnable Discussions on the repo # github.com/chrisvoo/chrisvoo.github.io → Settings → General → check Discussions Discussions tab → Categories Click the pencil/edit icon next to Categories → New category and add Blog Comments (type: Open-ended discussion) Install Giscus app: https://github.com/apps/giscus → select chrisvoo/chrisvoo.github.io Visit https://giscus.app, enter chrisvoo/chrisvoo.github.io → copy data-repo-id and data-category-id As you can see, Giscus offer a dynamic way to populate the script parts necessary to enable this feature.\nCreate layouts/_partials/comments.html #💬 Note: Congo uses _partials/ (with underscore prefix) for all its override hooks, checked via templates.Exists \u0026quot;_partials/comments.html\u0026quot;. A file at layouts/partials/comments.html (no underscore) is invisible to this check and the warning [CONGO] Comments are enabled but no comments partial exists will keep firing.\n{{ if .Params.comments | default true }} \u0026lt;script src=\u0026#34;https://giscus.app/client.js\u0026#34; data-repo=\u0026#34;chrisvoo/chrisvoo.github.io\u0026#34; data-repo-id=\u0026#34;\u0026lt;REPO_ID\u0026gt;\u0026#34; data-category=\u0026#34;Blog comments\u0026#34; data-category-id=\u0026#34;\u0026lt;CAT_ID\u0026gt;\u0026#34; data-mapping=\u0026#34;pathname\u0026#34; data-strict=\u0026#34;0\u0026#34; data-reactions-enabled=\u0026#34;1\u0026#34; data-emit-metadata=\u0026#34;0\u0026#34; data-input-position=\u0026#34;bottom\u0026#34; data-theme=\u0026#34;preferred_color_scheme\u0026#34; data-lang=\u0026#34;en\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; async\u0026gt; \u0026lt;/script\u0026gt; {{ end }} Replace the placeholder above with the values generated on Giscus website. To disable comments on a specific post, add comments: false to its frontmatter. If everything works, you\u0026rsquo;ll see the comment form below the post\nFavicons #Create the necessary files by yourself or denerate a full favicon set at favicon.io or realfavicongenerator.net and then place all files under assets/favicon/ (not static/) so Hugo Pipes manages them. Expected files:\nassets/favicon/ favicon.ico favicon.svg favicon-96x96.png apple-touch-icon.png web-app-manifest-192x192.png web-app-manifest-512x512.png site.webmanifest assets/favicon/site.webmanifest #This is my manifest file. The manifest\u0026rsquo;s src values are resolved from the domain root, not the Hugo baseURL\n{ \u0026#34;name\u0026#34;: \u0026#34;The Castles\u0026#34;, \u0026#34;short_name\u0026#34;: \u0026#34;The Castles\u0026#34;, \u0026#34;icons\u0026#34;: [ { \u0026#34;src\u0026#34;: \u0026#34;/favicon/android-chrome-192x192.png\u0026#34;, \u0026#34;sizes\u0026#34;: \u0026#34;192x192\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;image/png\u0026#34; }, { \u0026#34;src\u0026#34;: \u0026#34;/favicon/android-chrome-512x512.png\u0026#34;, \u0026#34;sizes\u0026#34;: \u0026#34;512x512\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;image/png\u0026#34; } ], \u0026#34;theme_color\u0026#34;: \u0026#34;#ffffff\u0026#34;, \u0026#34;background_color\u0026#34;: \u0026#34;#ffffff\u0026#34;, \u0026#34;display\u0026#34;: \u0026#34;standalone\u0026#34; } layouts/_partials/favicons.html #Congo\u0026rsquo;s head.html checks templates.Exists \u0026quot;_partials/favicons.html\u0026quot; — if it exists, Congo delegates all favicon \u0026lt;link\u0026gt; tags to it entirely. Files in assets/ are only emitted to public/ when referenced via resources.Get.\n{{- $ico := resources.Get \u0026#34;favicon/favicon.ico\u0026#34; -}} {{- $svg := resources.Get \u0026#34;favicon/favicon.svg\u0026#34; -}} {{- $png96 := resources.Get \u0026#34;favicon/favicon-96x96.png\u0026#34; -}} {{- $apple := resources.Get \u0026#34;favicon/apple-touch-icon.png\u0026#34; -}} {{- $manifest := resources.Get \u0026#34;favicon/site.webmanifest\u0026#34; -}} \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;{{ $ico.RelPermalink }}\u0026#34; sizes=\u0026#34;32x32\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;{{ $svg.RelPermalink }}\u0026#34; type=\u0026#34;image/svg+xml\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;icon\u0026#34; type=\u0026#34;image/png\u0026#34; sizes=\u0026#34;96x96\u0026#34; href=\u0026#34;{{ $png96.RelPermalink }}\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;apple-touch-icon\u0026#34; href=\u0026#34;{{ $apple.RelPermalink }}\u0026#34; /\u0026gt; \u0026lt;link rel=\u0026#34;manifest\u0026#34; href=\u0026#34;{{ $manifest.RelPermalink }}\u0026#34; /\u0026gt; Verify after build: grep favicon public/index.html should show five \u0026lt;link\u0026gt; tags\n","date":"20 May 2026","permalink":"https://chrisvoo.github.io/posts/part-2-configuring-comments-and-favicon/","section":"Posts","summary":"Enabling comments under your posts and create a custom favicon","title":"Part 2: Configuring comments and favicon"},{"content":" It\u0026rsquo;s been a long time since I had a blog. In an attempt to start again to write and keep interesting things as my memories, something that could be useful for both myself and others bumping into it, I looked for a cheap but effective solution. Here is the one I\u0026rsquo;m currently using.\nStack # Topic Choice Why Static site generator Hugo Fastest builds, 400+ themes, Go binary Theme Congo Feature-rich, Tailwind-based, active maintenance, live demos at jpanther.github.io/congo CMS Sveltia CMS Modern Decap CMS drop-in; GitHub Access Token auth; works on GitHub Pages with zero backend Auth GitHub PAT Personal Access Token stored in browser — no proxy, no Cloudflare, no OAuth app needed Comments Giscus GitHub Discussions-backed, no DB, GitHub OAuth for visitors Set up #Prerequisites #Start by installing Go, gh and Hugo. The following command will use Brew since I\u0026rsquo;m using a Mac:\nbrew install hugo go gh hugo version # must show hugo v0.x.x+extended go version # needed for Hugo Modules (Congo install method) gh (the GitHub CLI) is certainly optional, but it\u0026rsquo;s pretty good and fast to operate with instead of searching for the things you need in GitHub settings.\n1. Repository \u0026amp; Hugo Scaffold #Let\u0026rsquo;s create and clone a new GitHub repo and then create the basic blog structure in the directory:\ngh repo create chrisvoo/blog --public --clone cd blog # Scaffold Hugo site (--force because dir has .git) hugo new site . --force 2. Congo Theme via Hugo Modules #Hugo Modules is the preferred Congo install method (easier upgrades than submodules). The following snippets perfectly reflect this website\u0026rsquo;s configuration, so it already acts as a demo.\nInitialize the module: hugo mod init github.com/chrisvoo/blog Create config/_default/module.toml [[imports]] path = \u0026#34;github.com/jpanther/congo/v2\u0026#34; Then pull it:\nhugo mod get -u Congo uses a split config layout under config/_default/:\nconfig/ └── _default/ ├── hugo.toml ← site identity + baseURL ├── params.toml ← theme features ├── languages.en.toml ← title, tagline, menu labels ├── menus.en.toml ← nav items ├── markup.toml ← code highlight settings └── taxonomies.toml ← tags, categories, series Let\u0026rsquo;s modify the configuration (config/_default/hugo.toml) for base URL, locale, search bar and tags:\nbaseURL = \u0026#34;https://chrisvoo.github.io/\u0026#34; locale = \u0026#34;en\u0026#34; defaultContentLanguage = \u0026#34;en\u0026#34; enableRobotsTXT = true [outputs] home = [\u0026#34;HTML\u0026#34;, \u0026#34;RSS\u0026#34;, \u0026#34;JSON\u0026#34;] # JSON required for Fuse.js search index [taxonomies] tag = \u0026#34;tags\u0026#34; category = \u0026#34;categories\u0026#34; series = \u0026#34;series\u0026#34; [markup] [markup.highlight] noClasses = false # emit CSS classes (.chroma .k etc.) instead of inline styles style = \u0026#34;monokai\u0026#34; # Chroma style — controls token colours in the generated CSS Now the params (config/_default/params.toml) for defining the peculiarities of header, footer and articles:\ncolorScheme = \u0026#34;congo\u0026#34; # built-in palette; others: avocado, cherry, fire, ocean, slate defaultAppearance = \u0026#34;dark\u0026#34; # auto | light | dark autoSwitchAppearance = true enableSearch = true enableCodeCopy = true enableImageLazyLoading = true enableImageWebp = true showReadingTime = true showBreadcrumbs = true showTableOfContents = true showAuthor = true showDate = true showWordCount = false [header] showTitle = true layout = \u0026#34;hybrid\u0026#34; [footer] showAppearanceSwitcher = true showScrollToTop = true [article] showDate = true showTaxonomies = true showComments = true showRelatedContent = true sharingLinks = [\u0026#34;linkedin\u0026#34;, \u0026#34;email\u0026#34;, \u0026#34;telegram\u0026#34;, \u0026#34;reddit\u0026#34;, \u0026#34;facebook\u0026#34;, \u0026#34;mastodon\u0026#34;] [list] showSummary = true showBreadcrumbs = true showTaxonomies = true groupByYear = true [homepage] layout = \u0026#34;page\u0026#34; showRecent = true recentLimit = 5 [sitemap] excludedKinds = [] [author] name = \u0026#34;Christian Castelli\u0026#34; Something about the language and the website\u0026rsquo;s title (config/_default/languages.en.toml):\ntitle = \u0026#34;The Castles\u0026#34; label = \u0026#34;English\u0026#34; [params] description = \u0026#34;Thoughts on software, tech, and whatever.\u0026#34; Finally the menu items. The search icon in the nav requires a menu entry with action = \u0026quot;search\u0026quot;. Without this, the search bar never renders even if enableSearch = true.\n[[main]] name = \u0026#34;Blog\u0026#34; pageRef = \u0026#34;posts\u0026#34; weight = 10 [[main]] name = \u0026#34;GitHub\u0026#34; url = \u0026#34;https://github.com/chrisvoo\u0026#34; weight = 30 [main.params] icon = \u0026#34;github\u0026#34; showName = false target = \u0026#34;_blank\u0026#34; [[main]] identifier = \u0026#34;search\u0026#34; weight = 99 [main.params] action = \u0026#34;search\u0026#34; icon = \u0026#34;search\u0026#34; Putting the code below in content/_index.md prevents Congo\u0026rsquo;s page homepage layout from rendering a duplicate \u0026lt;h1\u0026gt; with the site title (which also appears in the header logo). An empty frontmatter file suppresses the title in the page body while keeping the recent-posts list.\n--- draft: false --- The following blog posts will explain the remaining parts to be described. 🤘\n","date":"18 May 2026","permalink":"https://chrisvoo.github.io/posts/part-1-building-blog-github-configuring-hugo/","section":"Posts","summary":"First part of a series of posts for building a blog with GitHub","title":"Part 1: Building a blog with GitHub - Configuring Hugo"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":null,"permalink":"https://chrisvoo.github.io/series/","section":"Series","summary":"","title":"Series"}]