<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Figueirego Blog – Next.js</title><link>https://figueirego.com/blog/tags/next.js/</link><description>Recent posts in Next.js 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/next.js/index.xml" rel="self" type="application/rss+xml"/><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 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>