<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>figueirego. – figueirego</title><link>https://figueirego.com/blog/</link><description>Recent content in figueirego on figueirego.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 13 Oct 2026 12:03:00 -0300</lastBuildDate><atom:link href="https://figueirego.com/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Building my blog, part 1: writing without running a backend</title><link>https://figueirego.com/blog/posts/building-the-blog-01-architecture/</link><pubDate>Tue, 13 Oct 2026 12:03:00 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-01-architecture/</guid><description>
&lt;p&gt;This blog starts with text files. Hugo reads those files, applies the layouts, and generates the pages delivered to the browser. I do not need to run a publishing API or my own database to serve the articles.&lt;/p&gt;
&lt;p&gt;That was the main requirement when I started: I wanted somewhere to explain what I build, record decisions, and return to them later. I also wanted it to carry the figueirego identity, feel comfortable on a phone, and support reading in Portuguese or English.&lt;/p&gt;
&lt;p&gt;Between the first reference and the published version, I had to make decisions about design, navigation, comments, search, hosting, and images. I split the story into three parts so that each responsibility has room for a proper explanation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; why I chose this foundation and what each component does.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;The reading experience&lt;/a&gt;: organization, languages, search, themes, and comments.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;Publishing and operations&lt;/a&gt;: from Markdown to the live website, including images and analytics.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-hextra-search-id="akita-was-the-reference-the-result-needed-to-be-mine"&gt;Akita was the reference; the result needed to be mine&lt;span class="hx:absolute hx:-mt-20" id="akita-was-the-reference-the-result-needed-to-be-mine"&gt;&lt;/span&gt;
&lt;a href="#akita-was-the-reference-the-result-needed-to-be-mine" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The starting point was Fabio Akita&amp;rsquo;s &lt;a href="https://akitaonrails.com/2025/09/10/meu-novo-blog-como-eu-fiz/" target="_blank" rel="noopener"&gt;Meu Novo Blog — Como Eu Fiz&lt;/a&gt;. What interested me was his choice to publish Markdown files as a static site and reduce the work involved in maintaining a custom blog engine. The priority was being able to write.&lt;/p&gt;
&lt;p&gt;I liked the technical approach, but wanted a different visual direction: my website&amp;rsquo;s colors, my brand, and an organization suited to the content I intend to produce. Separating those decisions helped. Choosing Hugo did not require reproducing another blog&amp;rsquo;s appearance.&lt;/p&gt;
&lt;p&gt;I also had to clear up the names early on: &lt;strong&gt;Hugo is the generator; Hextra is the theme&lt;/strong&gt;. Hugo is written in Go. Hextra provides layouts and features to build on. The &lt;a href="https://github.com/imfing/hextra" target="_blank" rel="noopener"&gt;Hextra repository&lt;/a&gt; documents that relationship.&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-happens-when-someone-opens-an-article"&gt;What happens when someone opens an article&lt;span class="hx:absolute hx:-mt-20" id="what-happens-when-someone-opens-an-article"&gt;&lt;/span&gt;
&lt;a href="#what-happens-when-someone-opens-an-article" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The page has already been generated when the reader arrives. The host serves HTML, CSS, JavaScript, and the necessary assets. There is no query to my own database to assemble the article on every request.&lt;/p&gt;
&lt;p&gt;The main path looks like this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Markdown text + metadata + layouts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Hugo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; static pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Hostinger
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; reader&amp;#39;s browser&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This still leaves room for interaction. Search runs in the browser, filters respond to clicks, the theme changes, and comments come from an external service. The useful distinction is what I need to operate: the main content does not depend on an application server of my own staying online.&lt;/p&gt;
&lt;p&gt;I chose this structure because it fits the kind of publishing I want to do. I am not building a platform with its own reader accounts, subscription billing, or simultaneous browser editing. If those needs arise, they will deserve a separate decision.&lt;/p&gt;
&lt;h2 data-hextra-search-id="a-clear-job-for-each-tool"&gt;A clear job for each tool&lt;span class="hx:absolute hx:-mt-20" id="a-clear-job-for-each-tool"&gt;&lt;/span&gt;
&lt;a href="#a-clear-job-for-each-tool" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The implementation ended up with these responsibilities:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Its job in this blog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Markdown&lt;/td&gt;
&lt;td&gt;Store articles and their metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hugo + Hextra&lt;/td&gt;
&lt;td&gt;Generate pages and provide the visual and functional foundation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom layouts, CSS, and JavaScript&lt;/td&gt;
&lt;td&gt;Apply the figueirego identity and specific interactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private GitHub repository&lt;/td&gt;
&lt;td&gt;Version the source and run checks and builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hostinger&lt;/td&gt;
&lt;td&gt;Serve the static site at its public URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Store images uploaded separately from the site.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disqus&lt;/td&gt;
&lt;td&gt;Host comments and their moderation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoatCounter&lt;/td&gt;
&lt;td&gt;Record visits used to generate the most-read lists.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An important decision was to use the hosting I already had. I considered other destinations, but the blog was published on Hostinger. &lt;strong&gt;AWS handles images, not page hosting.&lt;/strong&gt; There is no CloudFront distribution in this workflow.&lt;/p&gt;
&lt;p&gt;The source files remain in a private repository. Readers receive the published output and do not need repository access. That does not make files served by the site private: articles, images, HTML, and JavaScript delivered to a browser are public.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-theme-is-a-foundation-i-can-customize"&gt;The theme is a foundation I can customize&lt;span class="hx:absolute hx:-mt-20" id="the-theme-is-a-foundation-i-can-customize"&gt;&lt;/span&gt;
&lt;a href="#the-theme-is-a-foundation-i-can-customize" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;For this first version, I pinned Hugo Extended 0.167.0 and Hextra 0.13.0. The theme is included in the repository. Building this project with the installed Hugo binary does not require installing Go or running a local npm build.&lt;/p&gt;
&lt;p&gt;Customizations live separately from the theme directory:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content/ Portuguese and English content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;layouts/ custom pages and components
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assets/ styles, search, and interactions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;static/ fonts, brand assets, and local images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;i18n/ interface messages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;themes/hextra/ the underlying theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scripts/ upload and validation helpers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This separation makes problems easier to locate. Incorrect article text belongs in &lt;code&gt;content/&lt;/code&gt;. An English button label belongs in &lt;code&gt;i18n/&lt;/code&gt;. Spacing or contrast belongs in the styles. A comment-service change belongs in that integration.&lt;/p&gt;
&lt;p&gt;A future theme update will still require review. Keeping these files separate makes the work easier to understand; it does not guarantee automatic compatibility with every new release.&lt;/p&gt;
&lt;h2 data-hextra-search-id="where-do-i-actually-write"&gt;Where do I actually write?&lt;span class="hx:absolute hx:-mt-20" id="where-do-i-actually-write"&gt;&lt;/span&gt;
&lt;a href="#where-do-i-actually-write" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;That was one of my questions during the build. There is no administration tab inside the blog where I sign in and start typing. Editing happens on my computer, in Markdown files, using a text or code editor.&lt;/p&gt;
&lt;p&gt;Each publication begins with metadata: title, date, summary, topics, and draft status. The text follows. The content remains editable without opening a particular editing service, and Git records the changes I choose to commit.&lt;/p&gt;
&lt;p&gt;For review, I use Hugo&amp;rsquo;s local preview. It renders the actual layout and can include drafts. Publishing is a separate step: submit the reviewed content, generate the production version, and check the live website. The full commands are in &lt;a href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;part 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Akita&amp;rsquo;s &lt;a href="https://akitaonrails.com/2025/08/29/como-contribuir-no-blog-do-akitaonrails-usando-docker/" target="_blank" rel="noopener"&gt;article about Docker&lt;/a&gt; also informed that investigation. Docker provides a local environment for contributing to his repository. Running that copy does not automatically publish to his site; contributions go through pull requests. My current workflow runs Hugo locally without making Docker a requirement.&lt;/p&gt;
&lt;h2 data-hextra-search-id="languages-became-part-of-the-structure-early"&gt;Languages became part of the structure early&lt;span class="hx:absolute hx:-mt-20" id="languages-became-part-of-the-structure-early"&gt;&lt;/span&gt;
&lt;a href="#languages-became-part-of-the-structure-early" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I wanted English at the domain root and Portuguese under &lt;code&gt;/pt/&lt;/code&gt;. That became a content decision: each language has its own files, connected through a stable translation key.&lt;/p&gt;
&lt;p&gt;Akita&amp;rsquo;s &lt;a href="https://akitaonrails.com/2026/04/09/20-anos-de-blog-o-ano-em-que-a-ia-finalmente-me-deixou-traduzir-tudo/" target="_blank" rel="noopener"&gt;account of translating his archive&lt;/a&gt; was another reference. In my case, I am starting with pairs of articles rather than migrating a historical archive. The language control navigates between prepared versions; it does not call a translator on every click.&lt;/p&gt;
&lt;p&gt;That puts review inside the editorial process. Titles, explanations, and examples need to make sense in both languages. The infrastructure connects the files, but it cannot decide whether a translation reads well.&lt;/p&gt;
&lt;h2 data-hextra-search-id="building-with-ai-still-required-checking-the-result"&gt;Building with AI still required checking the result&lt;span class="hx:absolute hx:-mt-20" id="building-with-ai-still-required-checking-the-result"&gt;&lt;/span&gt;
&lt;a href="#building-with-ai-still-required-checking-the-result" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I used Codex to help turn decisions into code, review behavior, and organize this series. To show the result, this series uses actual screenshots, with captions identifying their context. The process involved previews, screenshots, requests for changes, and checks of the site actually being served.&lt;/p&gt;
&lt;p&gt;Some corrections came directly from using it: the search button that did not open as expected, comments split between languages, and excessive spacing in the footer. Choosing an appropriate technology alone would not have resolved any of them.&lt;/p&gt;
&lt;p&gt;The build also included proposals for a newsletter and more menu sections. The newsletter was left out for now, and content formats were gathered inside the Archive. Having the ability to implement something did not mean it had to enter the first release.&lt;/p&gt;
&lt;p&gt;The next step is to use this structure to write. To see how it works for someone arriving at the blog, continue to &lt;a href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;part 2: the reading experience&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Building my blog:&lt;/strong&gt; &lt;strong&gt;1. Architecture&lt;/strong&gt; · &lt;a href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;2. The reading experience&lt;/a&gt; · &lt;a href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;3. Publishing and operations&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Building my blog, part 2: the reader's experience</title><link>https://figueirego.com/blog/posts/building-the-blog-02-reading/</link><pubDate>Tue, 13 Oct 2026 12:02:00 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-02-reading/</guid><description>
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Editorial note:&lt;/strong&gt; This series documents the initial Hugo blog, which was separate from the portfolio. The unified version of the site brings both together at figueirego.com, with the blog under &lt;code&gt;/blog/&lt;/code&gt; (English) and &lt;code&gt;/pt/blog/&lt;/code&gt; (Portuguese). The navigation and screenshots below document that initial stage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A reader arrives looking for a piece of writing. They might remember its subject, want to see what I have published recently, or have followed a link. The blog needs to support those paths without requiring anyone to understand how I organize my files.&lt;/p&gt;
&lt;p&gt;This was the most iterative part of the build. I wanted the simplicity of the approach Akita described, with my website&amp;rsquo;s identity and an experience of its own. Colors and fonts were the starting point; the more consequential decisions involved navigation, search, and comments.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;part one&lt;/a&gt;, I explain the architecture. Here, the focus is what happens after the page opens.&lt;/p&gt;
&lt;h2 data-hextra-search-id="keeping-the-figueirego-identity"&gt;Keeping the figueirego identity&lt;span class="hx:absolute hx:-mt-20" id="keeping-the-figueirego-identity"&gt;&lt;/span&gt;
&lt;a href="#keeping-the-figueirego-identity" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I used the SVG wordmarks already present on my website, with versions for light and dark backgrounds. This preserves the actual shape of the logo. Manrope handles headings, interface text, and prose; JetBrains Mono handles code. The blog serves the font files itself.&lt;/p&gt;
&lt;p&gt;Petroleum green identifies links and actions in the light theme. In dark mode, the accent becomes a lighter turquoise so it remains visible against the background. Subtle surfaces, dividers, and spacing organize the content.&lt;/p&gt;
&lt;p&gt;The header received the glass effect I also wanted on my website: a translucent background, blur, and a light border. It falls back to a solid surface when the browser lacks support or the visitor prefers reduced transparency. The effect should support reading, not make it harder.&lt;/p&gt;
&lt;h2 data-hextra-search-id="four-paths-in-the-header"&gt;Four paths in the header&lt;span class="hx:absolute hx:-mt-20" id="four-paths-in-the-header"&gt;&lt;/span&gt;
&lt;a href="#four-paths-in-the-header" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Earlier versions put Articles, Notes, and Experiments at the top. As Responses and Transcripts joined them, putting every format in the header would make the navigation compete for space, particularly on a phone.&lt;/p&gt;
&lt;p&gt;In the standalone version described here, the final navigation had four destinations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latest:&lt;/strong&gt; all publications, newest first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive:&lt;/strong&gt; the complete collection, with filters to find a particular entry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;About:&lt;/strong&gt; my introduction and links to find me elsewhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My website:&lt;/strong&gt; figueirego.com in the corresponding language.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;External links open a new tab. Navigation within the blog stays in the current one. On phones, the header rearranges its controls and navigation into rows while retaining touch targets and accessible labels.&lt;/p&gt;
&lt;h2 data-hextra-search-id="format-and-topic-answer-different-questions"&gt;Format and topic answer different questions&lt;span class="hx:absolute hx:-mt-20" id="format-and-topic-answer-different-questions"&gt;&lt;/span&gt;
&lt;a href="#format-and-topic-answer-different-questions" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;An article about Go can share its subject with an experiment or a response to a reader. That is why I kept formats separate from topics:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Intended use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Articles&lt;/td&gt;
&lt;td&gt;Develop an idea, decision, or process with context.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;td&gt;Record a shorter observation or something learned.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experiments&lt;/td&gt;
&lt;td&gt;Document an attempt, its setup, and the observations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Responses&lt;/td&gt;
&lt;td&gt;Develop a question received on social media or in comments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcripts&lt;/td&gt;
&lt;td&gt;Publish text versions of my videos and conversations.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Topics are tags: software engineering, learning, Go, and other subjects that emerge from the writing. An entry can belong to several topics without being copied into several places.&lt;/p&gt;
&lt;p&gt;In the Archive, readers can combine format, year, topic, and a text query. Filters are preserved in the URL, so the selection can be shared and browser back and forward continue to work. Without JavaScript, the complete list remains available. Topic counts come from published content; I did not fill the interface with demonstration numbers.&lt;/p&gt;
&lt;h2 data-hextra-search-id="images-help-when-they-serve-a-purpose"&gt;Images help when they serve a purpose&lt;span class="hx:absolute hx:-mt-20" id="images-help-when-they-serve-a-purpose"&gt;&lt;/span&gt;
&lt;a href="#images-help-when-they-serve-a-purpose" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I liked the idea of showing covers on posts. They can make an entry easier to recognize and introduce a concept, such as a workflow or an architecture. I kept them optional: a short note may work perfectly well with a title and text alone.&lt;/p&gt;
&lt;p&gt;The newest publication receives more space on the home page. The others follow with a title, summary, and metadata, plus a thumbnail when a cover exists. On the article page, the image appears in full, with alternative text and a caption when provided.&lt;/p&gt;
&lt;p&gt;I also record its dimensions to reserve space before it loads. To explain this build, I chose actual screenshots of the process. This part&amp;rsquo;s cover shows the Archive in the local preview, with articles from this series. The caption identifies the environment: a local screenshot can show the interface, but it does not prove that version is already published.&lt;/p&gt;
&lt;h2 data-hextra-search-id="search-needs-to-find-what-is-in-the-text"&gt;Search needs to find what is in the text&lt;span class="hx:absolute hx:-mt-20" id="search-needs-to-find-what-is-in-the-text"&gt;&lt;/span&gt;
&lt;a href="#search-needs-to-find-what-is-in-the-text" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;During the revisions, search became a point of attention. The button needed to be easy to notice, open reliably, and find more than titles. A good-looking control that gets stuck interrupts the reader&amp;rsquo;s path.&lt;/p&gt;
&lt;p&gt;The implementation uses FlexSearch in the browser, with an index for the current language. It considers titles, descriptions, summaries, tags, and full text. Accent normalization lets a Portuguese reader search for &lt;code&gt;publicacao&lt;/code&gt; and find content containing “publicação.” The button and &lt;code&gt;Ctrl+K&lt;/code&gt; or &lt;code&gt;⌘K&lt;/code&gt; open the dialog.&lt;/p&gt;
&lt;p&gt;There are two search tools with different jobs. The &lt;strong&gt;Archive&lt;/strong&gt; field filters its list using titles, summaries, and topics. The &lt;strong&gt;header&lt;/strong&gt; search also looks inside articles. Someone who remembers a passage about &lt;code&gt;translationKey&lt;/code&gt; can use the latter to find this post.&lt;/p&gt;
&lt;p&gt;Local search follows the content generated by Hugo. It requires JavaScript in the browser but does not require me to operate a search server.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://figueirego.com/blog/images/blog-search-process.webp" alt="The blog’s local preview showing a search for translationKey and its results." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Searching the local preview for &lt;code&gt;translationKey&lt;/code&gt;, before publishing the series.&lt;/em&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="two-languages-a-linked-publication"&gt;Two languages, a linked publication&lt;span class="hx:absolute hx:-mt-20" id="two-languages-a-linked-publication"&gt;&lt;/span&gt;
&lt;a href="#two-languages-a-linked-publication" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In that version, English lived at the root of &lt;code&gt;blog.figueirego.com&lt;/code&gt;; Portuguese lived under &lt;code&gt;/pt/&lt;/code&gt; on the same subdomain. Each version has its own Markdown file. The language selector opens the corresponding translation when one exists; clicking it does not translate the text.&lt;/p&gt;
&lt;p&gt;The relationship between this article&amp;rsquo;s versions is explicit:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;translationKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;building-blog-reading&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Both files use the same key, even though their titles and URLs differ. That allows the translation to be edited as a piece of writing in its own right. It also means an editorial correction needs to be considered in both languages.&lt;/p&gt;
&lt;p&gt;Language does not determine appearance. By default, the theme follows the system. One click switches between light and dark and saves the preference in that browser. Icon transitions respect the reader&amp;rsquo;s reduced-motion preference.&lt;/p&gt;
&lt;h2 data-hextra-search-id="one-conversation-across-both-versions"&gt;One conversation across both versions&lt;span class="hx:absolute hx:-mt-20" id="one-conversation-across-both-versions"&gt;&lt;/span&gt;
&lt;a href="#one-conversation-across-both-versions" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In the initial test, switching languages could lead to a different discussion. For the same article, I preferred shared comments: English and Portuguese readers can participate in the same conversation.&lt;/p&gt;
&lt;p&gt;Disqus uses an identity shared by the translations, based on the &lt;code&gt;translationKey&lt;/code&gt;. Its interface follows the page language. Each person&amp;rsquo;s comments remain in the language in which they wrote them.&lt;/p&gt;
&lt;p&gt;Comments load automatically on eligible production publications. If loading fails, a retry option appears. The local preview does not open the live discussions.&lt;/p&gt;
&lt;p&gt;Theme changes required an extra precaution. The Disqus editor lives in an iframe from another domain. After a reader interacts with it, the blog preserves the component when changing theme because it may contain an unsent comment. It offers a manual refresh instead. Readers should submit or copy their text before using that option.&lt;/p&gt;
&lt;p&gt;This is an external integration, with its own moderation, appearance settings, and advertisements on the selected plan. A static blog still has dependencies; it simply means I do not maintain a comments backend myself.&lt;/p&gt;
&lt;h2 data-hextra-search-id="following-the-blog-without-another-task-to-manage"&gt;Following the blog without another task to manage&lt;span class="hx:absolute hx:-mt-20" id="following-the-blog-without-another-task-to-manage"&gt;&lt;/span&gt;
&lt;a href="#following-the-blog-without-another-task-to-manage" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I kept RSS in the footer, with a page that explains it and lets readers copy the feed address. Published entries from all five formats appear in their language&amp;rsquo;s RSS feed. Anyone using a feed reader can follow new posts there.&lt;/p&gt;
&lt;p&gt;The newsletter is left out for now. Most read depends on real data, which I cover in &lt;a href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;part three, on publishing and operations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To try the organization, open the Archive and select &lt;strong&gt;software engineering&lt;/strong&gt;. Then search for &lt;code&gt;translationKey&lt;/code&gt; from the header and switch this article&amp;rsquo;s language. Those paths provide a more useful test of the interface than a screenshot of the home page alone.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Building my blog series:&lt;/strong&gt; &lt;a href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;1. Architecture&lt;/a&gt; · &lt;strong&gt;2. Reading experience&lt;/strong&gt; · &lt;a href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;3. Publishing and operations&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Building my blog, part 3: from Markdown to publishing</title><link>https://figueirego.com/blog/posts/building-the-blog-03-publishing/</link><pubDate>Tue, 13 Oct 2026 12:01:00 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-03-publishing/</guid><description>
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Editorial note:&lt;/strong&gt; This series documents the initial Hugo blog, which was separate from the portfolio. The unified version of the site brings both together at figueirego.com, with the blog under &lt;code&gt;/blog/&lt;/code&gt; (English) and &lt;code&gt;/pt/blog/&lt;/code&gt; (Portuguese). The commands, screenshots, and &lt;code&gt;main&lt;/code&gt; → &lt;code&gt;published&lt;/code&gt; → &lt;code&gt;blog.figueirego.com&lt;/code&gt; workflow below belong to the standalone version; they do not describe publishing the unified site.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The file containing this article lives in &lt;code&gt;content/en/posts/&lt;/code&gt;. Its Portuguese version lives in &lt;code&gt;content/pt/posts/&lt;/code&gt;. To publish, I review both, generate the site, check the output, and push the changes to GitHub. The hosting platform receives finished pages.&lt;/p&gt;
&lt;p&gt;That was the experience I wanted: being able to write without looking after a publishing application running around the clock. There are still tools and services involved. The difference is knowing what each one does and keeping the path between the text and its reader short.&lt;/p&gt;
&lt;p&gt;This is the third part of building the blog. In &lt;a href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;part one, I explained the architecture&lt;/a&gt;; in &lt;a href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;part two, the reading and navigation decisions&lt;/a&gt;. Here, I&amp;rsquo;ll walk through publishing from start to finish.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-editor-works-on-a-file-on-my-computer"&gt;The editor works on a file on my computer&lt;span class="hx:absolute hx:-mt-20" id="the-editor-works-on-a-file-on-my-computer"&gt;&lt;/span&gt;
&lt;a href="#the-editor-works-on-a-file-on-my-computer" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I write Markdown in a code editor. To start an article, I use the template already in the repository:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo new content content/pt/posts/meu-artigo.md --kind default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo new content content/en/posts/my-article.md --kind default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Both files start as drafts. The header, called &lt;em&gt;front matter&lt;/em&gt;, holds the information the layout needs. Here is a shortened example following this project&amp;rsquo;s conventions:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;How I published my blog&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ld"&gt;2026-10-13T09:00:00&lt;/span&gt;&lt;span class="m"&gt;-03&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Matheus Figueirêdo&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;translationKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;how-i-published-my-blog&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;From writing in Markdown to publishing.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;The decisions and workflow behind putting the blog online.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;software engineering&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hugo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/images/my-cover.webp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;imageAlt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;The blog Archive, with publications organized by year.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;imageCaption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Screenshot of the local Archive preview.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;imageWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1512&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;imageHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;805&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The title, summary, and body change in the translation. The &lt;code&gt;translationKey&lt;/code&gt; stays the same, even when the filenames differ. That is how I connect the two versions. The language selector finds an existing translation; it does not translate the article itself. This use of the key follows &lt;a href="https://gohugo.io/content-management/front-matter/" target="_blank" rel="noopener"&gt;Hugo&amp;rsquo;s front matter documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dimensions must match the actual image. A cover is optional: a short note may work better as text alone. To insert an image into the body, I use the usual syntax:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;Description of what the diagram explains&lt;/span&gt;](&lt;span class="na"&gt;/images/my-diagram.webp&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-hextra-search-id="saving-previewing-and-publishing-are-different-steps"&gt;Saving, previewing, and publishing are different steps&lt;span class="hx:absolute hx:-mt-20" id="saving-previewing-and-publishing-are-different-steps"&gt;&lt;/span&gt;
&lt;a href="#saving-previewing-and-publishing-are-different-steps" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;While writing, I start the preview:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server --buildDrafts --bind 127.0.0.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The local address lets me review the text, images, and both languages. &lt;code&gt;--buildDrafts&lt;/code&gt; includes drafts in this preview. Saving updates the preview; none of this puts the article on the internet.&lt;/p&gt;
&lt;p&gt;For the standalone version, before publishing, I changed &lt;code&gt;draft&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; and checked the output with this production configuration:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m unittest discover -s tests -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --config hugo.yaml,hugo.production.yaml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --environment production --minify --panicOnWarning &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --cleanDestinationDir &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --baseURL https://blog.figueirego.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 scripts/check-site.py public &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --production-url https://blog.figueirego.com/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Tests cover the project&amp;rsquo;s behavior; the checker inspects the generated HTML, local links, translations, feeds, and publication metadata. The production configuration also removes the indexing restrictions used in the preview. Building only the local version and copying its files could carry those restrictions onto the public site.&lt;/p&gt;
&lt;p&gt;Tests do not replace reading. I still need to check whether the cover fits the text, whether the translation sounds natural, and whether the examples make sense.&lt;/p&gt;
&lt;h2 data-hextra-search-id="github-delivers-hostinger-serves-the-pages"&gt;GitHub delivers; Hostinger serves the pages&lt;span class="hx:absolute hx:-mt-20" id="github-delivers-hostinger-serves-the-pages"&gt;&lt;/span&gt;
&lt;a href="#github-delivers-hostinger-serves-the-pages" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In that version, after reviewing, I pushed the content files and any required local images to &lt;code&gt;main&lt;/code&gt;. The workflow repeated the tests, ran Hugo, and prepared the &lt;code&gt;published&lt;/code&gt; branch with the output.&lt;/p&gt;
&lt;p&gt;The publishing path for the standalone blog looked like this:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Markdown and local images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitHub: main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tests + Hugo + generated-file checks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GitHub: published
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hostinger → blog.figueirego.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The source remains separate from the files being served. I do not edit &lt;code&gt;published&lt;/code&gt; manually because the workflow generates its contents.&lt;/p&gt;
&lt;p&gt;That branch includes a minimal &lt;code&gt;package.json&lt;/code&gt; for compatibility with Hostinger&amp;rsquo;s preset. It has no dependencies, and its build command only checks that &lt;code&gt;public/index.html&lt;/code&gt; exists. Hugo has already run on GitHub; the hosting platform receives finished HTML, CSS, JavaScript, and assets. I do not need to maintain my own application server or database to deliver the articles.&lt;/p&gt;
&lt;p&gt;A publication is confirmed only after checking three things: the GitHub checks, the corresponding deployment in Hostinger, and the page on the live website. A green workflow proves that execution succeeded; it does not, by itself, prove which version a reader received.&lt;/p&gt;
&lt;h2 data-hextra-search-id="images-can-travel-with-the-site-or-live-in-s3"&gt;Images can travel with the site or live in S3&lt;span class="hx:absolute hx:-mt-20" id="images-can-travel-with-the-site-or-live-in-s3"&gt;&lt;/span&gt;
&lt;a href="#images-can-travel-with-the-site-or-live-in-s3" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;AWS joined the setup as image storage. The pages remain on Hostinger. A file in &lt;code&gt;static/images/&lt;/code&gt; goes with the build; an image in S3 is uploaded separately and referenced by its public URL.&lt;/p&gt;
&lt;p&gt;I do not need to move every image to AWS to use the blog. I can publish a local cover with its article and use S3 when I want a file outside the site&amp;rsquo;s package. The build does not upload images automatically.&lt;/p&gt;
&lt;p&gt;For uploads, I configured a dedicated IAM user with permissions limited to the bucket&amp;rsquo;s image prefix. It uses passkey MFA and temporary AWS CLI authentication, without a permanent access key inside the project. AWS documents this &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sign-in.html" target="_blank" rel="noopener"&gt;browser-based sign-in flow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The upload script lets me inspect the destination before sending anything. The names below are examples; they must be replaced with the values of an environment that has already been configured:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws login --profile my-blog --region us-east-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 scripts/upload-image.py /path/to/cover.webp &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --bucket my-image-bucket &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --base-url https://my-image-bucket.s3.us-east-1.amazonaws.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --profile my-blog &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --prefix images &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --dry-run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;--dry-run&lt;/code&gt; validates the file and shows a possible destination, but uploads nothing and does not confirm public access. To upload, I repeat the script command without that option. Each execution generates a new key; the simulated URL will not be the final URL.&lt;/p&gt;
&lt;p&gt;After uploading, I open the returned URL without an AWS session and check the image. The script does not make a private bucket public: read access must already be configured. In my workflow, published images use HTTPS without a temporary signature, so the address does not expire with a session.&lt;/p&gt;
&lt;p&gt;Uploads use long-lived caching with &lt;code&gt;immutable&lt;/code&gt; and unique filenames. If I correct an image, I upload another file and update its URL in the text. The previous version remains in the bucket until removed. There is also a cost alert: it reports spending but does not enforce a cap or shut resources down. A simple blog is not a promise of a zero-dollar bill.&lt;/p&gt;
&lt;h2 data-hextra-search-id="most-read-without-fetching-statistics-on-every-visit"&gt;“Most read” without fetching statistics on every visit&lt;span class="hx:absolute hx:-mt-20" id="most-read-without-fetching-statistics-on-every-visit"&gt;&lt;/span&gt;
&lt;a href="#most-read-without-fetching-statistics-on-every-visit" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;GoatCounter records visits, and GitHub fetches those statistics during publication and through a scheduled daily run. The result becomes a static list included in the homepage.&lt;/p&gt;
&lt;p&gt;The token has permission to read statistics and lives in a GitHub secret, available to the step responsible for that request. It does not enter the reader&amp;rsquo;s JavaScript. The browser runs the public counting script; the ranking is assembled before the page is served. Authentication follows the &lt;a href="https://www.goatcounter.com/help/api" target="_blank" rel="noopener"&gt;GoatCounter API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The filters have precise meanings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Week:&lt;/strong&gt; the seven complete days before the update, in UTC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Month:&lt;/strong&gt; the thirty complete days before the update, in UTC.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are rolling windows, not calendar weeks or months. The current day is excluded because it is incomplete. The list updates daily, with no promise of real-time results.&lt;/p&gt;
&lt;p&gt;Visits to Portuguese and English versions are combined using &lt;code&gt;translationKey&lt;/code&gt;; links and titles appear in the reader&amp;rsquo;s language. The number represents recorded visits, not a guarantee of completed reading or unique people across languages. Blockers can also prevent collection.&lt;/p&gt;
&lt;p&gt;If a request fails, I preserve that period&amp;rsquo;s last valid result with its original timestamp. Without valid data, I do not invent numbers: the list stays empty, and the section remains hidden when neither period has content.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-routine-after-launch"&gt;The routine after launch&lt;span class="hx:absolute hx:-mt-20" id="the-routine-after-launch"&gt;&lt;/span&gt;
&lt;a href="#the-routine-after-launch" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Comments live in Disqus and need moderation. RSS includes eligible publications in each language and offers a way to follow the blog. The newsletter is out of scope for now.&lt;/p&gt;
&lt;p&gt;Delegating these functions reduces what I operate, but leaves dependencies: hosting, storage, comments, and statistics can fail or change their terms. The text and its publication remain grounded in versioned files.&lt;/p&gt;
&lt;p&gt;For the next article, my routine is concrete: write, review the translation, prepare the cover, check the preview, run the checks, push the changes, and open the published page. Then respond to comments and fix anything that needs it. The infrastructure has to leave room for that work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this series:&lt;/strong&gt; &lt;a href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;1. Architecture&lt;/a&gt; · &lt;a href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;2. Reading and navigation&lt;/a&gt; · &lt;strong&gt;3. Publishing&lt;/strong&gt;.&lt;/p&gt;</description></item></channel></rss>