<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Figueirego Blog – Behind the blog</title><link>https://figueirego.com/blog/tags/behind-the-blog/</link><description>Recent posts in Behind the blog on Figueirego Blog.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 04:33:15 -0300</lastBuildDate><atom:link href="https://figueirego.com/blog/tags/behind-the-blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Building my blog, part 5: SEO, languages, and RSS</title><link>https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-seo-rss/seo-rss.webp" alt="Library with a card catalog, an open book and a window overlooking illuminated homes at night."&gt;&lt;/p&gt;&lt;p&gt;An article can open normally in the browser and still present itself incorrectly elsewhere. Its search title may be generic, a Portuguese page may advertise the English feed, or a topic feed may contain every article on the blog.&lt;/p&gt;
&lt;p&gt;While reviewing the website, I began looking at these outputs too. Published content needs to remain identifiable outside the layout I designed for it.&lt;/p&gt;
&lt;h2 data-hextra-search-id="visible-identity-and-metadata-have-their-own-roles"&gt;Visible identity and metadata have their own roles&lt;span class="hx:absolute hx:-mt-20" id="visible-identity-and-metadata-have-their-own-roles"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;On the page, a title and description help people understand where they are. Metadata provides the title, description, URL, and other information used by external services. These fields should agree about the content without repeating the brand in every visible sentence.&lt;/p&gt;
&lt;p&gt;Articles in this project use editorial data to generate social metadata and structured information. Review starts with the writing: a specific title, a description explaining the subject, and sections that deliver what was promised. Adding search terms that the article never explains would not help.&lt;/p&gt;
&lt;p&gt;Each part of this series has a focus: architecture, reading, publishing, images, or discovery. Links between them help readers follow the story without turning five articles into variations of the same introduction.&lt;/p&gt;
&lt;h2 data-hextra-search-id="two-languages-need-clear-addresses"&gt;Two languages need clear addresses&lt;span class="hx:absolute hx:-mt-20" id="two-languages-need-clear-addresses"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;English articles live under &lt;code&gt;/blog/&lt;/code&gt;; Portuguese articles under &lt;code&gt;/pt/blog/&lt;/code&gt;. A &lt;code&gt;translationKey&lt;/code&gt; relates the files, while language metadata points to the equivalent pages.&lt;/p&gt;
&lt;p&gt;A canonical URL identifies the preferred address for that page. &lt;code&gt;hreflang&lt;/code&gt; identifies language alternatives. The Portuguese version should not treat English as the only primary page and lose its own identity. Both versions need consistent, corresponding links.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developers.google.com/search/docs/specialty/international/localized-versions" target="_blank" rel="noopener"&gt;Google&amp;rsquo;s localized-version documentation&lt;/a&gt; explains the relationship between these alternatives. In my workflow, validation of generated files helps identify incorrect translation links before publication.&lt;/p&gt;
&lt;p&gt;The sitemap should also reflect what is publicly available. Drafts stay out of normal publication, and a preview should not be treated as the final address.&lt;/p&gt;
&lt;h2 data-hextra-search-id="requesting-indexing-does-not-prove-the-outcome"&gt;Requesting indexing does not prove the outcome&lt;span class="hx:absolute hx:-mt-20" id="requesting-indexing-does-not-prove-the-outcome"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Search Console has different stages that can look like one when you focus only on a success message:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accepted request&lt;/td&gt;
&lt;td&gt;The request was received.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page already indexed&lt;/td&gt;
&lt;td&gt;A version of the page exists in the index.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New crawl&lt;/td&gt;
&lt;td&gt;Google fetched the page again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Displayed search result&lt;/td&gt;
&lt;td&gt;You can observe how it appeared for that query.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A crawl after an edit does not, by itself, demonstrate that the new description was adopted in the result. It does not guarantee a position for a keyword either. Google explicitly notes that &lt;a href="https://developers.google.com/search/docs/crawling-indexing/ask-google-to-recrawl" target="_blank" rel="noopener"&gt;requesting another crawl does not guarantee immediate inclusion or inclusion in the index&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The project includes a routine that reads indexing status for the main pages. It tracks changes and failures; it does not submit a new request or automatically track every future article. The monitored set needs review as the website grows.&lt;/p&gt;
&lt;h2 data-hextra-search-id="rss-is-another-presentation-of-the-same-content"&gt;RSS is another presentation of the same content&lt;span class="hx:absolute hx:-mt-20" id="rss-is-another-presentation-of-the-same-content"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;RSS lets people follow publications through a feed reader. They subscribe to an address and receive items without visiting the home page to check for something new.&lt;/p&gt;
&lt;p&gt;The general Portuguese feed is at &lt;code&gt;/pt/blog/index.xml&lt;/code&gt;; English is at &lt;code&gt;/blog/index.xml&lt;/code&gt;. Topic and section pages can also have their own feeds. The scope needs to match the page: someone subscribing to a topic expects articles about that topic.&lt;/p&gt;
&lt;p&gt;Identification happens in two layers. HTML advertises the feed through a discovery link, and the XML itself identifies its title, language, link, and &lt;code&gt;self&lt;/code&gt; URL. A Portuguese page advertising the English feed would be wrong even if both files were valid XML.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-seo-rss/rss-real-en.webp" alt="Follow via RSS page showing the English feed URL and Copy address button." width="1512" height="893" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;The public English RSS page presents the feed address for that language. Readers subscribe to this URL in their feed application.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-seo-rss/rss-real-en.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-the-feed-review-needed-to-check"&gt;What the feed review needed to check&lt;span class="hx:absolute hx:-mt-20" id="what-the-feed-review-needed-to-check"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The template starts with eligible publications and applies the section or topic scope before the fifty-item limit. A less frequent topic therefore does not disappear simply because the fifty newest articles overall cover other subjects.&lt;/p&gt;
&lt;p&gt;Drafts, samples, and unlisted content are excluded, as are future publication dates. In production, the filter also excludes content marked &lt;code&gt;noindex&lt;/code&gt;. That needs checking in the generated XML as well as in the template.&lt;/p&gt;
&lt;p&gt;Within an item, relative images and links need to keep working outside the website. An address relying on the current page can resolve incorrectly in a feed reader. Feed content resolves those references against the article&amp;rsquo;s URL and includes a cover when available.&lt;/p&gt;
&lt;p&gt;Dates have separate meanings too. &lt;code&gt;pubDate&lt;/code&gt; records publication and preserves the real offset, such as &lt;code&gt;-0300&lt;/code&gt;. When revising a published article, I set &lt;code&gt;lastmod&lt;/code&gt; to the date of that change:&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="c"&gt;# Example of a revision after publication.&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-19T09: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;lastmod&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-20T10:30: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&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 channel&amp;rsquo;s &lt;code&gt;lastBuildDate&lt;/code&gt; uses the latest modification date among its included items. In this project, editing a file without setting &lt;code&gt;lastmod&lt;/code&gt; does not automatically turn the commit date into an editorial date.&lt;/p&gt;
&lt;h2 data-hextra-search-id="keep-a-routine-i-can-repeat"&gt;Keep a routine I can repeat&lt;span class="hx:absolute hx:-mt-20" id="keep-a-routine-i-can-repeat"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Before publishing, I review the title and description, translation, links between parts, and cover metadata. After building, I check URLs, feeds, and which pages entered the publication. Once the version reaches the host, I check the served files.&lt;/p&gt;
&lt;p&gt;If I started again, I would organize design and architecture earlier, separating each technology and its purpose. Reviewing SEO and RSS reinforced that lesson: each output has its own job, but all of them need to tell the same story about the content.&lt;/p&gt;
&lt;p&gt;The website will keep changing. I want these decisions recorded so I can return to them when a tool, integration, or my own idea of a blog changes.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-04-images/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item><item><title>Building my blog, part 4: uploading images to S3 from Finder</title><link>https://figueirego.com/blog/posts/building-the-blog-04-images/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-04-images/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/imagens.webp" alt="Photographs arranged on a desk, archive drawers and a computer showing image previews."&gt;&lt;/p&gt;&lt;p&gt;Select an image, right-click, and receive a URL ready for the article. That was the convenience I wanted in my blogging routine. Automating AWS took the most work after the design.&lt;/p&gt;
&lt;p&gt;The path began with an upload helper and became a Finder Quick Action. The successful case was easy to describe. The work appeared in the next questions: what happens when the session expires, when I abandon login, or when the upload fails?&lt;/p&gt;
&lt;h2 data-hextra-search-id="pages-on-hostinger-images-on-s3"&gt;Pages on Hostinger, images on S3&lt;span class="hx:absolute hx:-mt-20" id="pages-on-hostinger-images-on-s3"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;S3 stores objects: in this case, image files. Pages remain on Hostinger. Markdown contains the file&amp;rsquo;s public URL, and the browser retrieves the image from that address.&lt;/p&gt;
&lt;p&gt;This separates image uploads from the website build. Adding a file to the repository does not execute an AWS upload. Some local assets also travel with the build; new editorial images use the S3 workflow.&lt;/p&gt;
&lt;p&gt;A published image URL needs to work after my session ends. That is why the article does not use a temporary signed URL. The image prefix has public read access configured, while writing requires authentication. The upload script does not make any arbitrary private bucket public.&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-i-mean-by-image-versions"&gt;What I mean by image versions&lt;span class="hx:absolute hx:-mt-20" id="what-i-mean-by-image-versions"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Each upload receives a new name containing the year, month, a normalized name, and an identifier. An illustrative path would be:&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;images/2026/10/my-cover-unique-identifier.webp&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;If I correct a cover, I upload another file and update the article&amp;rsquo;s reference. The earlier image remains available until it is removed. That helps prevent a URL with long-lived caching from unexpectedly representing different content.&lt;/p&gt;
&lt;p&gt;This differs from &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html" target="_blank" rel="noopener"&gt;S3 Versioning&lt;/a&gt;, which retains versions of the same object within the bucket. Here I am describing new keys and new URLs. Having two files does not establish that native versioning is enabled.&lt;/p&gt;
&lt;p&gt;Git history records changes to the reference in an article. By itself, it is not a backup of external files. Preserving and recovering images also requires looking after their storage.&lt;/p&gt;
&lt;h2 data-hextra-search-id="a-dedicated-upload-identity"&gt;A dedicated upload identity&lt;span class="hx:absolute hx:-mt-20" id="a-dedicated-upload-identity"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The shortcut uses an AWS CLI profile with temporary credentials for a dedicated user. It does not depend on a root session or store a permanent access key inside the project.&lt;/p&gt;
&lt;p&gt;The account and permissions are prepared beforehand. The helper uses that existing destination; it does not provision an entire AWS environment. The &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sign-in.html" target="_blank" rel="noopener"&gt;AWS CLI&amp;rsquo;s local browser login&lt;/a&gt; renews authentication without putting a password or code in an article, script, or conversation.&lt;/p&gt;
&lt;p&gt;In the configured project, installation of the Quick Action starts from the repository root:&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 blog/scripts/install-finder-upload.py&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 command belongs to the private repository and requires the environment described above. It is not a universal public installer for any AWS account. Reproducing the idea requires adapting the destination, profile, and permissions to your environment.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-finder-routine"&gt;The Finder routine&lt;span class="hx:absolute hx:-mt-20" id="the-finder-routine"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;With the action installed, I select the image and choose &lt;strong&gt;Quick Actions → Enviar imagem ao S3 — Figueirego&lt;/strong&gt;. After uploading, the action copies the URL and shows a completion notification. I do not need to open Terminal for the normal routine.&lt;/p&gt;
&lt;p&gt;If the session expired, the shortcut opens browser authentication and waits for completion. Once login is authorized, it continues uploading. Testing the full path through Finder was important: running the helper in Terminal alone would not demonstrate that notifications appeared through the macOS action.&lt;/p&gt;
&lt;p&gt;We also checked the public URL and the received image. A completion message helps, but it does not replace checking that the correct file is accessible outside the AWS session.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-acao-real.webp" alt="Finder Quick Actions menu containing Enviar imagem ao S3 — Figueirego." width="1840" height="872" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;The installed Finder action is available by right-clicking an image. The menu was opened for this screenshot without performing another upload; the custom action keeps its Portuguese name.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-acao-real.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="cancellation-is-part-of-the-interface"&gt;Cancellation is part of the interface&lt;span class="hx:absolute hx:-mt-20" id="cancellation-is-part-of-the-interface"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Waiting five minutes to discover that a login had been abandoned was a poor experience. The waiting step gained a window with &lt;strong&gt;Cancelar envio&lt;/strong&gt; (Cancel upload), making it possible to explicitly end that attempt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Expected behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid session&lt;/td&gt;
&lt;td&gt;Upload, copy the URL, and announce completion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired session&lt;/td&gt;
&lt;td&gt;Open login and continue after authentication.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellation while waiting for login&lt;/td&gt;
&lt;td&gt;Notify, do not upload the image, and preserve clipboard contents.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Another attempt after cancellation&lt;/td&gt;
&lt;td&gt;Allow the workflow to start again.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Closing the browser tab alone is not equivalent to pressing the cancellation button. And that button cancels the authentication wait; it does not promise to interrupt an upload that has already started.&lt;/p&gt;
&lt;p&gt;We tested the waiting step first with simulated AWS behavior and then with real login. The checks answer different questions: simulation helps control the scenario; the real test confirms the integration that will be used.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-en.webp" alt="From an image in Finder to a copied URL. Cancelling sign-in stops the flow before uploading." width="1200" height="1000" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;From an image in Finder to a copied URL. Cancelling sign-in stops the flow before uploading.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-en.webp" target="_blank" rel="noopener"&gt;Open the full-size diagram&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-still-requires-care"&gt;What still requires care&lt;span class="hx:absolute hx:-mt-20" id="what-still-requires-care"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I do not automatically remove an old image just because I uploaded another one. A draft, another article, or a feed already received by a reader may still refer to it. Cleanup needs to account for those references.&lt;/p&gt;
&lt;p&gt;If the upload succeeds but copying to the clipboard fails, that does not undo the uploaded object either. Distinguishing an upload failure from failure in the final convenience step avoids unnecessary repeat uploads.&lt;/p&gt;
&lt;p&gt;Automation shortened the routine. The lesson was to give cancellation and recovery proper attention too: a tool that is simple to use should explain when it finished, when it failed, and what happened to the file.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item><item><title>Building my blog, part 3: from Markdown to publication</title><link>https://figueirego.com/blog/posts/building-the-blog-03-publishing/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-03-publishing/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-publishing/publicacao.webp" alt="Bookbinding workshop with a press, organized sheets and finished notebooks on the workbench."&gt;&lt;/p&gt;&lt;p&gt;Saving an article in the editor does not mean it is published. The text still needs review, page generation, checks, and delivery to the host. After that, I need to open the public address and see what readers received.&lt;/p&gt;
&lt;p&gt;That distinction became important when integrating the blog and portfolio. The first version had a separate blog workflow. Today, both come from the same repository and are delivered by the Next.js application on Hostinger. The old subdomain is no longer the destination.&lt;/p&gt;
&lt;h2 data-hextra-search-id="an-article-starts-with-text-and-metadata"&gt;An article starts with text and metadata&lt;span class="hx:absolute hx:-mt-20" id="an-article-starts-with-text-and-metadata"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I write in Markdown. Before the body, a block called &lt;em&gt;front matter&lt;/em&gt; describes how the article should be presented. This is a reduced example, not a file ready to publish:&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 organized 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-19T09: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;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="l"&gt;how-i-organized-my-blog&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;The decisions behind content, navigation, and 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;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;Hugo&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;publishing&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="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 date needs to match the actual publication. &lt;code&gt;draft: true&lt;/code&gt; keeps the article out of the public build. In the translation, the title, description, topics, and body change; the &lt;code&gt;translationKey&lt;/code&gt; stays the same.&lt;/p&gt;
&lt;p&gt;A cover can be added later. When present, it needs a URL, alternative text, and dimensions matching the file. The article should remain readable without relying on an image to communicate its subject.&lt;/p&gt;
&lt;h2 data-hextra-search-id="two-previews-for-different-questions"&gt;Two previews for different questions&lt;span class="hx:absolute hx:-mt-20" id="two-previews-for-different-questions"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To review the writing alone, I can use Hugo&amp;rsquo;s server. These commands start at the repository root and assume the project&amp;rsquo;s pinned Hugo extended version is installed:&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;&lt;span class="nb"&gt;cd&lt;/span&gt; blog
&lt;/span&gt;&lt;/span&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 option includes drafts in the local preview. It is useful for checking text, the table of contents, and translation. To assess integration with the portfolio, I use the complete project in another terminal:&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;&lt;span class="nb"&gt;cd&lt;/span&gt; client
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm ci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run dev&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 path requires Node.js 24.12.0 or later and Hugo extended 0.167.0 in the environment. The development script builds the blog at startup. After editing Markdown, I need to rebuild that part with &lt;code&gt;node scripts/build-blog.mjs --preview&lt;/code&gt; and reload the page. This does not replace the dedicated Hugo server&amp;rsquo;s file watching, and the regular integrated build still excludes drafts.&lt;/p&gt;
&lt;p&gt;The commands depend on helpers in this private repository. For another project, the transferable idea is to keep a quick content preview and another one that exercises the complete integration.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-tested-version-is-the-one-promoted"&gt;The tested version is the one promoted&lt;span class="hx:absolute hx:-mt-20" id="the-tested-version-is-the-one-promoted"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The current workflow is:&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;reviewed files → dev → 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 and integrated build
&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; approved SHA → production
&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; public website checks&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;A SHA identifies a commit. Using it during promotion connects the tested version with the version sent for publication. CI should not approve one commit and silently promote a different one that arrived later.&lt;/p&gt;
&lt;p&gt;The central verification command runs from &lt;code&gt;client/&lt;/code&gt;:&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;node scripts/verify-release.mjs --production&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;It combines type checking, linting, formatting, tests, the Next.js build with Hugo, and checks of the generated files. I use &lt;code&gt;--dry-run&lt;/code&gt; to list the steps without executing them. Neither option pushes code to GitHub on its own.&lt;/p&gt;
&lt;p&gt;After checks pass on &lt;code&gt;main&lt;/code&gt;, automation promotes the approved commit to &lt;code&gt;production&lt;/code&gt;. Hostinger follows that branch. If &lt;code&gt;main&lt;/code&gt; advanced during the run, the new version needs its own verification. Promotion should not overwrite history with a force push.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-publishing/publicacao-en.webp" alt="Review, test, deploy and verify: the four stages in this website’s delivery flow." width="1200" height="1000" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;Review, test, deploy and verify: the four stages in this website’s delivery flow.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-publishing/publicacao-en.webp" target="_blank" rel="noopener"&gt;Open the full-size diagram&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="check-three-separate-results"&gt;Check three separate results&lt;span class="hx:absolute hx:-mt-20" id="check-three-separate-results"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;A green check reports verification. The Hostinger deployment reports whether the host could build and start the corresponding version. Opening public pages shows what is actually delivered.&lt;/p&gt;
&lt;p&gt;That is why checks include both languages, article links, images, RSS, and the route back to the rest of the website. I also need to inspect the deployment SHA. An HTTP 200 response does not demonstrate that every page component worked.&lt;/p&gt;
&lt;p&gt;Content requires an additional check: a draft must not appear in search, the sitemap, or a feed. A preview that includes drafts cannot be copied to production as though it were the public build.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-publishing/hostinger-deploy-real.webp" alt="Hostinger panel showing a completed deployment from the production branch using Next.js and Node 24." width="1092" height="195" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;Actual deployment detail from October 19, 2026, for the initial release of this series with its covers. The Portuguese control panel identifies the deployed commit.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-publishing/hostinger-deploy-real.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="most-read-lists-also-go-through-publication"&gt;Most-read lists also go through publication&lt;span class="hx:absolute hx:-mt-20" id="most-read-lists-also-go-through-publication"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;GoatCounter provides visit statistics. A GitHub job queries those data and writes a snapshot to &lt;code&gt;blog/data/popular.json&lt;/code&gt;. The build incorporates the result and serves a public file at &lt;code&gt;/blog/popular.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The browser can refresh the lists from that file without receiving the GoatCounter API token. Collection has a daily schedule and recovery attempts. Scheduled times depend on providers; they are not a guarantee of an update at an exact second.&lt;/p&gt;
&lt;p&gt;Week and month represent the previous seven and thirty complete days in UTC. Translations of the same article are aggregated by editorial identity. These are recorded visits, not unique people across languages or evidence that someone finished reading.&lt;/p&gt;
&lt;p&gt;When collection fails, the workflow preserves the previous snapshot rather than publishing invented numbers. A valid update still has to pass verification and reach the host. Monitoring the public JSON helps detect the difference between collecting data and publishing them.&lt;/p&gt;
&lt;p&gt;This routine is part of maintaining the site. Automation helps, but the result that matters remains the correct content at the address someone opens.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-04-images/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item><item><title>Building my blog, part 2: designing for clarity</title><link>https://figueirego.com/blog/posts/building-the-blog-02-reading/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-02-reading/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-reading/design-leitura.webp" alt="Reading corner with an armchair, an open book and a computer beside a window at night."&gt;&lt;/p&gt;&lt;p&gt;The old terminal made sense to me, but it could ask too much of a visitor. Someone trying to learn about my work should not have to discover a command to find information. When adapting that area into a desktop, I wanted a more familiar language: open, explore, and close windows.&lt;/p&gt;
&lt;p&gt;That did not solve the entire design. A desktop can also be confusing if everything depends on it. The website has dedicated pages for my background, projects, writing, and contact information; the interactive area is another way to explore. Conventional navigation remains available.&lt;/p&gt;
&lt;p&gt;Getting this combination right was the hardest part of building the site. I wanted it to reflect me and help people understand what they were seeing. Its appearance needed to support that reading.&lt;/p&gt;
&lt;h2 data-hextra-search-id="one-reference-for-presentation-another-for-construction"&gt;One reference for presentation, another for construction&lt;span class="hx:absolute hx:-mt-20" id="one-reference-for-presentation-another-for-construction"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;With Zeno Rocha, I was interested in how he presents himself and gives his work visibility while keeping navigation intuitive. With Akita, the reference was mainly how he built his blog and the ideas around it. These were not two interfaces I intended to combine literally.&lt;/p&gt;
&lt;p&gt;Through experiments with Claude, Antigravity, and then Codex with skills, I refined a direction that needed to fit the rest of my website. An interesting visual result could still have poor spacing, too much information in one place, or unclear interaction.&lt;/p&gt;
&lt;p&gt;This was particularly noticeable with article covers. I wanted to retain the title, summary, author, date, reading time, and topics. When those elements were too close together, a large image did not compensate for the missing hierarchy. The gradient also needed revision: a transition that seemed acceptable in dark mode could form a heavy band in light mode.&lt;/p&gt;
&lt;h2 data-hextra-search-id="give-each-area-a-purpose"&gt;Give each area a purpose&lt;span class="hx:absolute hx:-mt-20" id="give-each-area-a-purpose"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Featured articles present a selection of up to three pieces. Recent articles show the latest publications and can repeat a featured item. The Archive provides a way to explore more content and filter it.&lt;/p&gt;
&lt;p&gt;Topics appear as small labels without a hashtag symbol. A label communicates a category; its appearance should still suggest that it is clickable. Similarly, a return link should explain its destination. A link promising to return to the Archive needs to go there.&lt;/p&gt;
&lt;p&gt;These details can look minor in isolation. Together, they determine whether someone can predict the next step. I began looking at each element&amp;rsquo;s purpose as well as its position in a screenshot.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-reading/arquivo-real-en.webp" alt="Blog archive with search, format and year filters, and a topic list." width="1512" height="893" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;The published Archive in English, captured on October 19, 2026. Search, filters, and topics each serve a different navigation task.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-reading/arquivo-real-en.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="motion-should-follow-the-action"&gt;Motion should follow the action&lt;span class="hx:absolute hx:-mt-20" id="motion-should-follow-the-action"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Featured images appear in grayscale on devices with a mouse and gain color on hover. The first transition felt too abrupt. We adjusted it to give the change time to register, with a slight enlargement of the image.&lt;/p&gt;
&lt;p&gt;The table of contents needed similar attention. Clicking from the first topic to the last should not make the indicator compete for attention with every intermediate section during scrolling. Navigation began treating that movement as an action with a chosen destination.&lt;/p&gt;
&lt;p&gt;These effects need alternatives. A phone does not have a reliable equivalent of mouse hover. Keyboard focus should appear without depending on a slow animation. For people who request reduced motion in their system settings, movement and transitions should be reduced or removed. The preference is read through the &lt;code&gt;prefers-reduced-motion&lt;/code&gt; media query.&lt;/p&gt;
&lt;p&gt;The goal is not to make visitors wait for an effect before using the site. Motion should explain what is changing and let navigation continue.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-reading/leitura-sumario-real-en.webp" alt="English article with a sidebar table of contents and Beyond programming selected." width="1512" height="893" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;An actual screenshot of the English reading view. The table of contents keeps the selected section visible beside the text.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-reading/leitura-sumario-real-en.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="reading-in-portuguese-or-english"&gt;Reading in Portuguese or English&lt;span class="hx:absolute hx:-mt-20" id="reading-in-portuguese-or-english"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The Portuguese blog lives under &lt;code&gt;/pt/blog/&lt;/code&gt;; the English version is under &lt;code&gt;/blog/&lt;/code&gt;. Each article has one file per language. Both share a &lt;code&gt;translationKey&lt;/code&gt;:&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="c"&gt;# In both files; the title and body are translated.&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="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;The language selector opens the corresponding version when one exists. It does not automatically translate the text. Summaries, labels, image alternative text, and links between parts of a series also need review.&lt;/p&gt;
&lt;p&gt;Search uses a local index containing text, titles, and other editorial fields. It helps find a word inside an article; Archive filters help narrow a collection by criteria. Those are related tasks, but they are not identical.&lt;/p&gt;
&lt;h2 data-hextra-search-id="real-comments-and-the-limits-of-testing"&gt;Real comments and the limits of testing&lt;span class="hx:absolute hx:-mt-20" id="real-comments-and-the-limits-of-testing"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I kept the existing Disqus integration. Reaction buttons that only stored a choice in the browser did not provide a real interaction with me, so that proposal was left out.&lt;/p&gt;
&lt;p&gt;Loading comments showed why an attractive screenshot does not prove something works. We had to review the domain authorized in Disqus and test automatic loading. Tool interference and changing browser tabs also affected some checks. Diagnosis needed to distinguish a service failure, website configuration, and a problem in the test itself.&lt;/p&gt;
&lt;p&gt;In production, comments attempt to load automatically. The retry button is a recovery path for failures. Production integration is disabled in previews so experiments do not mix with real discussions.&lt;/p&gt;
&lt;p&gt;Today, I would review a page by following its tasks: find an article, change language, use the table of contents, return to the listing, and locate another subject. I would repeat that with a keyboard, light and dark themes, and a phone-sized viewport. Emulation helps identify layout problems, but it is not the same as testing a physical device. That use is what shows whether the design is doing its job.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-04-images/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item><item><title>Building my blog, part 1: from landing page to website</title><link>https://figueirego.com/blog/posts/building-the-blog-01-architecture/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-01-architecture/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-architecture/arquitetura.webp" alt="Model of three buildings on a wooden desk, with architectural drawings and an open sketchbook."&gt;&lt;/p&gt;&lt;p&gt;I already had a website project. It was a landing page with an idea I still wanted to keep, but it needed more room. I decided to reuse that foundation, divide the content into sections, and improve how I presented my work and the things I wanted to write about.&lt;/p&gt;
&lt;p&gt;One of the most visible changes was turning a terminal experience into a desktop. The terminal spoke a language closely associated with programming. I wanted people to explore without knowing commands. Windows and icons seemed like a more familiar starting point, although the result still had to work in practice and on a phone.&lt;/p&gt;
&lt;p&gt;The blog became part of that same effort. My concern about combining the two was confusing the navigation and failing to explain what each area offered. That is why this series starts with how the project is organized. The code came with decisions about communication.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-architecture/desktop-real-en.webp" alt="Portfolio desktop with an explorer, folders, and an application dock." width="1512" height="893" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;The actual portfolio desktop: windows and folders provide an alternative way to explore while conventional navigation remains available.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-architecture/desktop-real-en.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="references-with-different-roles"&gt;References with different roles&lt;span class="hx:absolute hx:-mt-20" id="references-with-different-roles"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://akitaonrails.com/2025/09/10/meu-novo-blog-como-eu-fiz/" target="_blank" rel="noopener"&gt;Akita&amp;rsquo;s blog&lt;/a&gt; helped me think about building a blog and the routine of publishing. &lt;a href="https://zenorocha.com/" target="_blank" rel="noopener"&gt;Zeno Rocha&amp;rsquo;s website&lt;/a&gt; was a reference for presentation: showing your work while keeping exploration intuitive.&lt;/p&gt;
&lt;p&gt;I used these references to study what I could improve, reuse, or discard. I had an idea of my own to preserve and needed to understand which solutions would help express it. Getting the design to convey that identity turned out to be the hardest part.&lt;/p&gt;
&lt;p&gt;AI tools were part of that investigation. I relied more on Codex for information and examining the project, and mainly on Claude and Antigravity for design. Then I returned to Codex with skills to separate and explore each concept. That division describes my process, not a rule about which tool everyone should use.&lt;/p&gt;
&lt;h2 data-hextra-search-id="where-the-website-ends-and-the-blog-begins"&gt;Where the website ends and the blog begins&lt;span class="hx:absolute hx:-mt-20" id="where-the-website-ends-and-the-blog-begins"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Today, the portfolio uses Next.js, React, and TypeScript. Articles live in Markdown files and Hugo generates their pages. Hextra provides the theme foundation, with customizations in the project files.&lt;/p&gt;
&lt;p&gt;Hugo is a generator: it reads the text and metadata and produces HTML, CSS, JavaScript, and indexes. Next.js brings that output together with the portfolio pages. Hostinger runs the Next.js application and serves everything under the same domain.&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;blog/content/ → Hugo → blog pages, search, and feeds
&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; client/public/blog/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client/public/pt/blog/
&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; Next.js + portfolio pages → Hostinger&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 corrects a description of the first version, when the blog was separate. The current website has a Next.js server in production; Hugo runs only while generating the files. Articles do not need a custom editing API or a database query to assemble their content for each visit.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-architecture/arquitetura-en.webp" alt="Markdown passes through Hugo; generated files are integrated into Next.js and served by Hostinger." width="1200" height="1000" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;Markdown passes through Hugo; generated files are integrated into Next.js and served by Hostinger.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-architecture/arquitetura-en.webp" target="_blank" rel="noopener"&gt;Open the full-size diagram&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="each-technology-needs-a-clear-job"&gt;Each technology needs a clear job&lt;span class="hx:absolute hx:-mt-20" id="each-technology-needs-a-clear-job"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Responsibility in this project&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;Text, summary, date, language, and other editorial metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hugo and Hextra&lt;/td&gt;
&lt;td&gt;Blog generation and the layout foundation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;td&gt;Portfolio pages and integrated delivery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub and Actions&lt;/td&gt;
&lt;td&gt;Code and content history, tests, and release preparation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hostinger&lt;/td&gt;
&lt;td&gt;Building and running the published version.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Storing images uploaded separately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disqus&lt;/td&gt;
&lt;td&gt;Discussions and comment moderation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoatCounter&lt;/td&gt;
&lt;td&gt;Visit statistics used by the most-read lists.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I did not add a database just to store articles. I also kept image storage outside Hugo&amp;rsquo;s responsibilities. That separation helps locate problems: an unavailable image, a missing article, and comments that fail to load may have different causes.&lt;/p&gt;
&lt;h2 data-hextra-search-id="organization-visible-in-the-files"&gt;Organization visible in the files&lt;span class="hx:absolute hx:-mt-20" id="organization-visible-in-the-files"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The main structure 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;client/ Next.js application
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/content/pt/ Portuguese content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/content/en/ English content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/layouts/ custom layouts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/assets/ styles and interactions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/themes/hextra/ base theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog/scripts/ publishing and image helpers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.github/workflows/ checks and integrated automation&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;Customizations live outside the theme directory. That makes an upgrade easier to review without mixing every local change into Hextra&amp;rsquo;s original code. An upgrade still needs testing; the folder structure does not guarantee compatibility.&lt;/p&gt;
&lt;p&gt;The active repository is private. Published output, including articles, images, and scripts delivered to the browser, is public. Readers do not need repository access to use the website, but the commands in this series are examples from my project, not a public package ready to clone.&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-i-would-organize-before-starting"&gt;What I would organize before starting&lt;span class="hx:absolute hx:-mt-20" id="what-i-would-organize-before-starting"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;If I started today, I would look more carefully at design and architecture before implementing. I would separate responsibilities, each area&amp;rsquo;s purpose, and the role of the technologies. I discovered some of that organization while building.&lt;/p&gt;
&lt;p&gt;For a similar project, I would first write down the visitor&amp;rsquo;s path: where they understand who I am, find my projects, and read my articles. Then I would map an article&amp;rsquo;s journey to publication. Those two maps help reveal which parts need to communicate and which can remain independent.&lt;/p&gt;
&lt;p&gt;In the next part, I cover what took the most iteration: making that organization visible in the interface without requiring visitors to understand the project&amp;rsquo;s architecture.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-04-images/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item></channel></rss>