<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>DevPortals.Tech | Blog</title><description>Evaluating and building better developer portals</description><link>https://devportals.tech/</link><language>en</language><item><title>Building Custom Column Components for Text Comparisons</title><link>https://devportals.tech/blog/building-custom-column-components/</link><guid isPermaLink="true">https://devportals.tech/blog/building-custom-column-components/</guid><pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I needed a way to display text side-by-side for translation comparisons in blog posts. This post walks through the two component variants I shipped, the decisions behind them, and the pattern that kept the implementation simple and maintainable.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;what-i-asked-for-vs-what-i-got&quot;&gt;What I Asked For vs. What I Got&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;While Sanity CMS has a rich ecosystem of plugins, I couldn’t find anything specifically designed for parallel text display that would work seamlessly with our Astro + Sanity setup. I asked my AI pair programmer: “Let’s build a two-column component for text comparisons.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What I had in mind:&lt;/strong&gt; A simple, clean layout - just two columns of text side-by-side. Functional. Straightforward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What the AI built:&lt;/strong&gt; A boxed container with gray background, borders, visual separators, and styled like a callout component.&lt;/p&gt;
&lt;p&gt;My first reaction was, “That’s not what I pictured.” But after reviewing it, the design was strong. It gave the comparison visual weight and emphasis. For formal translations or important side-by-side content, the container style made it stand out on the page.&lt;/p&gt;
&lt;p&gt;This is where human-AI collaboration gets interesting.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;building-two-instead-of-one&quot;&gt;Building Two Instead of One&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Instead of asking the AI to redo it to match my original vision, I said: “I like this, but can we also make a second version? A plain newspaper-style layout without the container?”&lt;/p&gt;
&lt;p&gt;We kept both. That was the real insight: &lt;strong&gt;AI does not replace your ideas, it augments them&lt;/strong&gt;. Now I had two components where I originally planned for one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Container Columns&lt;/strong&gt; — The AI’s interpretation with emphasis styling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Newspaper Columns&lt;/strong&gt; — The original vision of subtle, flowing text&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Two different tools for different contexts.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;container-columns-the-creative-interpretation&quot;&gt;Container Columns: The Creative Interpretation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The first implementation was more elaborate than my original ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Two text input fields&lt;/strong&gt; (left and right columns)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;50/50 split&lt;/strong&gt; on desktop&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responsive stacking&lt;/strong&gt; on mobile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual container&lt;/strong&gt; with background and borders for emphasis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional label&lt;/strong&gt; for headings like “English | Spanish”&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;schema-definition&quot;&gt;Schema Definition&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The schema was text-only to keep it simple:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;src/sanity/schemaTypes/objects/containerColumns.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defineType&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;containerColumns&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Container Columns&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fields: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;leftText&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Rule&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; Rule&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rightText&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Rule&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; Rule&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Optional label (e.g., &quot;English | Spanish&quot;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; Rule.required(),    },    {      name: &amp;#x27;rightText&amp;#x27;,      type: &amp;#x27;text&amp;#x27;,      rows: 10,      validation: (Rule) =&gt; Rule.required(),    },    {      name: &amp;#x27;label&amp;#x27;,      type: &amp;#x27;string&amp;#x27;,      description: &amp;#x27;Optional label (e.g., &amp;#x22;English | Spanish&amp;#x22;)&amp;#x27;,    },  ],});&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;the-renderer-component&quot;&gt;The Renderer Component&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Astro component uses CSS Grid for the layout:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;src/components/PortableTextContainerColumns.astro&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;container-columns-wrapper&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;label &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;container-columns-label&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;container-columns-container&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;column column-left&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(leftText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;column column-right&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(rightText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.container-columns-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;grid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1.5&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--gray-light&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rgb&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--gray&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.container-columns-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  {label &amp;#x26;&amp;#x26; &lt;div&gt;{label}&lt;/div&gt;}  &lt;div&gt;    &lt;div&gt;      &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;    &lt;/div&gt;    &lt;div&gt;      &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;    &lt;/div&gt;  &lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Key design decisions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gray background and border for visual emphasis&lt;/li&gt;
&lt;li&gt;Divider line between columns on desktop&lt;/li&gt;
&lt;li&gt;Line breaks preserved with &lt;code dir=&quot;auto&quot;&gt;white-space: pre-wrap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mobile-first responsive design&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-evolution-newspaper-columns&quot;&gt;The Evolution: Newspaper Columns&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After testing the boxed layout, I realized there was another use case — flowing text that did not need the heavy visual container. Think newspaper-style columns or subtle side-by-side comparisons that integrate more naturally with body text.&lt;/p&gt;
&lt;p&gt;The “newspaper columns” variant would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove the box styling entirely&lt;/li&gt;
&lt;li&gt;Keep just the column structure&lt;/li&gt;
&lt;li&gt;Be more subtle and versatile&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;naming-matters&quot;&gt;Naming Matters&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I initially called it &lt;code dir=&quot;auto&quot;&gt;twoColumnText&lt;/code&gt;, but “newspaper columns” felt more evocative and descriptive. The name instantly communicates the visual style.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-second-component&quot;&gt;The Second Component&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The schema is nearly identical, but the renderer is much lighter:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;src/components/PortableTextNewspaperColumns.astro&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;newspaper-columns-container&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;newspaper-column&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(leftText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;newspaper-column&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(rightText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.newspaper-columns-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;grid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* Wider gap since there&apos;s no divider */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;/* No background, no borders, no container styling */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  &lt;div&gt;    &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;  &lt;/div&gt;  &lt;div&gt;    &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;  &lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;adding-text-alignment&quot;&gt;Adding Text Alignment&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With the cleaner newspaper columns, text alignment became more important. The question was: &lt;strong&gt;one alignment for both columns, or independent alignment per column?&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-decision-independent-alignment&quot;&gt;The Decision: Independent Alignment&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Independent alignment per column unlocks more use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RTL languages:&lt;/strong&gt; Arabic or Hebrew in the right column, right-aligned&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creative layouts:&lt;/strong&gt; Centered poetry next to left-aligned prose&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual balance:&lt;/strong&gt; Match the text style to the content type&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Implementation was straightforward:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Added to newspaperColumns schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;leftAlign&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Left Column Alignment&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;list: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initialValue: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rightAlign&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Right Column Alignment&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;list: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initialValue: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Applied in the template with inline styles:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;text-align: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;leftAlign&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(leftText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;text-align: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;rightAlign&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(rightText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;&lt;div&gt;  &lt;p set:html=&quot;&quot;&gt;&lt;/p&gt;&lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;technical-implementation-details&quot;&gt;Technical Implementation Details&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;why-custom-components&quot;&gt;Why Custom Components?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Sanity has a plugin ecosystem, but custom components give you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full control&lt;/strong&gt; over styling and behavior&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No dependencies&lt;/strong&gt; on third-party packages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perfect integration&lt;/strong&gt; with your design system&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exactly the features you need&lt;/strong&gt;, nothing more&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;the-three-file-pattern&quot;&gt;The Three-File Pattern&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every custom Sanity component in our setup requires three files:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schema Definition&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;src/sanity/schemaTypes/objects/*.ts&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Defines the data structure&lt;/li&gt;
&lt;li&gt;Field types, validation, preview&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Renderer Component&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;src/components/PortableText*.astro&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How it displays on the frontend&lt;/li&gt;
&lt;li&gt;Styling, layout, interactivity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Registry Updates&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;src/sanity/schemaTypes/index.ts&lt;/code&gt; — Import and register schema&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;src/sanity/schemaTypes/post.ts&lt;/code&gt; — Add to post body field&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;src/components/PortableText.astro&lt;/code&gt; — Register renderer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;critical-implementation-notes&quot;&gt;Critical Implementation Notes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Must update &lt;code dir=&quot;auto&quot;&gt;post.ts&lt;/code&gt; body field:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;defineField&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;of: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;containerColumns&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },  &lt;/span&gt;&lt;span&gt;// ADD HERE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;newspaperColumns&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },  &lt;/span&gt;&lt;span&gt;// AND HERE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is easy to miss. The post schema defines its own body field inline, so you must explicitly add new component types there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sanity Studio deployment is separate:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Frontend deploys automatically via GitHub Actions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Studio must be deployed manually&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sanity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sanity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deploy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dist&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The Studio is a separate React app hosted on Sanity’s infrastructure. Schema changes will not appear in production Studio until you deploy it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;line-break-preservation&quot;&gt;Line Break Preservation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Text fields in Sanity preserve newlines, but they need special handling in HTML:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;&amp;#x3C;br&gt;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; {  return text.replace(/\n/g, &amp;#x27;&lt;br&gt;&amp;#x27;);};&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then render with &lt;code dir=&quot;auto&quot;&gt;set:html&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;formatText&lt;/span&gt;&lt;span&gt;(leftText)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;&lt;/button&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Combined with &lt;code dir=&quot;auto&quot;&gt;white-space: pre-wrap&lt;/code&gt; in CSS, this preserves the author’s intended formatting.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;complexity-vs-features&quot;&gt;Complexity vs. Features&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The initial thought was to support full Portable Text (rich text with nested blocks) in each column. That would have been significantly more complex — recursive rendering, nested component handling, edge cases galore.&lt;/p&gt;
&lt;p&gt;Text-only was the right call. It covers most use cases and took a fraction of the development time.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;naming-is-design&quot;&gt;Naming Is Design&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;“Two Column Layout” → Generic, unclear
“Container Columns” → Describes the visual style
“Newspaper Columns” → Instantly conveys the aesthetic&lt;/p&gt;
&lt;p&gt;Good names make components discoverable and understandable in the Studio UI.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;start-simple-add-features&quot;&gt;Start Simple, Add Features&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Version 1: Just two text fields, 50/50 split
Version 2: Add optional label
Version 3: Create second variant (newspaper)
Version 4: Add alignment options to newspaper columns&lt;/p&gt;
&lt;p&gt;Each step was tested and working before moving to the next. Incremental development prevents half-finished features.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;use-cases&quot;&gt;Use Cases&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;container-columns-boxed&quot;&gt;Container Columns (Boxed)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Translation comparisons that need emphasis&lt;/li&gt;
&lt;li&gt;Code examples side-by-side&lt;/li&gt;
&lt;li&gt;Before/after text&lt;/li&gt;
&lt;li&gt;Formal comparisons (technical writing)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;newspaper-columns-plain&quot;&gt;Newspaper Columns (Plain)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Poetry in multiple languages&lt;/li&gt;
&lt;li&gt;Flowing parallel narratives&lt;/li&gt;
&lt;li&gt;Subtle text comparisons&lt;/li&gt;
&lt;li&gt;Academic citations with translations&lt;/li&gt;
&lt;li&gt;RTL language support&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;performance-considerations&quot;&gt;Performance Considerations&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;These components are statically rendered at build time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No client-side JavaScript&lt;/li&gt;
&lt;li&gt;Pure HTML and CSS&lt;/li&gt;
&lt;li&gt;Fast page loads&lt;/li&gt;
&lt;li&gt;SEO-friendly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The only runtime work is CSS Grid layout, which is native and performant.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;future-enhancements&quot;&gt;Future Enhancements&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Potential additions (not implemented yet):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Font size controls&lt;/strong&gt; per column&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Column width ratio&lt;/strong&gt; (60/40, 70/30)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vertical divider toggle&lt;/strong&gt; for newspaper columns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background color picker&lt;/strong&gt; for container columns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rich text support&lt;/strong&gt; (would be complex but powerful)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This project taught me something important about working with AI as a creative partner.&lt;/p&gt;
&lt;p&gt;When I asked for “a two-column component,” I had a specific mental image. The AI gave me something different, and better, in its own way. Container Columns was not what I expected, but it solved use cases I had not considered. The boxed style gives emphasis and visual hierarchy that my simpler vision would have lacked.&lt;/p&gt;
&lt;p&gt;Instead of discarding the AI’s interpretation and insisting on my original idea, we built both. &lt;strong&gt;This is the power of human–AI collaboration&lt;/strong&gt; — it is not about the AI executing your exact specifications, it is about the AI providing perspectives and possibilities you would not have considered.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ai-as-creative-augmentation&quot;&gt;AI as Creative Augmentation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Working with an AI pair programmer does not mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The AI replaces your creativity&lt;/li&gt;
&lt;li&gt;You fight to get exactly what you envisioned&lt;/li&gt;
&lt;li&gt;You choose between your idea or the AI’s suggestion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The AI expands your catalogue of solutions&lt;/li&gt;
&lt;li&gt;You get multiple perspectives on the same problem&lt;/li&gt;
&lt;li&gt;You build more instead of choosing one&lt;/li&gt;
&lt;li&gt;Creative friction becomes creative abundance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I started this session wanting to build one component. I ended with two, each serving different purposes, each valuable in its own context. That is not compromise — that is multiplication.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-numbers&quot;&gt;The Numbers&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What I asked for:&lt;/strong&gt; 1 component&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What we built:&lt;/strong&gt; 2 components&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total development time:&lt;/strong&gt; ~2 hours&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lines of code:&lt;/strong&gt; ~300&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependencies added:&lt;/strong&gt; 0&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My perspectives:&lt;/strong&gt; 1&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined perspectives:&lt;/strong&gt; 2&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problems solved:&lt;/strong&gt; Many&lt;/p&gt;
&lt;p&gt;Building custom Sanity components is not as daunting as it seems. With a clear understanding of the three-file pattern, careful incremental development, and a collaborative mindset with your AI pair programmer, you can create tailored solutions that not only fit your needs but expand beyond what you originally imagined.&lt;/p&gt;
&lt;p&gt;Sometimes the best result is not getting exactly what you asked for — it is getting what you asked for plus something you did not know you needed.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/adding-custom-components-guide.md&quot;&gt;How to Add Custom Components Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/component-reference-library.md&quot;&gt;Component Reference Library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>sanity</category><category>astro</category><category>components</category><category>portable-text</category><category>css</category><category>ai</category></item><item><title>Building a Chat Conversation Component for Sanity Studio (iMessage-Style Bubbles)</title><link>https://devportals.tech/blog/sanity-chat-conversation-component/</link><guid isPermaLink="true">https://devportals.tech/blog/sanity-chat-conversation-component/</guid><pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever wanted to embed a conversation or chat screenshot in your blog post, but pasting an image felt too static? What if you could recreate chat bubbles—complete with sender names, timestamps, and multiple visual styles—as structured content?&lt;/p&gt;
&lt;p&gt;That’s what I built for my &lt;a href=&quot;https://peanutbutterandjelly.ai&quot;&gt;Astro + Sanity blog&lt;/a&gt;. And thanks to &lt;a href=&quot;https://devportals.tech/blog/sanity-documentation-driven-components/&quot;&gt;the documentation system I built&lt;/a&gt;, it only took 90 minutes from concept to production.&lt;/p&gt;
&lt;p&gt;Here’s how it works, what design decisions went into it, and the complete implementation guide.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-problem-conversations-are-hard-to-present&quot;&gt;The Problem: Conversations Are Hard to Present&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I wanted to share some ChatGPT conversations and WhatsApp exchanges in blog posts. My options were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Screenshots&lt;/strong&gt; - Static, not accessible, can’t copy text, breaks on different screen sizes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blockquotes&lt;/strong&gt; - Doesn’t convey the back-and-forth nature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom HTML&lt;/strong&gt; - Works once, painful to reuse&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What I really wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Message bubbles that look like actual chat apps&lt;/li&gt;
&lt;li&gt;Different colors for different speakers&lt;/li&gt;
&lt;li&gt;Optional sender names and timestamps&lt;/li&gt;
&lt;li&gt;Multiple visual styles (iMessage, WhatsApp, minimal)&lt;/li&gt;
&lt;li&gt;Mobile-friendly and accessible&lt;/li&gt;
&lt;li&gt;Easy to create in Sanity Studio (no code required per conversation)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-solution-a-structured-chat-component&quot;&gt;The Solution: A Structured Chat Component&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s what the final component looks like in action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌─────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  Alice                                  │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  ┌──────────────────────────────┐       │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  │ Hey, did you finish the docs?│       │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  │ 10:30 AM                     │       │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  └──────────────────────────────┘       │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                                         │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                    ┌──────────────────┐ │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                    │ Just pushed them!│ │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                    │ 10:32 AM         │ │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                    └──────────────────┘ │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│                                      Bob│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└─────────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Three visual styles:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modern&lt;/strong&gt; (iMessage-style): Blue bubbles on the right, gray on the left&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Classic&lt;/strong&gt; (WhatsApp-style): Green bubbles with borders&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal&lt;/strong&gt;: No bubbles, just border lines and alignment&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-schema-structured-conversation-data&quot;&gt;The Schema: Structured Conversation Data&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;First, I defined the Sanity schema in &lt;code dir=&quot;auto&quot;&gt;src/sanity/schemaTypes/objects/chatConversation.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { defineType } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;sanity&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defineType&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;chatConversation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Chat Conversation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fields: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Messages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;of: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fields: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Message Text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Rule&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; Rule&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Sender&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Name of the sender (e.g., &quot;User&quot;, &quot;Assistant&quot;, &quot;Alice&quot;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;side&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Side&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;list: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Left (Gray)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Right (Blue)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initialValue: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Timestamp&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Optional timestamp (e.g., &quot;10:30 AM&quot;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;validation&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Rule&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; Rule&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Chat Style&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;list: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Modern (Like iMessage)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modern&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Classic (Like WhatsApp)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;classic&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Minimal&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;minimal&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;initialValue: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modern&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; Rule.required(),            },            {              name: &amp;#x27;sender&amp;#x27;,              title: &amp;#x27;Sender&amp;#x27;,              type: &amp;#x27;string&amp;#x27;,              description: &amp;#x27;Name of the sender (e.g., &amp;#x22;User&amp;#x22;, &amp;#x22;Assistant&amp;#x22;, &amp;#x22;Alice&amp;#x22;)&amp;#x27;,            },            {              name: &amp;#x27;side&amp;#x27;,              title: &amp;#x27;Side&amp;#x27;,              type: &amp;#x27;string&amp;#x27;,              options: {                list: [                  { title: &amp;#x27;Left (Gray)&amp;#x27;, value: &amp;#x27;left&amp;#x27; },                  { title: &amp;#x27;Right (Blue)&amp;#x27;, value: &amp;#x27;right&amp;#x27; },                ],              },              initialValue: &amp;#x27;right&amp;#x27;,            },            {              name: &amp;#x27;timestamp&amp;#x27;,              title: &amp;#x27;Timestamp&amp;#x27;,              type: &amp;#x27;string&amp;#x27;,              description: &amp;#x27;Optional timestamp (e.g., &amp;#x22;10:30 AM&amp;#x22;)&amp;#x27;,            },          ],        },      ],      validation: (Rule) =&gt; Rule.min(1).required(),    },    {      name: &amp;#x27;style&amp;#x27;,      title: &amp;#x27;Chat Style&amp;#x27;,      type: &amp;#x27;string&amp;#x27;,      options: {        list: [          { title: &amp;#x27;Modern (Like iMessage)&amp;#x27;, value: &amp;#x27;modern&amp;#x27; },          { title: &amp;#x27;Classic (Like WhatsApp)&amp;#x27;, value: &amp;#x27;classic&amp;#x27; },          { title: &amp;#x27;Minimal&amp;#x27;, value: &amp;#x27;minimal&amp;#x27; },        ],      },      initialValue: &amp;#x27;modern&amp;#x27;,    },  ],});&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;design-decision-why-three-styles&quot;&gt;Design Decision: Why Three Styles?&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Modern (iMessage):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Familiar to iOS users&lt;/li&gt;
&lt;li&gt;High contrast with blue/gray&lt;/li&gt;
&lt;li&gt;Clean, rounded bubbles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Classic (WhatsApp):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Green bubbles feel friendly&lt;/li&gt;
&lt;li&gt;Border/shadow gives depth&lt;/li&gt;
&lt;li&gt;Recognizable across platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Minimal:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perfect for technical content&lt;/li&gt;
&lt;li&gt;No visual clutter&lt;/li&gt;
&lt;li&gt;Just colored border lines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives content creators flexibility depending on the tone of their post.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-renderer-astro-component-with-dynamic-styling&quot;&gt;The Renderer: Astro Component with Dynamic Styling&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Next, the Astro component (&lt;code dir=&quot;auto&quot;&gt;src/components/PortableTextChatConversation.astro&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; } = &lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modern&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; } = &lt;/span&gt;&lt;span&gt;node;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;chat-conversation chat-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;side&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt; } = &lt;/span&gt;&lt;span&gt;message;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;chat-message-wrapper &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;side&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sender &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; side &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;left&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chat-sender-name&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;chat-bubble &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;side&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chat-text&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timestamp &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chat-timestamp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sender &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; side &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;right&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chat-sender-name right&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  {messages.map((message: any) =&gt; {    const { text, sender, side = &amp;#x27;right&amp;#x27;, timestamp } = message;    return (      &lt;div&gt;        {sender &amp;#x26;&amp;#x26; side === &amp;#x27;left&amp;#x27; &amp;#x26;&amp;#x26; (          &lt;div&gt;{sender}&lt;/div&gt;        )}        &lt;div&gt;          &lt;div&gt;{text}&lt;/div&gt;          {timestamp &amp;#x26;&amp;#x26; (            &lt;div&gt;{timestamp}&lt;/div&gt;          )}        &lt;/div&gt;        {sender &amp;#x26;&amp;#x26; side === &amp;#x27;right&amp;#x27; &amp;#x26;&amp;#x26; (          &lt;div&gt;{sender}&lt;/div&gt;        )}      &lt;/div&gt;    );  })}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;key-css-patterns&quot;&gt;Key CSS Patterns&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Modern Style (iMessage):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.chat-modern&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.chat-bubble.left&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;e5e7eb&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Gray */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;111827&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-bottom-left-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Tail effect */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.chat-modern&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.chat-bubble.right&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;3b82f6&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Blue */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-bottom-right-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Classic Style (WhatsApp):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.chat-classic&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.chat-bubble.right&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;dcf8c6&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Light green */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;111827&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;c3e6a8&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Minimal Style:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.chat-minimal&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.chat-bubble.left&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;9ca3af&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-radius&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;the-mobile-experience&quot;&gt;The Mobile Experience&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Chat bubbles need to work on phones. Here’s how:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.chat-bubble&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;85&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* More screen space on mobile */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;padding&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;0.65&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;0.85&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;0.9&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On desktop, bubbles max out at 75% width. On mobile, they expand to 85% to use the limited space better.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;dark-mode-support&quot;&gt;Dark Mode Support&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Because people read blogs at night:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;prefers-color-scheme: dark&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.chat-conversation&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;1f2937&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Dark container */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.chat-modern&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.chat-bubble.left&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;374151&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;/* Darker gray */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;f3f4f6&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The blue bubbles stay blue in dark mode—they already have good contrast.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;accessibility-considerations&quot;&gt;Accessibility Considerations&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sender names above/below bubbles&lt;/strong&gt; - Screen readers announce who’s speaking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic HTML&lt;/strong&gt; - Uses &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;div&gt;&lt;/code&gt; with proper roles, not &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;ul&gt;&lt;/code&gt; (conversations aren’t lists)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Color isn’t the only indicator&lt;/strong&gt; - Side alignment also shows who’s speaking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text is selectable&lt;/strong&gt; - Users can copy conversation text&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;using-it-in-sanity-studio&quot;&gt;Using It in Sanity Studio&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The editor experience is dead simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click “Chat Conversation” in the body editor&lt;/li&gt;
&lt;li&gt;Add messages one by one:
&lt;ul&gt;
&lt;li&gt;Type the message text&lt;/li&gt;
&lt;li&gt;Choose “Left” or “Right”&lt;/li&gt;
&lt;li&gt;Optionally add sender name&lt;/li&gt;
&lt;li&gt;Optionally add timestamp&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Select the chat style&lt;/li&gt;
&lt;li&gt;Publish!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Preview in Studio:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Chat Conversation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;2 messages&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each message shows a preview:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;► User: Hey, did you finish the docs?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;real-world-example&quot;&gt;Real-World Example&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s how I used it to share a ChatGPT conversation:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message 1 (Left):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sender: “User”&lt;/li&gt;
&lt;li&gt;Text: “How do I add custom components to Sanity?”&lt;/li&gt;
&lt;li&gt;Timestamp: “2:30 PM”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Message 2 (Right):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sender: “ChatGPT”&lt;/li&gt;
&lt;li&gt;Text: “You’ll need to create a schema file, register it, and build a renderer component…”&lt;/li&gt;
&lt;li&gt;Timestamp: “2:31 PM”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Message 3 (Left):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sender: “User”&lt;/li&gt;
&lt;li&gt;Text: “Wait, do I need to deploy twice?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so on. The result looks like an actual chat interface, but it’s structured data that’s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Accessible&lt;/li&gt;
&lt;li&gt;Responsive&lt;/li&gt;
&lt;li&gt;Copy-pasteable&lt;/li&gt;
&lt;li&gt;Version-controlled&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;performance-notes&quot;&gt;Performance Notes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Bundle Size:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No external dependencies&lt;/li&gt;
&lt;li&gt;Pure CSS styling&lt;/li&gt;
&lt;li&gt;~3KB compressed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Rendering:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Static HTML (built at compile time with Astro)&lt;/li&gt;
&lt;li&gt;No JavaScript needed for display&lt;/li&gt;
&lt;li&gt;No API calls&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;what-id-do-differently&quot;&gt;What I’d Do Differently&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;If I built this again, I’d add:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Avatar images&lt;/strong&gt; - Small profile pics next to sender names&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typing indicator animation&lt;/strong&gt; - For dramatic effect in tutorials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read receipts&lt;/strong&gt; - Blue checkmarks like WhatsApp&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reactions&lt;/strong&gt; - Emoji reactions to messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group chat support&lt;/strong&gt; - More than two participants&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But for v1, the current feature set covers 90% of my use cases.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-90-minute-timeline&quot;&gt;The 90-Minute Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://devportals.tech/blog/sanity-documentation-driven-components/&quot;&gt;my documentation system&lt;/a&gt;, here’s how the build broke down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Schema design:&lt;/strong&gt; 15 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic renderer:&lt;/strong&gt; 30 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Three style variants:&lt;/strong&gt; 25 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile/dark mode:&lt;/strong&gt; 15 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing &amp;#x26; polish:&lt;/strong&gt; 5 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Total: 90 minutes from idea to production.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The guides eliminated all the “wait, how do I…” moments. I knew exactly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where to put the schema&lt;/li&gt;
&lt;li&gt;How to structure the component&lt;/li&gt;
&lt;li&gt;What the deployment steps were&lt;/li&gt;
&lt;li&gt;Common pitfalls to avoid&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Want to add this to your own Sanity + Astro blog?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the schema from this post&lt;/li&gt;
&lt;li&gt;Copy the Astro component&lt;/li&gt;
&lt;li&gt;Register both following the &lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/adding-custom-components-guide.md&quot;&gt;Component Adding Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Deploy and enjoy!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or customize it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the bubble colors&lt;/li&gt;
&lt;li&gt;Add your own style variant&lt;/li&gt;
&lt;li&gt;Modify the border radius&lt;/li&gt;
&lt;li&gt;Add animations&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;what-would-you-build&quot;&gt;What Would You Build?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This chat component scratched my itch for displaying conversations. But the pattern works for tons of other use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email thread viewer&lt;/strong&gt; (Gmail-style)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comment sections&lt;/strong&gt; (Reddit-style)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code review threads&lt;/strong&gt; (GitHub-style)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Twitter/X thread embeds&lt;/strong&gt; (X-style)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What conversations do you want to display in your blog?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Full Code:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/src/sanity/schemaTypes/objects/chatConversation.ts&quot;&gt;Chat Schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/src/components/PortableTextChatConversation.astro&quot;&gt;Chat Renderer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/adding-custom-components-guide.md&quot;&gt;Component Adding Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;See it live:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://peanutbutterandjelly.ai&quot;&gt;peanutbutterandjelly.ai&lt;/a&gt; (check the latest posts)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Building custom Sanity components? Running into deployment gotchas? Let’s chat about it.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>sanity-cms</category><category>astro</category><category>ui-components</category><category>react</category><category>web-development</category></item><item><title>Self-Documenting Your Dev Workflow: How Writing Guides Made Me 3x Faster</title><link>https://devportals.tech/blog/sanity-documentation-driven-components/</link><guid isPermaLink="true">https://devportals.tech/blog/sanity-documentation-driven-components/</guid><pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There’s a moment in every developer’s journey when you finally realize: “I should have documented this the first time.”&lt;/p&gt;
&lt;p&gt;Mine came after spending two hours debugging why my Sanity Studio components weren’t showing up in production—even though they worked perfectly on localhost. The problem? I had forgotten that my blog actually consists of &lt;strong&gt;two separate applications&lt;/strong&gt; that need independent deployments.&lt;/p&gt;
&lt;p&gt;This is the story of how writing documentation for “future me” accidentally made my development process 3x faster.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-two-site-confusion&quot;&gt;The Two-Site Confusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;My blog, &lt;a href=&quot;https://peanutbutterandjelly.ai&quot;&gt;peanutbutterandjelly.ai&lt;/a&gt;, is built with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; for the static frontend (deployed to GitHub Pages)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sanity Studio&lt;/strong&gt; for content management (deployed to Sanity’s cloud)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I added my first custom component (a table), I went through this painful cycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write the schema&lt;/li&gt;
&lt;li&gt;Create the Astro renderer&lt;/li&gt;
&lt;li&gt;Test locally (works!)&lt;/li&gt;
&lt;li&gt;Commit and push to GitHub&lt;/li&gt;
&lt;li&gt;Component doesn’t appear in production Studio&lt;/li&gt;
&lt;li&gt;Spend 30 minutes confused&lt;/li&gt;
&lt;li&gt;Remember: “Oh right, I need to deploy the Studio separately!”&lt;/li&gt;
&lt;li&gt;Run &lt;code dir=&quot;auto&quot;&gt;npx sanity deploy dist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Finally works&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A week later, I added image sizing controls. Same exact confusion. Same 30-minute debugging session. Same forehead slap when I remembered the separate deployment.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-breaking-point&quot;&gt;The Breaking Point&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After the third time making this mistake, I did what every developer should do more often: &lt;strong&gt;I stopped and wrote it down.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I created two documentation files:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-how-to-add-custom-components-guide&quot;&gt;1. &lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/adding-custom-components-guide.md&quot;&gt;How to Add Custom Components Guide&lt;/a&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A step-by-step checklist for adding any custom component, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The critical &lt;code dir=&quot;auto&quot;&gt;post.ts&lt;/code&gt; file that everyone forgets&lt;/li&gt;
&lt;li&gt;The difference between &lt;code dir=&quot;auto&quot;&gt;npx sanity dev&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;npx sanity start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Why you have to deploy twice (Astro + Studio)&lt;/li&gt;
&lt;li&gt;Common pitfalls and debugging tips&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;2-component-reference-library&quot;&gt;2. &lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/component-reference-library.md&quot;&gt;Component Reference Library&lt;/a&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A catalog of components we could build, complete with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Schema examples&lt;/li&gt;
&lt;li&gt;Use cases and complexity ratings&lt;/li&gt;
&lt;li&gt;Implementation priorities&lt;/li&gt;
&lt;li&gt;Copy-pasteable TypeScript code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The guides took me about 2 hours to write. I figured they’d save me maybe 30 minutes next time.&lt;/p&gt;
&lt;p&gt;I was wildly wrong about the ROI.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-3x-speed-multiplier&quot;&gt;The 3x Speed Multiplier&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Fast forward one week. I decided to add three new components in one sitting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Divider&lt;/strong&gt; (visual separators between sections)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accordion&lt;/strong&gt; (collapsible FAQ sections)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Block&lt;/strong&gt; (syntax-highlighted code with copy buttons)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Armed with my documentation, here’s what happened:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Without guides (previous attempts):&lt;/strong&gt; ~3 hours per component&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Schema: 20 min&lt;/li&gt;
&lt;li&gt;Renderer: 40 min&lt;/li&gt;
&lt;li&gt;Testing: 15 min&lt;/li&gt;
&lt;li&gt;Debugging deployment issues: 90 min&lt;/li&gt;
&lt;li&gt;Actually deploying correctly: 15 min&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;With guides (this time):&lt;/strong&gt; ~20 minutes per component&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Schema: 5 min (reference library had examples)&lt;/li&gt;
&lt;li&gt;Renderer: 10 min (clearer patterns)&lt;/li&gt;
&lt;li&gt;Testing: 3 min (knew what to look for)&lt;/li&gt;
&lt;li&gt;Deployment: 2 min (checklist = no confusion)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Total time: 60 minutes for all three components. Time saved: ~8 hours.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But the speed wasn’t even the best part.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-unexpected-benefits&quot;&gt;The Unexpected Benefits&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-fearless-iteration&quot;&gt;1. Fearless Iteration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;With a clear checklist, I wasn’t afraid to try complex components. The accordion has nested rich text content and JavaScript interactions—normally I’d save that for “later.” But with my guide, I knew exactly what steps to follow and could focus on the creative parts.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-better-code-quality&quot;&gt;2. Better Code Quality&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The documentation forced me to think about patterns. When I wrote “Step 5: Register the Renderer,” I realized I was registering components in slightly different ways each time. Documenting the process revealed the inconsistency.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-ai-collaboration-gold&quot;&gt;3. AI Collaboration Gold&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here’s something I didn’t expect: &lt;strong&gt;the documentation became incredible context for working with AI coding assistants.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I wanted to add the chat conversation component later, I just shared my guides with Claude. Instead of explaining the entire architecture, deployment process, and gotchas, I could say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Based on these two guides, let’s add a chat conversation component with message bubbles.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The AI immediately understood:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It needed both a schema AND a renderer&lt;/li&gt;
&lt;li&gt;The schema goes in &lt;code dir=&quot;auto&quot;&gt;src/sanity/schemaTypes/objects/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;It must be added to &lt;code dir=&quot;auto&quot;&gt;post.ts&lt;/code&gt; (the critical file!)&lt;/li&gt;
&lt;li&gt;Testing requires restarting Sanity Studio&lt;/li&gt;
&lt;li&gt;Production requires a separate Studio deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We shipped the chat component—with three visual styles, sender names, timestamps, and full mobile responsiveness—in under 90 minutes.&lt;/strong&gt; That would have taken me half a day without the guides.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-documentation-architecture&quot;&gt;The Documentation Architecture&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s what made these guides actually useful (unlike most documentation):&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;checklist-format&quot;&gt;Checklist Format&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Create schema file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Register in index.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Add to post.ts body field (CRITICAL!)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Create renderer component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Register in PortableText.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Restart Sanity Studio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Test in Studio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Test in Astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Commit changes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;□ Deploy Studio separately&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;No walls of text. Just: “Did you do this? Check. Next.”&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-why-explained-once&quot;&gt;The “Why” Explained Once&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The guide has a whole section titled “Why Separate Deployment?” with ASCII diagrams showing the two-site architecture. I can reference it instead of re-learning it every time.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;real-code-examples&quot;&gt;Real Code Examples&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every component type in the reference library has actual, copy-pasteable TypeScript. No “TODO: implement this” placeholders.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;troubleshooting-section&quot;&gt;Troubleshooting Section&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;All my mistakes, documented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Component doesn’t appear in Studio” → Check &lt;code dir=&quot;auto&quot;&gt;post.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;“Works locally but not in production” → Did you deploy the Studio?&lt;/li&gt;
&lt;li&gt;“Schema loads but data doesn’t save” → Check validation rules&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-roi-calculation&quot;&gt;The ROI Calculation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Time invested in documentation:&lt;/strong&gt; 2 hours&lt;br&gt;
&lt;strong&gt;Time saved (so far):&lt;/strong&gt; ~8 hours&lt;br&gt;
&lt;strong&gt;Confidence gained:&lt;/strong&gt; Immeasurable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But here’s the real ROI:&lt;/strong&gt; Before the guides, I would avoid adding components because the process felt painful. Now I actively look for excuses to build new components because I know it’ll take less than an hour, not half a day.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-for-your-own-projects&quot;&gt;Lessons for Your Own Projects&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-document-on-the-second-time-not-the-first&quot;&gt;1. Document on the Second Time, Not the First&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The first time you do something, you’re still learning. The second time, you know what actually matters. That’s when you should document.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-write-for-3am-tired-you&quot;&gt;2. Write for “3am Tired You”&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Don’t write documentation for the smartest version of yourself. Write it for when you’re exhausted, distracted, or returning to the project after six months away.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-checklists--prose&quot;&gt;3. Checklists &gt; Prose&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Dense paragraphs get skipped. Checklists get followed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-make-it-grep-able&quot;&gt;4. Make It Grep-able&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Use specific error messages and keywords. When you Google “sanity component not appearing,” your own docs should show up.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-include-the-deployment-architecture&quot;&gt;5. Include the Deployment Architecture&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This was my breakthrough. A simple diagram showing:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Sanity Studio (separate deploy)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Sanity API&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Astro Site (separate deploy)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Once I drew this, everything clicked.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;These guides have become living documents. Every time I add a component, I update the reference library. Every time I hit a gotcha, I add it to the troubleshooting section.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; I’m planning to add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Video embed component (YouTube/Vimeo)&lt;/li&gt;
&lt;li&gt;Callout boxes (warnings, tips, notes)&lt;/li&gt;
&lt;li&gt;Two-column layouts for comparisons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And thanks to my documentation, each one should take about an hour instead of half a day.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’re building with a headless CMS (Sanity, Strapi, Contentful, etc.) or any two-part architecture, I challenge you to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Document your deployment process&lt;/strong&gt; (30 minutes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a component checklist&lt;/strong&gt; (30 minutes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a reference library&lt;/strong&gt; as you add components (10 minutes per component)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then measure your speed on the 4th component vs. the 1st.&lt;/p&gt;
&lt;p&gt;I bet you’ll see at least a 2x improvement.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/adding-custom-components-guide.md&quot;&gt;Full Component Adding Guide&lt;/a&gt; (on GitHub)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/j-romo/pbnj-blog/blob/main/docs/component-reference-library.md&quot;&gt;Component Reference Library&lt;/a&gt; (on GitHub)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peanutbutterandjelly.ai&quot;&gt;peanutbutterandjelly.ai&lt;/a&gt; (the blog this is all for)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Want to see this in action?&lt;/strong&gt; Next post, I’ll walk through building the chat conversation component—including the 3-style bubble system and why dark mode support matters.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you documented your deployment process? What was your “aha” moment that made you finally write it down?&lt;/em&gt;&lt;/p&gt;</content:encoded><category>documentation</category><category>developer-experience</category><category>astro</category><category>sanity-cms</category><category>headless-cms</category><category>workflow</category></item><item><title>Integrating a Next.js Portfolio as a Subdomain: A Technical Deep Dive</title><link>https://devportals.tech/blog/integrating-nextjs-portfolio-subdomain/</link><guid isPermaLink="true">https://devportals.tech/blog/integrating-nextjs-portfolio-subdomain/</guid><pubDate>Sat, 03 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As a documentation engineer and technical writer, I needed a way to showcase both my technical writing work and my development projects.
I had already built a documentation site using Astro and Starlight at &lt;code dir=&quot;auto&quot;&gt;devportals.tech&lt;/code&gt;, and I wanted to add my Next.js portfolio site without disrupting the existing architecture.&lt;/p&gt;
&lt;p&gt;This post walks through the technical decisions, implementation challenges, and solutions I encountered while integrating these two separate applications into a cohesive experience.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-challenge-two-sites-one-domain&quot;&gt;The Challenge: Two Sites, One Domain&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I had two distinct applications:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Main documentation site&lt;/strong&gt;: Built with Astro + Starlight, hosted at &lt;code dir=&quot;auto&quot;&gt;devportals.tech&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portfolio site&lt;/strong&gt;: Built with Next.js, deployed separately on Vercel&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The question was: how do I connect these two sites in a way that feels unified to visitors while maintaining deployment independence?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;architecture-decision-subdomain-vs-subdirectory&quot;&gt;Architecture Decision: Subdomain vs. Subdirectory&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I considered three approaches:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;option-1-separate-domain&quot;&gt;Option 1: Separate Domain&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Complete independence, simplest DNS setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Weakens brand cohesion, splits SEO authority, requires users to remember two URLs&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;option-2-subdirectory-devportalstechportfolio&quot;&gt;Option 2: Subdirectory (&lt;code dir=&quot;auto&quot;&gt;devportals.tech/portfolio&lt;/code&gt;)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Single domain, better for SEO, unified URL structure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Requires complex proxy/rewrite rules, complicates deployments, potential routing conflicts&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;option-3-subdomain-portfoliodevportalstech&quot;&gt;Option 3: Subdomain (&lt;code dir=&quot;auto&quot;&gt;portfolio.devportals.tech&lt;/code&gt;)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Deployment independence, clean separation, simple DNS, maintains brand&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Splits SEO slightly, requires DNS configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;I chose the subdomain approach&lt;/strong&gt; because it provided the best balance of deployment flexibility and brand cohesion. Each application can be deployed independently, use different frameworks, and scale separately while still feeling like part of the same ecosystem.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;infrastructure-setup-dns-and-vercel-configuration&quot;&gt;Infrastructure Setup: DNS and Vercel Configuration&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;setting-up-the-subdomain-in-vercel&quot;&gt;Setting Up the Subdomain in Vercel&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The infrastructure setup was straightforward but required coordination between both Vercel projects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Main site&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;devportals.tech&lt;/code&gt;): Already configured in Vercel project #1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portfolio site&lt;/strong&gt;: Deployed to Vercel project #2&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the portfolio project’s Vercel dashboard:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Domains&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code dir=&quot;auto&quot;&gt;portfolio.devportals.tech&lt;/code&gt; as a custom domain&lt;/li&gt;
&lt;li&gt;Vercel provides DNS records to configure&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;dns-configuration&quot;&gt;DNS Configuration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In my DNS provider (where I registered &lt;code dir=&quot;auto&quot;&gt;devportals.tech&lt;/code&gt;), I added:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Type: CNAME&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Name: portfolio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Value: cname.vercel-dns.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TTL: Auto&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This CNAME record directs &lt;code dir=&quot;auto&quot;&gt;portfolio.devportals.tech&lt;/code&gt; to Vercel’s infrastructure, which then routes to the correct project based on domain configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: DNS propagation can take up to 48 hours, but typically completes within an hour. Use &lt;code dir=&quot;auto&quot;&gt;dig portfolio.devportals.tech&lt;/code&gt; to verify the CNAME record has propagated.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;navigation-integration-the-real-challenge&quot;&gt;Navigation Integration: The Real Challenge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With the infrastructure in place, the next challenge was making the portfolio accessible from the main site’s navigation. Users shouldn’t have to manually type in a subdomain—they should see a clear “Portfolio” link in the navigation bar.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-blog-plugins-automatic-navigation&quot;&gt;The Blog Plugin’s Automatic Navigation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Before diving into the Portfolio implementation, it’s worth noting that I was already using the &lt;code dir=&quot;auto&quot;&gt;starlight-blog&lt;/code&gt; plugin, which adds a “Blog” link to the navigation automatically. This plugin integrates seamlessly with Starlight’s architecture:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;astro.config.mjs&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;plugins: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;starlightBlog&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;metrics: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;readingTime: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The blog plugin has &lt;strong&gt;built-in permission&lt;/strong&gt; to modify Starlight’s navigation—it hooks into Starlight’s plugin API and injects its navigation link without requiring any manual component overrides. This is the “blessed path” for extending Starlight: plugins can register navigation items, add routes, and modify behavior through official APIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But what if you need to add something custom that doesn’t have a plugin?&lt;/strong&gt; That’s where component overrides come in. Unlike the blog plugin, my Portfolio site wasn’t integrated via a Starlight plugin—it’s a completely separate Next.js application. To add a Portfolio link, I needed to go &lt;strong&gt;beyond the plugin system&lt;/strong&gt; and manually override the Header component.&lt;/p&gt;
&lt;p&gt;This distinction highlights an important architectural concept: &lt;strong&gt;official plugins get convenient APIs, custom modifications require deeper integration&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;understanding-starlights-component-architecture&quot;&gt;Understanding Starlight’s Component Architecture&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Starlight (the Astro documentation theme I’m using) has a modular component system. The header navigation is controlled by the &lt;code dir=&quot;auto&quot;&gt;Header.astro&lt;/code&gt; component, which orchestrates several subcomponents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;SiteTitle&lt;/code&gt; - Logo and site name&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Search&lt;/code&gt; - Search functionality&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;SocialIcons&lt;/code&gt; - Social media links&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ThemeSelect&lt;/code&gt; - Light/dark mode toggle&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;LanguageSelect&lt;/code&gt; - Language switcher&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To add a custom “Portfolio” link, I needed to override the Header component.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;research-phase-reading-the-documentation&quot;&gt;Research Phase: Reading the Documentation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;First, I consulted the &lt;a href=&quot;https://starlight.astro.build/guides/overriding-components/&quot;&gt;Starlight overrides documentation&lt;/a&gt;. Key findings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Component overrides&lt;/strong&gt; are configured in &lt;code dir=&quot;auto&quot;&gt;astro.config.mjs&lt;/code&gt; via the &lt;code dir=&quot;auto&quot;&gt;components&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;You can &lt;strong&gt;reuse built-in components&lt;/strong&gt; while adding custom elements&lt;/li&gt;
&lt;li&gt;The Header component doesn’t expose &lt;strong&gt;slots&lt;/strong&gt; for injection—you must replace it entirely&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I initially tried using a slot-based approach:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;!-- This didn&apos;t work - Header doesn&apos;t support slots --&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;slot&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;before-social&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://portfolio.devportals.tech&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;Portfolio&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After testing without success, I checked the &lt;a href=&quot;https://github.com/withastro/starlight/blob/main/packages/starlight/components/Header.astro&quot;&gt;source code&lt;/a&gt; and confirmed: &lt;strong&gt;the Header component must be completely replaced&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;implementation-creating-a-custom-header-component&quot;&gt;Implementation: Creating a Custom Header Component&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I created &lt;code dir=&quot;auto&quot;&gt;src/components/Header.astro&lt;/code&gt; with the full Header implementation:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/user-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; LanguageSelect &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/components/LanguageSelect&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Search &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/components/Search&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; SiteTitle &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/components/SiteTitle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; SocialIcons &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/components/SocialIcons&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ThemeSelect &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;virtual:starlight/components/ThemeSelect&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;shouldRenderSearch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pagefind&lt;/span&gt;&lt;span&gt; || &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;components&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Search&lt;/span&gt;&lt;span&gt; !== &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@astrojs/starlight/components/Search.astro&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;title-wrapper sl-flex&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SiteTitle&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sl-flex print:hidden&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;shouldRenderSearch &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Search&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sl-hidden md:sl-flex print:hidden right-group&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sl-flex social-icons&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SocialIcons&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;divider&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://portfolio.devportals.tech&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;_blank&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;noopener noreferrer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;portfolio-link&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Portfolio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ThemeSelect&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;LanguageSelect&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  &lt;div&gt;      &lt;/div&gt;  &lt;div&gt;    {shouldRenderSearch &amp;#x26;&amp;#x26; }  &lt;/div&gt;  &lt;div&gt;    &lt;div&gt;          &lt;/div&gt;    &lt;span&gt;&lt;/span&gt;    &lt;a href=&quot;&quot;&gt;      Portfolio    &lt;/a&gt;          &lt;/div&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Key implementation details:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Import all necessary components&lt;/strong&gt; from Starlight’s virtual modules&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preserve the search logic&lt;/strong&gt; - check if Pagefind is enabled&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the Portfolio link&lt;/strong&gt; in the right-group section&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Include a visual divider&lt;/strong&gt; to separate sections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintain responsive behavior&lt;/strong&gt; - hide on mobile with &lt;code dir=&quot;auto&quot;&gt;sl-hidden md:sl-flex&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;styling-the-portfolio-link&quot;&gt;Styling the Portfolio Link&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The styling needed to match Starlight’s design system:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;@layer&lt;/span&gt;&lt;span&gt; starlight.core {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.portfolio-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-color-text-accent&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;text-decoration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;font-weight&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-text-base&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.portfolio-link:hover&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-color-text-accent&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;text-decoration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;underline&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.divider&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;border-inline-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-color-gray-5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;.social-icons&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;align-items&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;/* ... rest of original Header styles ... */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  @layer starlight.core {    .portfolio-link {      color: var(--sl-color-text-accent);      text-decoration: none;      font-weight: 600;      font-size: var(--sl-text-base);    }    .portfolio-link:hover {      color: var(--sl-color-text-accent);      text-decoration: underline;    }    .divider {      height: 2rem;      border-inline-end: 1px solid var(--sl-color-gray-5);    }    .social-icons {      gap: 1rem;      align-items: center;    }    /* ... rest of original Header styles ... */  }&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;CSS considerations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use Starlight’s CSS custom properties (&lt;code dir=&quot;auto&quot;&gt;--sl-color-text-accent&lt;/code&gt;) for theme consistency&lt;/li&gt;
&lt;li&gt;Wrap styles in &lt;code dir=&quot;auto&quot;&gt;@layer starlight.core&lt;/code&gt; to maintain specificity&lt;/li&gt;
&lt;li&gt;Match the Blog link’s styling for visual consistency&lt;/li&gt;
&lt;li&gt;Use &lt;code dir=&quot;auto&quot;&gt;var(--sl-text-base)&lt;/code&gt; for font size parity&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;registering-the-component-override&quot;&gt;Registering the Component Override&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In &lt;code dir=&quot;auto&quot;&gt;astro.config.mjs&lt;/code&gt;, I registered the custom component:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defineConfig&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;integrations: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;starlight&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;DevPortals.Tech&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;components: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Header: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./src/components/Header.astro&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// ... rest of config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;troubleshooting-journey&quot;&gt;Troubleshooting Journey&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;issue-1-slot-approach-didnt-work&quot;&gt;Issue #1: Slot Approach Didn’t Work&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Initial attempt to use slots resulted in no visible changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Investigation&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checked browser dev tools - component wasn’t rendering&lt;/li&gt;
&lt;li&gt;Reviewed Starlight docs - no mention of Header slots&lt;/li&gt;
&lt;li&gt;Examined source code - confirmed Header doesn’t use slots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Complete component replacement instead of slot injection.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;issue-2-double-dividers&quot;&gt;Issue #2: Double Dividers&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Two vertical dividers appeared between social icons and Portfolio link.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: The original &lt;code dir=&quot;auto&quot;&gt;.social-icons::after&lt;/code&gt; pseudo-element was still creating a divider, and I added another &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;span class=&quot;divider&quot;&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Removed the &lt;code dir=&quot;auto&quot;&gt;::after&lt;/code&gt; pseudo-element CSS rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* Removed this */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.social-icons::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-inline-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-color-gray-5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;/* Kept the standalone divider element */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.divider&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;border-inline-end&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solid&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--sl-color-gray-5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;issue-3-cramped-layout&quot;&gt;Issue #3: Cramped Layout&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Portfolio link was initially placed inside the &lt;code dir=&quot;auto&quot;&gt;social-icons&lt;/code&gt; div, causing cramped spacing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Moved the link outside the social icons container into the &lt;code dir=&quot;auto&quot;&gt;right-group&lt;/code&gt; parent, allowing proper flex gap spacing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;testing-and-validation&quot;&gt;Testing and Validation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I validated the implementation across multiple dimensions:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;visual-testing&quot;&gt;Visual Testing&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅ Portfolio link displays on desktop viewports&lt;/li&gt;
&lt;li&gt;✅ Link hides on mobile (uses mobile menu instead)&lt;/li&gt;
&lt;li&gt;✅ Color matches Blog link and theme accent&lt;/li&gt;
&lt;li&gt;✅ Spacing is consistent with other nav elements&lt;/li&gt;
&lt;li&gt;✅ Single divider between sections&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;functional-testing&quot;&gt;Functional Testing&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅ Link opens in new tab (&lt;code dir=&quot;auto&quot;&gt;target=&quot;_blank&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Security attributes present (&lt;code dir=&quot;auto&quot;&gt;rel=&quot;noopener noreferrer&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Hover state shows underline&lt;/li&gt;
&lt;li&gt;✅ All original header functionality preserved (search, theme, language)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;responsive-testing&quot;&gt;Responsive Testing&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✅ Desktop: Full navigation visible&lt;/li&gt;
&lt;li&gt;✅ Tablet: Portfolio link present&lt;/li&gt;
&lt;li&gt;✅ Mobile: Falls back to Starlight’s mobile menu (portfolio link hidden)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;bonus-adding-a-docs-navigation-link&quot;&gt;Bonus: Adding a “Docs” Navigation Link&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once I had the Header component override working for Portfolio, I realized there was a UX gap: users on the landing page had no clear way to navigate to the documentation sections without clicking on specific content cards. The site logo redirected to the landing page, but there was no “Docs” entry point in the navigation bar.&lt;/p&gt;
&lt;p&gt;Since I’d already invested the effort to understand and override the Header component, adding another navigation link was straightforward—just another anchor element with the same styling:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/about/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;nav-link&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Docs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://portfolio.devportals.tech&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;_blank&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;noopener noreferrer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;nav-link&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Portfolio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  Docs&lt;a href=&quot;&quot;&gt;  Portfolio&lt;/a&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The “Docs” link points to &lt;code dir=&quot;auto&quot;&gt;/about/&lt;/code&gt;, which serves as a natural entry point—introducing who I am and what the site covers before diving into technical content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Navigation structure after enhancement:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Social icons&lt;/li&gt;
&lt;li&gt;Divider&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs&lt;/strong&gt; (new!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portfolio&lt;/strong&gt; (with external link indicator ↗)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blog&lt;/strong&gt; (from starlight-blog plugin)&lt;/li&gt;
&lt;li&gt;Theme toggle&lt;/li&gt;
&lt;li&gt;Language selector&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This change demonstrates an important principle: &lt;strong&gt;once you understand a system deeply enough to extend it, additional enhancements become significantly easier&lt;/strong&gt;. The initial investment in learning Starlight’s component architecture paid dividends when identifying and fixing a second UX issue.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;maintenance-considerations&quot;&gt;Maintenance Considerations&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;keeping-up-with-starlight-updates&quot;&gt;Keeping Up with Starlight Updates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Challenge&lt;/strong&gt;: This override copies Starlight’s internal Header implementation. When Starlight updates, the Header component might change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mitigation strategies&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pin Starlight version&lt;/strong&gt; in &lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt; to avoid unexpected breakages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor releases&lt;/strong&gt;: Watch Starlight’s changelog for Header changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test after updates&lt;/strong&gt;: Run visual regression tests when upgrading Starlight&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider upstreaming&lt;/strong&gt;: If this pattern is useful, consider proposing Header slots to the Starlight project&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;alternative-approach-for-the-future&quot;&gt;Alternative Approach for the Future&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If Starlight adds proper slot support to Header, this could be simplified to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Default &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@astrojs/starlight/components/Header.astro&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;slot&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;before-theme&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://portfolio.devportals.tech&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Portfolio&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Default&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;  &lt;a slot=&quot;&quot;&gt;    Portfolio  &lt;/a&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This would be more maintainable and less likely to break with updates.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;results-and-lessons-learned&quot;&gt;Results and Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;what-worked-well&quot;&gt;What Worked Well&lt;/h3&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Subdomain architecture&lt;/strong&gt;: Clean separation, independent deployments, easy DNS setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Component override pattern&lt;/strong&gt;: Full control over layout and styling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design system integration&lt;/strong&gt;: Using Starlight’s CSS variables ensures consistency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterative debugging&lt;/strong&gt;: Testing after each change helped isolate issues quickly&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;key-takeaways-for-documentation-engineers&quot;&gt;Key Takeaways for Documentation Engineers&lt;/h3&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Read the source code&lt;/strong&gt;: When documentation is unclear, the source is the ultimate truth&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start simple, iterate&lt;/strong&gt;: Begin with minimal implementation, add complexity incrementally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Respect the design system&lt;/strong&gt;: Use framework-provided variables for maintainability&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document your overrides&lt;/strong&gt;: This blog post itself is part of the solution—future me will thank current me&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Understand plugin vs. override patterns&lt;/strong&gt;: Plugins get blessed APIs, custom work requires deeper integration&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;skills-demonstrated&quot;&gt;Skills Demonstrated&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This project showcases several skills critical for senior technical writers and documentation engineers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical architecture&lt;/strong&gt;: Understanding DNS, subdomains, and deployment strategies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Framework expertise&lt;/strong&gt;: Deep dive into Astro, Starlight, and component systems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugin system understanding&lt;/strong&gt;: Recognizing when to use plugins vs. manual overrides&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Research methodology&lt;/strong&gt;: Finding answers through docs, source code, and experimentation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem-solving&lt;/strong&gt;: Systematic debugging and iterative refinement&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code implementation&lt;/strong&gt;: Writing production-ready Astro components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-framework integration&lt;/strong&gt;: Connecting Next.js and Astro applications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Creating a comprehensive guide for future reference&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Integrating a Next.js portfolio site as a subdomain and adding it to Starlight’s navigation required a combination of infrastructure knowledge, component architecture understanding, and systematic troubleshooting. The subdomain approach provides deployment flexibility while maintaining brand cohesion, and the component override pattern—though more complex than ideal—gives complete control over the navigation experience.&lt;/p&gt;
&lt;p&gt;For documentation engineers and technical writers building their own sites, this pattern demonstrates how to think architecturally about content delivery while maintaining the hands-on technical skills to implement custom solutions.&lt;/p&gt;
&lt;p&gt;The complete code for this integration is available in my &lt;a href=&quot;https://github.com/j-romo/devportals-starlight&quot;&gt;devportals-starlight repository&lt;/a&gt; on the &lt;code dir=&quot;auto&quot;&gt;feature-portfolio-label&lt;/code&gt; branch.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Want to see it in action?&lt;/strong&gt; Visit &lt;a href=&quot;https://devportals.tech&quot;&gt;devportals.tech&lt;/a&gt; and check out the Portfolio link in the navigation bar, or go directly to &lt;a href=&quot;https://portfolio.devportals.tech&quot;&gt;portfolio.devportals.tech&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>nextjs</category><category>astro</category><category>starlight</category><category>vercel</category><category>dns</category><category>component-override</category><category>architecture</category><category>portfolio</category></item><item><title>Building a GitHub Contribution Graph with Next.js Server Components</title><link>https://devportals.tech/blog/github-contributions/</link><guid isPermaLink="true">https://devportals.tech/blog/github-contributions/</guid><pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building a portfolio, showing your GitHub activity tells a story about your consistency, the technologies you work with, and how you balance personal and professional projects. In this guide, I walk you through building a production-ready GitHub contribution graph using Next.js 14 Server Components and TypeScript.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-server-components&quot;&gt;Why Server Components?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Unlike client-side rendering, Server Components allow us to fetch data directly on the server, keeping our GitHub API token secure and improving initial page load performance. This is especially important for portfolio sites where first impressions matter.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-technical-approach&quot;&gt;The Technical Approach&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-setting-up-githubs-graphql-api&quot;&gt;1. Setting Up GitHub’s GraphQL API&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;GitHub’s GraphQL API is more efficient than the REST API for fetching contribution data. Here’s why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Request&lt;/strong&gt;: Get all the data you need in one query&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precise Fields&lt;/strong&gt;: Request only what you need, reducing payload size&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strongly Typed&lt;/strong&gt;: GraphQL schemas provide built-in documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, create a Personal Access Token with &lt;code dir=&quot;auto&quot;&gt;read:user&lt;/code&gt; scope:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Visit https://github.com/settings/tokens&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Generate a new token with read:user scope&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Store it in .env.local&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;ghp_your_token_here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;2-typescript-types-for-type-safety&quot;&gt;2. TypeScript Types for Type Safety&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;One of the keys to maintainable code is proper typing. Here’s our contribution data structure:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; ContributionDay {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionCount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionLevel&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;NONE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;FIRST_QUARTILE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;SECOND_QUARTILE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;THIRD_QUARTILE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;FOURTH_QUARTILE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;GitHub provides contribution levels in quartiles, which we’ll map to color intensities in our visualization.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-the-fetch-function&quot;&gt;3. The Fetch Function&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here’s the core function that talks to GitHub’s API:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetchGitHubContributions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ContributionCalendar&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;query($username: String!) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;user(login: $username) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionsCollection {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionCalendar {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;totalContributions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;weeks {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionDays {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionCount&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;contributionLevel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;              &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;https://api.github.com/graphql&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;method: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Bearer &lt;/span&gt;&lt;span&gt;\$&lt;/span&gt;&lt;span&gt;{process.env.GITHUB_TOKEN}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Content-Type&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;application/json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; variables: { &lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt; } }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// ISR: Cache for 1 hour&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next: { revalidate: &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// Error handling omitted for brevity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contributionsCollection&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;contributionCalendar&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; {  const query = &amp;#x60;    query($username: String!) {      user(login: $username) {        contributionsCollection {          contributionCalendar {            totalContributions            weeks {              contributionDays {                date                contributionCount                contributionLevel              }            }          }        }      }    }  &amp;#x60;;  const response = await fetch(&amp;#x27;https://api.github.com/graphql&amp;#x27;, {    method: &amp;#x27;POST&amp;#x27;,    headers: {      &amp;#x27;Authorization&amp;#x27;: &amp;#x60;Bearer \${process.env.GITHUB_TOKEN}&amp;#x60;,      &amp;#x27;Content-Type&amp;#x27;: &amp;#x27;application/json&amp;#x27;,    },    body: JSON.stringify({ query, variables: { username } }),    // ISR: Cache for 1 hour    next: { revalidate: 3600 },  });  // Error handling omitted for brevity  const result = await response.json();  return result.data.user.contributionsCollection.contributionCalendar;}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;next: { revalidate: 3600 }&lt;/code&gt; option enables Incremental Static Regeneration (ISR), caching the data for one hour. This reduces API calls and improves performance.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-supporting-multiple-accounts&quot;&gt;4. Supporting Multiple Accounts&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Many developers have separate personal and work GitHub accounts. Let’s aggregate them:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aggregateContributions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;usernames&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;{ totalContributions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;; byAccount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&gt; }&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allSettled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;usernames&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;fetchGitHubContributions&lt;/span&gt;&lt;span&gt;(username))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;let &lt;/span&gt;&lt;span&gt;totalContributions&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;byAccount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&gt; = {}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fulfilled&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;totalContributions&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;byAccount[usernames[index]] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;totalContributions &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; count;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { totalContributions&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; byAccount };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; }&gt; {  const results = await Promise.allSettled(    usernames.map(username =&gt; fetchGitHubContributions(username))  );  let totalContributions = 0;  const byAccount: Record&lt;string, number&gt; = {};  results.forEach((result, index) =&gt; {    if (result.status === &amp;#x27;fulfilled&amp;#x27; &amp;#x26;&amp;#x26; result.value) {      const count = result.value.totalContributions;      byAccount[usernames[index]] = count;      totalContributions += count;    }  });  return { totalContributions, byAccount };}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code dir=&quot;auto&quot;&gt;Promise.allSettled&lt;/code&gt; ensures that if one account fails, we still get data from the others.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-the-visual-component&quot;&gt;5. The Visual Component&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Now for the fun part—rendering the contribution calendar:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GitHubContributions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;days&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;90&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;GitHubContributionsProps&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;usernames&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isArray&lt;/span&gt;&lt;span&gt;(username)&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; [username];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;aggregated&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;aggregateContributions&lt;/span&gt;&lt;span&gt;(usernames);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;primaryCalendar&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;fetchGitHubContributions&lt;/span&gt;&lt;span&gt;(usernames[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;recentDays&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;getRecentContributions&lt;/span&gt;&lt;span&gt;(primaryCalendar&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;days);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;weeks&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;(recentDays&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; / &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;grid gap-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;gridTemplateColumns: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;repeat(&lt;/span&gt;&lt;span&gt;\$&lt;/span&gt;&lt;span&gt;{weeks}, minmax(10px, 1fr))&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;gridTemplateRows: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;repeat(7, minmax(10px, 1fr))&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;gridAutoFlow: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;recentDays&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;w-3 h-3 rounded-sm &lt;/span&gt;&lt;span&gt;\$&lt;/span&gt;&lt;span&gt;{levelColors[day.contributionLevel]}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;\$&lt;/span&gt;&lt;span&gt;{day.contributionCount} contributions&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;      {recentDays.map((day) =&gt; (        &lt;div          key={day.date}          className={&amp;#x60;w-3 h-3 rounded-sm \${levelColors[day.contributionLevel]}&amp;#x60;}          title={&amp;#x60;\${day.contributionCount} contributions&amp;#x60;}        /&gt;      ))}      );}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The CSS Grid layout automatically flows contributions from top to bottom, left to right, just like GitHub’s native visualization.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;performance-optimizations&quot;&gt;Performance Optimizations&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Server-Side Rendering&lt;/strong&gt; - Data fetched before the page reaches the client&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISR Caching&lt;/strong&gt; - One-hour cache reduces API calls by 99%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal JavaScript&lt;/strong&gt; - Server Component means zero client-side JS for this feature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimized Query&lt;/strong&gt; - GraphQL fetches only required fields&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;dark-mode-support&quot;&gt;Dark Mode Support&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Using Tailwind’s dark mode variants:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;levelColors&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;NONE: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;bg-neutral-100 dark:bg-neutral-800&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FIRST_QUARTILE: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;bg-green-200 dark:bg-green-900&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// ... more levels&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;error-handling&quot;&gt;Error Handling&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Always handle API failures gracefully:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;primaryCalendar) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;p-4 border rounded-lg&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;p className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text-sm text-neutral-600&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Unable to load contribution data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Please check your GitHub token configuration&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;      &lt;p className=&quot;&quot;&gt;        Unable to load contribution data.        Please check your GitHub token configuration.      &lt;/p&gt;      );}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GraphQL &gt; REST for Nested Data&lt;/strong&gt; - One query beats multiple REST calls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server Components Are Powerful&lt;/strong&gt; - Keep secrets secure, improve performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type Safety Matters&lt;/strong&gt; - TypeScript caught several bugs during development&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache Strategically&lt;/strong&gt; - ISR balances freshness with performance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fail Gracefully&lt;/strong&gt; - Always provide fallback UI for API errors&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a Documentation Engineer, building this feature taught me how developers actually &lt;em&gt;use&lt;/em&gt; our API docs.
The disconnect between reference documentation and real-world implementation patterns became clear, a lesson I’ll carry into future docs work.
When you write the integration guide yourself, you discover which concepts need more explanation, which error messages are confusing, and where examples would save hours of debugging.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;future-enhancements&quot;&gt;Future Enhancements&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Some ideas for taking this further:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interactive Tooltips&lt;/strong&gt; - Show detailed contribution breakdown on hover&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit Message Analysis&lt;/strong&gt; - Display most-used technologies/keywords&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribution Streaks&lt;/strong&gt; - Highlight longest streak of consecutive days&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organization Contributions&lt;/strong&gt; - Include work from private repos (with proper scoping)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This implementation demonstrates the power of Next.js Server Components for building interactive, data-driven portfolio features. By combining GitHub’s GraphQL API with TypeScript and modern React patterns, we’ve created a performant, maintainable solution that showcases both engineering and content skills.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Next.js 14, TypeScript, Tailwind CSS, GitHub GraphQL API&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href=&quot;https://portfolio.devportals.tech/#github&quot;&gt;View on my portfolio&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href=&quot;https://github.com/j-romo/nextjs-portfolio/blob/main/app/lib/github.ts&quot;&gt;github.ts on GitHub&lt;/a&gt;&lt;/p&gt;</content:encoded><category>nextjs</category><category>typescript</category><category>github</category><category>graphql</category><category>server-components</category><category>portfolio</category><category>api-integration</category><category>react</category></item><item><title>Why Redirect Management is Critical for Developer Portals</title><link>https://devportals.tech/blog/redirects-comparison-netlify-vercel/</link><guid isPermaLink="true">https://devportals.tech/blog/redirects-comparison-netlify-vercel/</guid><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Redirects are one of those infrastructure pieces you don’t think about until you need them. I’ve worked with them in two different contexts: a large-scale developer portal using Netlify with complex wildcard patterns and catch-all rules, and now this personal portfolio site on Vercel where I just implemented my first redirect after renaming a directory.&lt;/p&gt;
&lt;p&gt;What struck me is how different platforms handle the same fundamental problem—keeping URLs stable when content moves—but with vastly different syntax and approaches. Whether you’re managing hundreds of versioned documentation pages or just reorganizing a small site, the principles remain the same.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;why-urls-break-and-why-it-matters&quot;&gt;Why URLs Break (and Why It Matters)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Content evolves. Files get renamed, directories get reorganized, documentation gets restructured. Every change risks breaking URLs that someone, somewhere, has bookmarked, shared, or embedded.&lt;/p&gt;
&lt;p&gt;On this site, I recently merged two sparse sections—frameworks and tools—into a single &lt;code dir=&quot;auto&quot;&gt;frameworks-and-tools&lt;/code&gt; directory. Simple change, but any existing links to &lt;code dir=&quot;auto&quot;&gt;/frameworks/*&lt;/code&gt; would now return 404s. On a larger developer portal, these changes happen constantly: version branches, content restructures, product renamings.&lt;/p&gt;
&lt;p&gt;The impact differs by scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Small sites&lt;/strong&gt;: Broken bookmarks, lost SEO ranking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer portals&lt;/strong&gt;: Broken Stack Overflow answers, tutorial videos with dead links, CI/CD scripts that can’t fetch docs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Any site&lt;/strong&gt;: Lost trust when links don’t work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A broken documentation link isn’t just bad UX—it’s a broken promise.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;two-projects-two-approaches&quot;&gt;Two Projects, Two Approaches&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;this-site-simple-structural-change-on-vercel&quot;&gt;This Site: Simple Structural Change on Vercel&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;After consolidating my frameworks and tools sections, I needed one redirect rule in &lt;code dir=&quot;auto&quot;&gt;vercel.json&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;redirects&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;source&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/frameworks/:path*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;destination&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/frameworks-and-tools/:path*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;permanent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Vercel’s syntax is JSON-based, uses &lt;code dir=&quot;auto&quot;&gt;:path*&lt;/code&gt; for wildcards, and &lt;code dir=&quot;auto&quot;&gt;permanent: true&lt;/code&gt; for 301 redirects. Deploy and done.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;enterprise-developer-portal-complex-version-management-on-netlify&quot;&gt;Enterprise Developer Portal: Complex Version Management on Netlify&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;At work, the redirect needs are more complex. Managing multiple documentation versions requires wildcard patterns and catch-all rules in &lt;code dir=&quot;auto&quot;&gt;netlify.toml&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version redirects:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/v2.12/*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/current/:splat&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;302&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Catch-all for SPA routing:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/api/v2.11/*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/api/v2.11/:splat&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Global fallback:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/api/*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/api/current/:splat&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Netlify uses TOML configuration, &lt;code dir=&quot;auto&quot;&gt;:splat&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;:path*&lt;/code&gt;, and supports status 200 rewrites for single-page applications. The syntax differs, but the goal is the same: keep URLs working.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;platform-differences-same-goal-different-syntax&quot;&gt;Platform Differences: Same Goal, Different Syntax&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s what I learned working across both platforms:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;vercel-json&quot;&gt;Vercel (JSON)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;vercel.json&lt;/code&gt; in repository root&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wildcard&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;:path*&lt;/code&gt; captures remaining path segments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status codes&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;permanent: true&lt;/code&gt; (301) or &lt;code dir=&quot;auto&quot;&gt;permanent: false&lt;/code&gt; (302)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Simple redirects, modern frameworks, multiple wildcards per rule&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;netlify-toml&quot;&gt;Netlify (TOML)&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;netlify.toml&lt;/code&gt; in repository root&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wildcard&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;:splat&lt;/code&gt; captures remaining path segments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status codes&lt;/strong&gt;: Explicit &lt;code dir=&quot;auto&quot;&gt;status = 301/302/200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Special feature&lt;/strong&gt;: Status 200 rewrites (serve different content without changing URL)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limitation&lt;/strong&gt;: One wildcard per rule&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Complex versioned docs, SPA routing with rewrites&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;the-common-patterns&quot;&gt;The Common Patterns&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Despite syntax differences, redirect strategies are universal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Wildcard redirects&lt;/strong&gt; - One rule handles entire directory trees&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permanent (301) vs Temporary (302)&lt;/strong&gt; - Choose based on whether the change is forever&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Catch-all patterns&lt;/strong&gt; - Fallback rules prevent 404s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order matters&lt;/strong&gt; - Most platforms process rules sequentially, first match wins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version control&lt;/strong&gt; - Both platforms treat redirects as code, reviewed in pull requests&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;practical-lessons-from-both-projects&quot;&gt;Practical Lessons from Both Projects&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-redirects-are-infrastructure-not-afterthoughts&quot;&gt;1. Redirects Are Infrastructure, Not Afterthoughts&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For this site, I created the redirect rule in the same commit as the directory rename. At work, redirect configuration lives in version control alongside content—every pull request that changes URLs includes the corresponding redirect rules.&lt;/p&gt;
&lt;p&gt;This treats redirects as first-class infrastructure:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;project-repo/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── src/content/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── vercel.json     ← Vercel redirects&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── netlify.toml    ← Or Netlify redirects&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;2-order-matters-specific-before-general&quot;&gt;2. Order Matters: Specific Before General&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Redirect platforms process rules sequentially—first match wins. This is critical for catch-all patterns:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wrong order (catch-all first):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# This will match everything, specific rules never trigger&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/current/:splat&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/old-guide&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/new-guide&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Never reached!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Correct order (specific first):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/old-guide&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/new-guide&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Matches first&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[[redirects]]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/*&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/docs/current/:splat&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Catches everything else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;3-choose-the-right-status-code&quot;&gt;3. Choose the Right Status Code&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;301 (Permanent)&lt;/strong&gt;: I used this for my directory rename—it’s permanent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;302 (Temporary)&lt;/strong&gt;: Better for version aliases that might change&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;200 (Rewrite)&lt;/strong&gt;: Netlify-specific, for SPA routing without changing browser URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The status code tells search engines whether to update their indexes (301) or keep the original URL (302).&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-test-before-production&quot;&gt;4. Test Before Production&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Both Vercel and Netlify offer deploy previews—every PR gets a preview URL to test redirects before merging. Essential for catching issues early.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-documentation-helps-future-you&quot;&gt;5. Documentation Helps Future You&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When I implemented my redirect, I referenced our comprehensive technical guide. Six months from now, I won’t remember the syntax differences between &lt;code dir=&quot;auto&quot;&gt;:splat&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;:path*&lt;/code&gt;—but the documentation will still be there.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;choosing-the-right-platform-for-redirect-management&quot;&gt;Choosing the Right Platform for Redirect Management&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Different hosting platforms handle redirects differently. The platform I work with uses file-based configuration that lives in version control alongside the documentation source. This approach fits naturally into docs-as-code workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration as code&lt;/strong&gt;: Redirect rules are reviewed in pull requests like any other change&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy preview testing&lt;/strong&gt;: Test redirects before they reach production&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No server management&lt;/strong&gt;: Focus on documentation, not infrastructure maintenance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readable syntax&lt;/strong&gt;: Technical writers can understand and modify redirect rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other platforms offer different tradeoffs—more powerful regex capabilities, multiple wildcards per rule, or programmatic control through serverless functions. The “best” platform depends on your specific needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple docs-as-code workflow?&lt;/strong&gt; File-based configuration platforms (Netlify, Vercel) work well&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex pattern matching needs?&lt;/strong&gt; Traditional servers (Apache/Nginx) offer full regex power&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High-scale distributed systems?&lt;/strong&gt; Cloud solutions (AWS CloudFront/Lambda@Edge) provide programmability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is choosing a platform that matches your team’s workflow and technical capabilities, not necessarily the most feature-rich option.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📚 &lt;strong&gt;Want the technical details?&lt;/strong&gt; Check out our comprehensive &lt;a href=&quot;https://devportals.tech/frameworks-and-tools/redirect-configuration-guide/&quot;&gt;Redirect Configuration Guide&lt;/a&gt; comparing syntax, features, and implementation across platforms.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;what-good-redirects-enable&quot;&gt;What Good Redirects Enable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Whether managing a small portfolio site or a large developer portal, redirects provide the same benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Restructure confidently&lt;/strong&gt; - reorganize content without worrying about breaking bookmarks or SEO&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterate faster&lt;/strong&gt; - make improvements without coordinating with everyone who’s linked to your content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintain trust&lt;/strong&gt; - users land on the right page, even from years-old links&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sustainable growth&lt;/strong&gt; - sites can evolve without accumulating technical debt from fear of breaking URLs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;True docs-as-code&lt;/strong&gt; - content changes flow through Git with redirect rules reviewed like any other code&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;key-takeaways-for-developer-portal-teams&quot;&gt;Key Takeaways for Developer Portal Teams&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’re building or migrating a developer portal, treat redirects as critical infrastructure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Plan redirects before restructuring&lt;/strong&gt; - don’t wait until after migration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version control your redirect config&lt;/strong&gt; - redirects are code, not server config&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use wildcard patterns&lt;/strong&gt; - one rule can handle hundreds of URLs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement catch-alls&lt;/strong&gt; - ensure users never hit 404s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test in preview environments&lt;/strong&gt; - catch problems before production&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor redirect metrics&lt;/strong&gt; - 404 rates, redirect hits, chains&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose platforms that support docs-as-code&lt;/strong&gt; - file-based config beats UI-based config&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document your redirect strategy&lt;/strong&gt; - future team members will thank you&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Redirect management isn’t glamorous work. It’s invisible when it works correctly—which is exactly the point. Users shouldn’t think about redirects. They should just land where they need to go, regardless of which old URL they clicked.&lt;/p&gt;
&lt;p&gt;The platforms differ—Vercel’s JSON vs Netlify’s TOML, &lt;code dir=&quot;auto&quot;&gt;:path*&lt;/code&gt; vs &lt;code dir=&quot;auto&quot;&gt;:splat&lt;/code&gt;—but the principles are universal. Whether you’re redirecting one renamed directory or managing hundreds of versioned documentation pages, the strategy remains the same: keep URLs stable, handle changes gracefully, and treat redirects as infrastructure.&lt;/p&gt;
&lt;p&gt;I just implemented my first redirect on this site. At work, I work with complex multi-version redirect strategies. Both serve the same fundamental purpose: keeping promises to users who depend on links working.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Building a developer portal or migrating documentation platforms? I’d love to hear about your redirect challenges and strategies. Connect with me on &lt;a href=&quot;https://www.linkedin.com/company/devportals-tech&quot;&gt;LinkedIn&lt;/a&gt; to continue the conversation.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>developer-portals</category><category>redirects</category><category>docs-as-code</category><category>content-strategy</category><category>url-management</category><category>netlify</category></item><item><title>From Chaos to Clarity: A Technical Writer&apos;s Journey Through Git Cherry-Picking and Team Collaboration</title><link>https://devportals.tech/blog/cherry-pick-copilot/</link><guid isPermaLink="true">https://devportals.tech/blog/cherry-pick-copilot/</guid><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As a Senior Technical Writer, my role extends beyond crafting clear documentation—it involves navigating complex technical challenges to help my team achieve our objectives. Recently, I led an effort to salvage months of work from a stale feature branch, teaching me valuable lessons about git workflows, team collaboration, AI-assisted development, and the resilience required in technical documentation work.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-challenge&quot;&gt;The Challenge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Our team had been working on a major documentation revamp for several months. Multiple writers contributed to a shared feature branch, building a comprehensive update to our product’s documentation. However, as weeks turned into months, our feature branch became increasingly disconnected from the main branch—912 commits behind, to be exact.&lt;/p&gt;
&lt;p&gt;The branch had contributions from numerous team members, each adding valuable content, but the cumulative drift from main created an untenable situation. When we finally attempted to merge, we faced hundreds of merge conflicts. The traditional merge approach would require untangling conflicts across dozens of files, risking the loss of carefully crafted content or introducing errors that could take days to identify.&lt;/p&gt;
&lt;p&gt;We needed a different strategy: cherry-picking.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-solution-strategic-cherry-picking&quot;&gt;The Solution: Strategic Cherry-Picking&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Rather than attempting to merge the entire bloated branch, I created a clean feature branch from main and selectively cherry-picked the commits that contained our team’s actual work. This surgical approach allowed us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cherry-picked 30+ commits&lt;/strong&gt; from the old feature branch, each representing a discrete piece of work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolved conflicts systematically&lt;/strong&gt; as they appeared, one commit at a time, rather than facing a wall of conflicts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fixed multiple build errors&lt;/strong&gt; including toctree references, YAML indentation issues, and duplicate reference labels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synced with 2 weeks of main branch updates&lt;/strong&gt; to ensure our work integrated cleanly with the latest changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Successfully merged into main&lt;/strong&gt; with zero conflicts at the end, delivering the complete documentation revamp&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;p&gt;&lt;img src=&quot;https://devportals.tech/_astro/cherry-pick-results.B6GQYeRC_Z1axTWr.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; fetchpriority=&quot;auto&quot; width=&quot;720&quot; height=&quot;655&quot;&gt;&lt;/p&gt;&lt;figcaption&gt;&lt;p&gt;GitHub Copilot’s terminal output showing the successful cherry-pick workflow recap.&lt;/p&gt;&lt;/figcaption&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;the-senior-technical-writers-role&quot;&gt;The Senior Technical Writer’s Role&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This experience highlighted several aspects of a Senior Technical Writer’s role that go beyond writing:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-technical-problem-solving&quot;&gt;1. Technical Problem-Solving&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Understanding git workflows deeply enough to propose and execute a cherry-picking strategy, rather than following the “obvious” merge path that would have failed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-risk-management&quot;&gt;2. Risk Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Identifying when a situation requires a different approach and having the confidence to advocate for it, even when it means more upfront work.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-team-coordination&quot;&gt;3. Team Coordination&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Managing a complex migration that affected multiple team members’ work while ensuring nothing was lost in the process.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-quality-assurance&quot;&gt;4. Quality Assurance&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Systematically addressing build errors, validating changes, and ensuring the final product met our standards before merging.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-persistence-and-attention-to-detail&quot;&gt;5. Persistence and Attention to Detail&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Maintaining focus through dozens of commits, each requiring conflict resolution and validation, without losing sight of the ultimate goal.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;technical-learnings-git-commands-that-made-the-difference&quot;&gt;Technical Learnings: Git Commands That Made the Difference&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This project deepened my understanding of git beyond the basics. Here are the key commands and concepts I mastered:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;checking-for-merge-conflicts-before-merging&quot;&gt;Checking for Merge Conflicts Before Merging&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge-tree&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge-base&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature-branch&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature-branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-E&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;^(&amp;#x3C;&amp;#x3C;&amp;#x3C;&amp;#x3C;&amp;#x3C;|=====|&gt;&gt;&gt;&gt;&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;wc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;&gt;&gt;&gt;&gt;)&amp;#x22; | wc -l&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command simulates a merge without actually performing it, returning the count of conflict markers. Zero means you’re clear to merge!&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;monitoring-branch-divergence&quot;&gt;Monitoring Branch Divergence&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge-tree&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge-base&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;HEAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;^changed in both&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This powerful one-liner fetches the latest main and checks how many files have conflicting changes between your branch and main—essential for planning your merge strategy.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;cherry-picking-workflow&quot;&gt;Cherry-Picking Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Starting the cherry-pick:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;commit-hash&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;When conflicts occur:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Review conflicts, fix them manually, then:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;resolved-files&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;git cherry-pick --continue&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;If you need to bail out:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--abort&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Handling empty commits&lt;/strong&gt; (useful when a commit’s changes already exist in your branch):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cherry-pick&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--allow-empty&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;commit-hash&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Or during conflict resolution:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--allow-empty&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt;git commit --allow-empty&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;examining-commits&quot;&gt;Examining Commits&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Quick one-line view of recent commits:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Detailed view of what changed in a specific commit:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--stat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;commit-hash&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&gt;&lt;/span&gt;&lt;span&gt;/dev/null&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;&quot;&gt; 2&gt;/dev/null | head -20&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This shows file statistics (additions/deletions) without overwhelming you with full diffs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;keeping-your-branch-up-to-date&quot;&gt;Keeping Your Branch Up-to-Date&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The three-step sync:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. Fetch latest changes from remote&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. Merge main into your feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin/main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. Push your updated branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature-branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;machine-readable-git-status&quot;&gt;Machine-Readable Git Status&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--porcelain&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--porcelain&lt;/code&gt; flag provides stable, script-friendly output with status codes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;M&lt;/code&gt; = modified&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;A&lt;/code&gt; = added&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;D&lt;/code&gt; = deleted&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;UU&lt;/code&gt; = both modified (conflict)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;AA&lt;/code&gt; = both added (conflict)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perfect for automation or quickly identifying conflict types.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;ai-as-a-force-multiplier-github-copilot-in-action&quot;&gt;AI as a Force Multiplier: GitHub Copilot in Action&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of the most significant factors in completing this project efficiently was my strategic use of GitHub Copilot as an AI pair-programming partner. Far from replacing the technical writer’s role, AI tools like Copilot enhanced my capabilities and accelerated problem-solving in ways that would have been impossible just a few years ago.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;how-github-copilot-transformed-the-workflow&quot;&gt;How GitHub Copilot Transformed the Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Real-Time Error Diagnosis&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When build errors appeared—and they appeared frequently—I could immediately share the error message with Copilot and get targeted suggestions. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;YAML indentation errors&lt;/strong&gt;: Copilot identified that a &lt;code dir=&quot;auto&quot;&gt;.. tabs::&lt;/code&gt; directive needed exactly 5 spaces of indentation to match sibling tabs, not the 4 I had initially used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;reStructuredText syntax issues&lt;/strong&gt;: When I encountered “Unexpected indentation” errors, Copilot explained that &lt;code dir=&quot;auto&quot;&gt;content: |&lt;/code&gt; blocks in YAML require a blank line after the pipe character when followed by reStructuredText directives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duplicate reference labels&lt;/strong&gt;: Copilot quickly located duplicate &lt;code dir=&quot;auto&quot;&gt;.. _reference:&lt;/code&gt; labels across files and suggested the appropriate fix.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Git Command Expertise On-Demand&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rather than constantly searching Stack Overflow or git documentation, I could ask Copilot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“How do I simulate a merge to check for conflicts?”&lt;/li&gt;
&lt;li&gt;“What does &lt;code dir=&quot;auto&quot;&gt;git status --porcelain&lt;/code&gt; do?”&lt;/li&gt;
&lt;li&gt;“How do I handle empty commits during cherry-picking?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The responses were contextual, accurate, and saved hours of research time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Pattern Recognition Across Files&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I discovered one YAML formatting error, Copilot helped me identify similar issues in related files. Instead of fixing errors reactively as the build system found them, I could proactively search for and fix patterns.&lt;/p&gt;
&lt;p&gt;For example, Copilot suggested searching for similar patterns:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-r&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content: | &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--include=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.yaml&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;documentation/source/includes/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;4. Batch Operations and Automation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Copilot helped me construct complex one-liners that would have taken significant time to craft manually, like the branch divergence checker or the conflict counter. These commands became part of my toolkit for future projects.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;technical-writers-are-not-disadvantaged-by-aiwere-empowered&quot;&gt;Technical Writers Are Not Disadvantaged by AI—We’re Empowered&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;There’s a common misconception that AI tools will diminish the role of technical writers. My experience proves the opposite:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI handles the tedious, writers handle the critical:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copilot helped me syntax-check YAML and identify file patterns&lt;/li&gt;
&lt;li&gt;I made the strategic decisions about which commits to cherry-pick, how to resolve conflicts, and what the documentation structure should be&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI accelerates learning, writers apply knowledge:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copilot explained git commands and their options&lt;/li&gt;
&lt;li&gt;I chose which commands to use, when to use them, and understood their implications for the team&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI suggests, writers validate:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copilot proposed fixes for indentation and formatting errors&lt;/li&gt;
&lt;li&gt;I reviewed each suggestion, tested the builds, and ensured the fixes were correct in context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI answers questions, writers ask the right questions:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Knowing what to ask Copilot required deep understanding of the problem space&lt;/li&gt;
&lt;li&gt;Interpreting and applying Copilot’s responses required expertise in documentation systems, build tools, and git workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;the-augmented-technical-writer&quot;&gt;The Augmented Technical Writer&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The future of technical writing isn’t about competing with AI—it’s about leveraging AI as a powerful tool in our arsenal. Just as we adopted spell-checkers, version control, and static site generators, AI coding assistants are the next evolution in our toolkit.&lt;/p&gt;
&lt;p&gt;Technical writers who embrace AI tools like GitHub Copilot will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Solve problems faster by having instant access to technical expertise&lt;/li&gt;
&lt;li&gt;Learn continuously through interactive exploration of commands and tools&lt;/li&gt;
&lt;li&gt;Focus on higher-value work by offloading syntax checking and pattern matching&lt;/li&gt;
&lt;li&gt;Deliver better results through the combination of AI efficiency and human judgment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is understanding that AI doesn’t replace the technical writer’s expertise—it amplifies it. My knowledge of documentation systems, content strategy, and user needs guided every decision. Copilot was my assistant, not my replacement.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-regular-integration-prevents-drift&quot;&gt;1. Regular Integration Prevents Drift&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Feature branches that live for months accumulate technical debt. Regular merges from main keep conflicts manageable and prevent the kind of crisis we faced.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-cherry-picking-is-not-always-harder&quot;&gt;2. Cherry-Picking Is Not Always Harder&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;While cherry-picking individual commits seems more tedious than a single merge, it provides granular control that can actually save time when dealing with heavily diverged branches.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-build-validation-is-critical&quot;&gt;3. Build Validation Is Critical&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;After every few cherry-picks, running a build and fixing errors immediately is far more efficient than waiting until the end and facing dozens of cascading issues.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-documentation-of-process-matters&quot;&gt;4. Documentation of Process Matters&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Keeping notes on which commits were cherry-picked, which conflicts were resolved, and what decisions were made creates an invaluable audit trail.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-ai-tools-are-essential-not-optional&quot;&gt;5. AI Tools Are Essential, Not Optional&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Using AI assistants like GitHub Copilot transformed a potentially week-long ordeal into a manageable, two-day project. Technical writers who resist these tools will find themselves at a significant disadvantage.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;6-human-expertise-remains-irreplaceable&quot;&gt;6. Human Expertise Remains Irreplaceable&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;AI provided suggestions and information, but every critical decision—which commits to include, how to resolve conflicts, when to merge—required human judgment and domain expertise.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This exercise transformed a potential disaster—months of work trapped in an unmergeable branch—into a successful delivery. More importantly, it reinforced that Senior Technical Writers must be technologists first, capable of solving complex technical problems to unblock their teams.&lt;/p&gt;
&lt;p&gt;The git commands I learned aren’t just theoretical knowledge—they’re practical tools that will serve me in future projects. The experience of systematically working through 30+ commits, resolving conflicts, fixing build errors, and ultimately delivering clean, integrated documentation has made me a more effective technical writer and a more valuable team member.&lt;/p&gt;
&lt;p&gt;And perhaps most importantly, this project demonstrated that AI tools like GitHub Copilot don’t diminish the technical writer’s role—they elevate it. By handling the mechanical aspects of syntax, pattern matching, and information retrieval, AI frees us to focus on what humans do best: strategic thinking, creative problem-solving, and making judgment calls that require deep domain expertise.&lt;/p&gt;
&lt;p&gt;To my fellow technical writers: embrace the technical challenges. Learn the tools deeply. Don’t shy away from complex git workflows or build systems. And absolutely use AI assistants—they’re not your competition, they’re your superpower. These skills and tools make us more than writers—they make us technical leaders who can guide our teams through any challenge.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you faced similar challenges with long-lived feature branches? How are you incorporating AI tools into your technical writing workflow? Share your experiences in the comments below.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>git</category><category>workflow</category><category>ai</category><category>github-copilot</category><category>cherry-pick</category><category>technical-writing</category><category>collaboration</category></item><item><title>From Forks to Branches: Streamlining Team Git Workflows</title><link>https://devportals.tech/blog/forks-to-branches/</link><guid isPermaLink="true">https://devportals.tech/blog/forks-to-branches/</guid><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As part of a GitHub working group within a 50-person technical writing team, I recently helped lead our transition from a fork-based workflow to a centralized branching strategy. This shift simplified our collaboration model and reduced friction across four sub-teams. Here’s what we learned.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-challenge&quot;&gt;The Challenge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Our team of technical writers was using personal forks of our main documentation repository. While this approach worked, we identified several pain points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extra complexity&lt;/strong&gt; for common operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confusion&lt;/strong&gt; about which remote to push/pull from&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent workflows&lt;/strong&gt; across team members&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited value&lt;/strong&gt; for our use case compared to open-source projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The GitHub working group formed with representatives from each sub-team to evaluate whether forks still made sense for our internal collaboration model.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;fork-based-workflow-pros-and-cons&quot;&gt;Fork-Based Workflow: Pros and Cons&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;pros&quot;&gt;Pros&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Permission isolation&lt;/strong&gt;: Contributors don’t need write access to the main repository&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safety net&lt;/strong&gt;: Mistakes in personal forks don’t affect the main repo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open-source standard&lt;/strong&gt;: Familiar pattern for external contributors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimentation space&lt;/strong&gt;: Break things without consequences&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;cons&quot;&gt;Cons&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cognitive overhead&lt;/strong&gt;: Managing two remotes (&lt;code dir=&quot;auto&quot;&gt;origin&lt;/code&gt; for fork, &lt;code dir=&quot;auto&quot;&gt;upstream&lt;/code&gt; for main repo)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex tracking&lt;/strong&gt;: Feature branches track &lt;code dir=&quot;auto&quot;&gt;upstream/main&lt;/code&gt;, not &lt;code dir=&quot;auto&quot;&gt;origin/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push/pull confusion&lt;/strong&gt;: Easy to push to wrong remote&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sync maintenance&lt;/strong&gt;: Keeping forks updated requires extra steps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited benefit&lt;/strong&gt;: For trusted team members with write access, forks add complexity without security gains&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;centralized-branching-pros-and-cons&quot;&gt;Centralized Branching: Pros and Cons&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;pros-1&quot;&gt;Pros&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simpler mental model&lt;/strong&gt;: One remote (&lt;code dir=&quot;auto&quot;&gt;origin&lt;/code&gt;), straightforward push/pull&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent workflow&lt;/strong&gt;: All team members follow identical patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced friction&lt;/strong&gt;: Create branch, push, open PR—done&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better visibility&lt;/strong&gt;: All work visible in main repository&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier collaboration&lt;/strong&gt;: Team members can push to each other’s branches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural for internal teams&lt;/strong&gt;: Aligns with typical corporate git usage&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;cons-1&quot;&gt;Cons&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Requires repository access&lt;/strong&gt;: All contributors need write permissions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch proliferation&lt;/strong&gt;: Main repo contains everyone’s feature branches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less isolation&lt;/strong&gt;: No fork-level separation between contributors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not suitable for open-source&lt;/strong&gt;: External contributors can’t use this model&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-migration-process&quot;&gt;The Migration Process&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Transitioning from forks to branches required careful planning to avoid disrupting in-flight work. Here’s the approach we took:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-timing-considerations&quot;&gt;1. Timing Considerations&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Existing pull requests from forks remain unaffected on GitHub&lt;/li&gt;
&lt;li&gt;PR merges happen server-side, independent of local configuration&lt;/li&gt;
&lt;li&gt;Safe to reconfigure during active PR reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;2-repository-reconfiguration&quot;&gt;2. Repository Reconfiguration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Preserve fork as backup:&lt;/p&gt;
&lt;p&gt;git remote rename origin fork&lt;/p&gt;
&lt;p&gt;Promote main repository to primary remote:&lt;/p&gt;
&lt;p&gt;git remote rename upstream origin&lt;/p&gt;
&lt;p&gt;Update branch tracking:&lt;/p&gt;
&lt;p&gt;git branch —set-upstream-to=origin/main main&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-new-workflow&quot;&gt;3. New Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Start new work:&lt;/p&gt;
&lt;p&gt;git checkout main
git pull origin main
git checkout -b TICKET-123-feature&lt;/p&gt;
&lt;p&gt;Push with tracking:&lt;/p&gt;
&lt;p&gt;git push -u origin TICKET-123-feature&lt;/p&gt;
&lt;p&gt;Open PR: same repo, branch → main&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;key-insights-from-the-working-group&quot;&gt;Key Insights from the Working Group&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context matters&lt;/strong&gt;: Forks excel for open-source projects with external contributors. Internal teams with repository access benefit from simpler branching models.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Change management&lt;/strong&gt;: Clear communication about the transition reduced confusion. We provided documentation and supported team members through the migration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tooling adaptation&lt;/strong&gt;: Some team members had scripts and tools configured for fork workflows. We identified these dependencies early and provided migration guidance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No one-size-fits-all&lt;/strong&gt;: Different teams have different needs. Our analysis showed that for our use case—a private repository with trusted contributors—the fork overhead wasn’t justified.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Post-migration, our team experienced:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reduced onboarding time&lt;/strong&gt; for new technical writers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer git-related questions&lt;/strong&gt; in team channels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More consistent practices&lt;/strong&gt; across sub-teams&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster PR workflows&lt;/strong&gt; without fork sync issues&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fork versus branch decision isn’t about which approach is objectively better—it’s about matching your workflow to your team structure and needs. For our internal documentation team with repository write access, moving to a centralized branching model eliminated unnecessary complexity while maintaining code review quality and collaboration practices.&lt;/p&gt;
&lt;p&gt;As technical writers increasingly manage documentation as code, understanding git workflow trade-offs becomes essential. This migration taught me that technical leadership means not just documenting tools, but critically evaluating whether those tools serve your team’s actual needs.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you transitioned between fork and branch workflows? I’d love to hear about your experience. Connect with me on LinkedIn to continue the conversation.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>git</category><category>workflow</category><category>team-collaboration</category><category>forks</category><category>branching-strategy</category><category>migration</category></item><item><title>How I&apos;m Using MDX Frontmatter to Scale devportals.tech</title><link>https://devportals.tech/blog/mdx-frontmatter-enterprise-documentation/</link><guid isPermaLink="true">https://devportals.tech/blog/mdx-frontmatter-enterprise-documentation/</guid><pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Building devportals.tech has been an exercise in practicing what I preach about documentation engineering. One of the most impactful decisions I made early was leveraging MDX frontmatter not just for basic metadata, but as the foundation for scalable content workflows. Here’s how it’s working in practice.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;starting-simple-scaling-smart&quot;&gt;Starting Simple, Scaling Smart&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When I first set up this Astro Starlight site, I could have just used basic Markdown. But knowing I wanted to build something that demonstrates professional documentation practices, I started with structured frontmatter from day one:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Content Branching Strategy for Documentation Teams&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;How to manage documentation releases, staging, and quality control using Git workflows&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2025-09-23&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;authors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Joaquin Romo&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;git-workflow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;content-management&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;documentation-strategy&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Simple, but it’s already paying dividends.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-content-strategy-thats-emerging&quot;&gt;The Content Strategy That’s Emerging&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As I’ve added more content to the site, patterns are emerging that I never planned but are incredibly valuable:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;tagging-for-discovery&quot;&gt;Tagging for Discovery&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;My tags are becoming a taxonomy that helps visitors (and me) understand the content landscape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;content-strategy&lt;/code&gt; for high-level planning topics&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;git-workflow&lt;/code&gt; for technical implementation details&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;documentation-strategy&lt;/code&gt; for process and methodology&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;astro-starlight&lt;/code&gt; for platform-specific insights&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Starlight Blog plugin automatically generates tag pages, so this creates navigation paths I didn’t have to manually build.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;authors-for-accountability&quot;&gt;Authors for Accountability&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Even as the only author right now, having structured author metadata sets me up for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Future collaboration&lt;/strong&gt; - The schema is ready when I invite guest writers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Professional presentation&lt;/strong&gt; - It shows I think about authorship professionally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content attribution&lt;/strong&gt; - If I repurpose content elsewhere, attribution is baked in&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;dates-for-content-lifecycle&quot;&gt;Dates for Content Lifecycle&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every piece has a clear publication date, and I’m starting to add &lt;code dir=&quot;auto&quot;&gt;lastUpdated&lt;/code&gt; fields for major revisions. This helps me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Track content freshness&lt;/strong&gt; - I can see what might need updates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Show visitors currency&lt;/strong&gt; - Recent dates build confidence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan maintenance&lt;/strong&gt; - I can query for older content that needs review&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;real-workflow-benefits&quot;&gt;Real Workflow Benefits&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;branch-based-content-development&quot;&gt;Branch-Based Content Development&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The structured metadata makes my Git workflow much more manageable. When I’m working on new content in feature branches, I can easily see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What’s ready to publish (clean frontmatter)&lt;/li&gt;
&lt;li&gt;What needs more work (missing descriptions, placeholder content)&lt;/li&gt;
&lt;li&gt;How content fits into the overall site structure&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;quality-control-through-structure&quot;&gt;Quality Control Through Structure&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Having consistent frontmatter forces me to think about each piece:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What’s the actual value proposition?&lt;/strong&gt; (title and description)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who benefits from reading this?&lt;/strong&gt; (tags and implicit audience)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How does this fit the larger narrative?&lt;/strong&gt; (category and relationships)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s like having a checklist that ensures I’m not just writing, but creating strategic content.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-technical-implementation&quot;&gt;The Technical Implementation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’m using Astro’s content collections with a schema that validates my frontmatter:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;blogSchema&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;date: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;authors: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tags: &lt;/span&gt;&lt;span&gt;z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;(z&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This catches mistakes early and ensures consistency. When I tried to publish a post with malformed dates or missing descriptions, the build failed with clear error messages.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As devportals.tech grows, I’m planning to leverage this metadata foundation for:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;automated-content-analysis&quot;&gt;Automated Content Analysis&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I want to build scripts that analyze my content patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which topics get the most tags (showing my focus areas)&lt;/li&gt;
&lt;li&gt;Content gaps where I have few posts&lt;/li&gt;
&lt;li&gt;Author productivity metrics (just for fun as a solo creator)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;better-discovery-features&quot;&gt;Better Discovery Features&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The structured data opens possibilities for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Related content” sections based on tag similarity&lt;/li&gt;
&lt;li&gt;Topic clustering for themed reading paths&lt;/li&gt;
&lt;li&gt;Content recommendations based on visitor interests&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;rss-and-social-integration&quot;&gt;RSS and Social Integration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Rich metadata makes automated social media posts more meaningful. Instead of generic “new post” notifications, I can generate contextual descriptions that highlight the specific value each piece provides.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-bigger-picture&quot;&gt;The Bigger Picture&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;What I’m really doing is treating my personal documentation site like a product. The frontmatter discipline forces me to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Think strategically&lt;/strong&gt; about each piece of content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build systems&lt;/strong&gt; that scale beyond manual management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demonstrate professionalism&lt;/strong&gt; in how I approach documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For anyone building their own documentation site, I’d recommend starting with structured frontmatter even if you’re not sure how you’ll use it all. The discipline of thinking about metadata makes you a better content creator, and the flexibility enables capabilities you haven’t thought of yet.&lt;/p&gt;
&lt;p&gt;The question isn’t whether you need metadata - it’s whether you want to build something that can grow intelligently or just accumulate content randomly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Have you found creative ways to use frontmatter in your own documentation projects? I’d love to hear about approaches that have worked well for you.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>mdx</category><category>frontmatter</category><category>content-strategy</category><category>astro-starlight</category><category>workflow</category></item><item><title>Content Branching Strategy for Documentation Sites</title><link>https://devportals.tech/blog/content-branching-strategy/</link><guid isPermaLink="true">https://devportals.tech/blog/content-branching-strategy/</guid><pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building a professional documentation site like DevPortals.tech, maintaining content quality while enabling rapid development requires a strategic approach to version control. Here’s the branching strategy I’ve implemented for managing documentation content lifecycle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-challenge&quot;&gt;The Challenge&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Building a portfolio-quality documentation site presents unique challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quality vs. Speed&lt;/strong&gt;: You want to publish frequently but maintain professional standards&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Work-in-Progress Content&lt;/strong&gt;: Some sections need extensive research and iteration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Professional Presentation&lt;/strong&gt;: Your live site represents your expertise to potential employers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Development Flexibility&lt;/strong&gt;: You need space to experiment without affecting production&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;&lt;h2 id=&quot;the-solution-feature-branches-for-content-sections&quot;&gt;The Solution: Feature Branches for Content Sections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Instead of treating all documentation as a single unit, I organize content development around feature branches that correspond to major site sections.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;current-branch-structure&quot;&gt;Current Branch Structure&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;# Production-ready content only&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;# Individual markup language deep-dives&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-frameworks&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# Next.js vs Docusaurus detailed guides&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-migration&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Advanced migration case studies&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-ai-tools&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# AI integration strategies&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-resources&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Curated tools and resources&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└──&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-templates&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Template library development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;real-world-example-markup-languages-section&quot;&gt;Real-World Example: Markup Languages Section&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s how I recently implemented this strategy:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;problem&quot;&gt;Problem&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;I had created overview content for markup languages (Markdown, MDX, reStructuredText, AsciiDoc) but the individual deep-dive pages weren’t production-ready. I needed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep the excellent overview page live&lt;/li&gt;
&lt;li&gt;Remove incomplete individual pages from production&lt;/li&gt;
&lt;li&gt;Maintain development access to work on detailed content&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;solution&quot;&gt;Solution&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. Create feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. Move non-production files to staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup-staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mv&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/{asciidoc,markdown,mdx,restructuredtext}.mdx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup-staging/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. Commit to feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Move non-production markup files to staging&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. Clean up main branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/{asciidoc,markdown,mdx,restructuredtext}.mdx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Keep only overview page in production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;development-workflow&quot;&gt;Development Workflow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When I want to work on the markup content:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Switch to development branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Copy staging files for preview&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup-staging/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start development server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Edit content with full preview capability&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Save changes back to staging when done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-markup-staging/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Commit progress&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Improve markdown deep-dive content&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;benefits-of-this-approach&quot;&gt;Benefits of This Approach&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;quality-control&quot;&gt;Quality Control&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Production site stays clean&lt;/strong&gt;: No placeholder or incomplete content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Professional presentation&lt;/strong&gt;: Every live page meets publication standards&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confidence in sharing&lt;/strong&gt;: Safe to include site URL in job applications&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;development-flexibility&quot;&gt;Development Flexibility&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Experimental freedom&lt;/strong&gt;: Try different approaches without consequences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Local preview capability&lt;/strong&gt;: See changes before committing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterative improvement&lt;/strong&gt;: Refine content over multiple sessions&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;portfolio-value&quot;&gt;Portfolio Value&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Demonstrates workflow proficiency&lt;/strong&gt;: Shows understanding of professional development practices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version control expertise&lt;/strong&gt;: Reflects enterprise-level Git knowledge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content lifecycle management&lt;/strong&gt;: Critical skill for platform roles&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;quick-branch-workflow-cheatsheet&quot;&gt;Quick Branch Workflow Cheatsheet&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;creating-a-new-content-section&quot;&gt;Creating a New Content Section&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Create and switch to feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section-name]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Develop content&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ... create and edit files ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Commit progress&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add [section] content framework&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Push feature branch (optional, for backup)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section-name]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;working-on-existing-feature-branch&quot;&gt;Working on Existing Feature Branch&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Switch to feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section-name]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Copy staging content for preview (if applicable)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section]-staging/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/[section]/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Start development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Edit content...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Save changes back to staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/[section]/&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section]-staging/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Commit changes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Update [section] content&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;publishing-to-production&quot;&gt;Publishing to Production&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Ensure you&apos;re on main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Merge feature branch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;merge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section-name]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Push to production&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# → Triggers deployment to live site&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Clean up (optional)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;branch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-d&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section-name]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;emergency-fixes&quot;&gt;Emergency Fixes&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# For quick fixes to live content&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;checkout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ... make changes ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Fix typo in migration guide&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-use-this-strategy&quot;&gt;When to Use This Strategy&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;good-fit&quot;&gt;Good Fit&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Professional portfolio sites&lt;/strong&gt;: Where quality matters more than speed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical documentation&lt;/strong&gt;: Content requiring research and iteration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-author projects&lt;/strong&gt;: Where review cycles are important&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long-form content&lt;/strong&gt;: Guides, tutorials, comprehensive references&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;consider-alternatives&quot;&gt;Consider Alternatives&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple blogs&lt;/strong&gt;: Where immediate publishing is preferred&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;News sites&lt;/strong&gt;: Where timeliness trumps perfection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal documentation&lt;/strong&gt;: Where informal content is acceptable&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;implementation-tips&quot;&gt;Implementation Tips&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;branch-naming-convention&quot;&gt;Branch Naming Convention&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Use descriptive prefixes that match your content structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;docs-&lt;/code&gt; for documentation sections&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;feature-&lt;/code&gt; for new site functionality&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;fix-&lt;/code&gt; for bug fixes&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;content-&lt;/code&gt; for major content restructuring&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;staging-directory-strategy&quot;&gt;Staging Directory Strategy&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For content that needs extensive development:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-[section]-staging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Move incomplete files here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Copy back for preview as needed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;automation-opportunities&quot;&gt;Automation Opportunities&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Consider shell scripts or aliases for common workflows:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# ~/.bashrc or ~/.zshrc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-preview&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cp docs-*-staging/* src/content/docs/*/; npm run dev&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docs-save&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cp src/content/docs/*/* docs-*-staging/&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This branching strategy transforms documentation development from a chaotic process into a professional workflow. It enables rapid iteration while maintaining production quality, demonstrates version control proficiency, and provides the confidence to share your work at any stage.&lt;/p&gt;
&lt;p&gt;The key insight: &lt;strong&gt;treat your documentation site like enterprise software&lt;/strong&gt;, with proper development, staging, and production environments. This approach not only improves your content quality but also showcases the systematic thinking that platform teams value.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This strategy has transformed how I approach documentation development, enabling both quality control and development velocity. How do you manage content lifecycle in your documentation projects?&lt;/em&gt;&lt;/p&gt;</content:encoded><category>git</category><category>documentation</category><category>workflow</category><category>content-management</category><category>version-control</category><category>branching-strategy</category></item><item><title>Starlight Sidebar Configuration Strategies: Manual vs Autogenerate</title><link>https://devportals.tech/blog/sidebar-config-strategies/</link><guid isPermaLink="true">https://devportals.tech/blog/sidebar-config-strategies/</guid><pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While building DevPortals.tech, I ran into a common documentation site challenge: how do you organize sidebar navigation when you want some content to appear in a specific order (like overview pages) while still automatically including new files as you create them?&lt;/p&gt;
&lt;p&gt;This is the kind of practical problem that comes up constantly when building documentation sites, and the solution isn’t always obvious from the docs.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I had a “Markup Languages” section with several files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;overview.mdx&lt;/code&gt; (should appear first)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;asciidoc.mdx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;markdown.mdx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;mdx.mdx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;restructuredtext.mdx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Starlight’s &lt;code dir=&quot;auto&quot;&gt;autogenerate&lt;/code&gt; feature, the sidebar was ordering files alphabetically, putting “overview” in the 4th position instead of first. Not ideal for user experience.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;strategy-1-manual-items-array&quot;&gt;Strategy 1: Manual Items Array&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The most straightforward approach is to manually specify all sidebar items:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;astro.config.mjs&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Markup Languages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Overview&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/overview/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;AsciiDoc&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/asciidoc/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Markdown&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/markdown/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;MDX&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/mdx/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;reStructuredText&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/restructuredtext/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complete control over order and labels&lt;/li&gt;
&lt;li&gt;Can customize link text independently of file names&lt;/li&gt;
&lt;li&gt;Clear and explicit configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manual maintenance required for new files&lt;/li&gt;
&lt;li&gt;Easy to forget updating sidebar when adding content&lt;/li&gt;
&lt;li&gt;More verbose configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Sections with stable content that rarely changes, or when you need custom labels.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;strategy-2-filename-prefix-for-ordering&quot;&gt;Strategy 2: Filename Prefix for Ordering&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Force alphabetical ordering by prefixing your overview file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Rename the file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mv&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/overview.mdx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/00-overview.mdx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Then use autogenerate normally:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Markup Languages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;autogenerate: { directory: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;markup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Set the display title in frontmatter:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Overview&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# This shows in sidebar, not the filename&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintains autogenerate benefits&lt;/li&gt;
&lt;li&gt;Guaranteed ordering for important files&lt;/li&gt;
&lt;li&gt;Simple implementation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ugly filenames with prefixes&lt;/li&gt;
&lt;li&gt;Not semantically clean&lt;/li&gt;
&lt;li&gt;Prefix strategy needs to be documented for team&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Sections where you want mostly automatic management but need to pin a few key files.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;strategy-3-index-file-convention&quot;&gt;Strategy 3: Index File Convention&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Rename your overview to follow index file conventions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;mv&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/overview.mdx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src/content/docs/markup/index.mdx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semantic file naming&lt;/li&gt;
&lt;li&gt;Works with autogenerate in Starlight&lt;/li&gt;
&lt;li&gt;Follows web conventions (index files appear first)&lt;/li&gt;
&lt;li&gt;Clean, professional approach&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only works for one “special” file per directory&lt;/li&gt;
&lt;li&gt;Less obvious what the file contains from filename alone&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Sections where you have a clear introduction/overview page and the rest can be auto-managed.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;strategy-4-mixed-manual--autogenerate-theoretical&quot;&gt;Strategy 4: Mixed Manual + Autogenerate (Theoretical)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This would be ideal but isn’t currently supported by Starlight:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// This doesn&apos;t work in Starlight (yet)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Markup Languages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Overview&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, link: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/markup/overview/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;label: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Languages&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;autogenerate: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;directory: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;markup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;exclude: [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;overview.mdx&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it would be great:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Best of both worlds&lt;/li&gt;
&lt;li&gt;Explicit control for key pages&lt;/li&gt;
&lt;li&gt;Automatic inclusion of new content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Reality:&lt;/strong&gt; Starlight doesn’t support &lt;code dir=&quot;auto&quot;&gt;exclude&lt;/code&gt; in autogenerate, so this approach isn’t available.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;my-recommendation&quot;&gt;My Recommendation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After testing all approaches, here’s my decision framework:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-overviewlanding-pages&quot;&gt;For Overview/Landing Pages&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use the index.mdx convention&lt;/strong&gt; (Strategy 3). It’s semantic, clean, and works perfectly with autogenerate. This is what I implemented for the markup section.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-stable-sections&quot;&gt;For Stable Sections&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use manual items arrays&lt;/strong&gt; (Strategy 1) when you have a well-defined set of content that rarely changes and you want custom labels.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-growing-sections&quot;&gt;For Growing Sections&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use filename prefixes&lt;/strong&gt; (Strategy 2) when you’re actively adding content but need some ordering control. Accept the ugly filenames as a temporary trade-off.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-pure-auto-management&quot;&gt;For Pure Auto-Management&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Use straight autogenerate&lt;/strong&gt; when alphabetical ordering is acceptable and you prioritize ease of maintenance.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;real-world-impact&quot;&gt;Real-World Impact&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This might seem like a small detail, but navigation organization directly impacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;User experience&lt;/strong&gt;: Can readers find what they need?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content discoverability&lt;/strong&gt;: Do overview pages get seen?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintenance overhead&lt;/strong&gt;: How much work is adding new content?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team adoption&lt;/strong&gt;: Will other writers follow your patterns?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In my case, the index.mdx approach solved the immediate problem while keeping the configuration clean and maintainable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Documentation tooling continues to evolve. I’d love to see Starlight add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;exclude&lt;/code&gt; patterns for autogenerate&lt;/li&gt;
&lt;li&gt;Custom sorting functions&lt;/li&gt;
&lt;li&gt;Mixed manual/auto approaches&lt;/li&gt;
&lt;li&gt;Priority/weight-based ordering&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Until then, these strategies provide good workarounds for common navigation challenges.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This post documents a real configuration challenge from building DevPortals.tech. What sidebar organization strategies have worked for your documentation sites?&lt;/em&gt;&lt;/p&gt;</content:encoded><category>astro-starlight</category><category>documentation</category><category>configuration</category><category>navigation</category></item><item><title>From reST to MDX: Why Documentation Frameworks Are Evolving</title><link>https://devportals.tech/blog/rest-to-mdx/</link><guid isPermaLink="true">https://devportals.tech/blog/rest-to-mdx/</guid><pubDate>Sun, 10 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Documentation frameworks are not just technical choices — they reflect &lt;strong&gt;cultural shifts&lt;/strong&gt; in how we think about developer experience.&lt;/p&gt;
&lt;p&gt;For years, &lt;strong&gt;reStructuredText (reST)&lt;/strong&gt; powered serious technical documentation, especially in the Python and scientific communities.&lt;br&gt;
Today, &lt;strong&gt;Markdown and MDX&lt;/strong&gt; dominate modern developer portals. Why? Let’s explore this shift.&lt;/p&gt;

&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-legacy-of-restructuredtext-rest&quot;&gt;The Legacy of reStructuredText (reST)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Origins:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Created for the Python ecosystem.&lt;/li&gt;
&lt;li&gt;Strict, extensible, and great for generating reference documentation.&lt;/li&gt;
&lt;li&gt;Closely tied to tools like &lt;strong&gt;Sphinx&lt;/strong&gt; and &lt;strong&gt;ReadTheDocs&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rigid grammar and semantic markup → consistency across docs.&lt;/li&gt;
&lt;li&gt;Mature auto-documentation pipelines (e.g., autodoc with Sphinx).&lt;/li&gt;
&lt;li&gt;Widely used in academic and scientific projects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Challenges:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verbose and harder to learn.&lt;/li&gt;
&lt;li&gt;Contributions from non-experts can be intimidating.&lt;/li&gt;
&lt;li&gt;Limited support for modern interactive or component-driven docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;markdown-and-the-rise-of-docs-as-code&quot;&gt;Markdown and the Rise of Docs-as-Code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why Markdown won:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lightweight, readable, and easy to learn.&lt;/li&gt;
&lt;li&gt;GitHub popularized it as the default for READMEs.&lt;/li&gt;
&lt;li&gt;Perfect fit for docs-as-code workflows (PRs, reviews, version control).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tradeoffs:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Less expressive than reST.&lt;/li&gt;
&lt;li&gt;Needed extensions (like frontmatter, tables, or fenced code blocks) to catch up.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;mdx-markdown-meets-react&quot;&gt;MDX: Markdown Meets React&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt;&lt;br&gt;
MDX lets you write Markdown, but embed JSX (React components) directly into your docs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it matters for dev portals:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live code samples and sandboxes.&lt;/li&gt;
&lt;li&gt;Reusable UI components inside content.&lt;/li&gt;
&lt;li&gt;Docs can behave more like apps than static manuals.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;In the wild:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Docusaurus&lt;/strong&gt; (Meta’s framework).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt; with MDX plugins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro Starlight&lt;/strong&gt; (content-first with React/Vue/Svelte integrations).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;cultural-shifts-in-documentation&quot;&gt;Cultural Shifts in Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Role Evolution:&lt;/strong&gt; Technical writers → Documentation engineers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs Evolution:&lt;/strong&gt; Static reference → Dynamic experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaboration Evolution:&lt;/strong&gt; Gatekeeping → Community contribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MD/MDX directly supports these cultural shifts by making docs easier to contribute to &lt;em&gt;and&lt;/em&gt; more engaging for developers.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-rest-still-matters&quot;&gt;Why reST Still Matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Despite the momentum of MD/MDX, reST continues to thrive in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python projects&lt;/strong&gt; like Django, NumPy, and SciPy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scientific communities&lt;/strong&gt; that value its rigor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy pipelines&lt;/strong&gt; built around Sphinx extensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For many orgs, rewriting entire docsets is costly, so reST will persist in specialized contexts.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;looking-forward&quot;&gt;Looking Forward&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Momentum:&lt;/strong&gt; MD/MDX frameworks are the present and future of dev portals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Niche longevity:&lt;/strong&gt; reST will survive in Python and academia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your takeaway:&lt;/strong&gt; Framework choice isn’t only about syntax. It’s about workflows, interactivity, and alignment with developer expectations.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;reST gave us &lt;strong&gt;rigor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Markdown gave us &lt;strong&gt;accessibility&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;MDX gives us &lt;strong&gt;interactivity&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This evolution mirrors the shift in developer portals themselves:&lt;br&gt;
From &lt;strong&gt;manuals&lt;/strong&gt; → to &lt;strong&gt;platforms&lt;/strong&gt; → to &lt;strong&gt;experiences&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you’re building a modern developer portal, understanding this history helps you make &lt;strong&gt;smarter choices today&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;</content:encoded><category>documentation</category><category>mdx</category><category>reStructuredText</category><category>developer-portals</category><category>migration</category></item><item><title>Building DevPortals.tech: From Idea to Live Site</title><link>https://devportals.tech/blog/first-post-building-this-site/</link><guid isPermaLink="true">https://devportals.tech/blog/first-post-building-this-site/</guid><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a senior technical writer who’s spent years managing enterprise documentation platforms, I’ve seen the pain points that drive teams to migrate from proprietary solutions to open-source alternatives.&lt;/p&gt;
&lt;p&gt;This site chronicles that journey and provides practical guidance for teams making similar transitions.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;why-astro-starlight&quot;&gt;Why Astro Starlight?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;After evaluating multiple documentation frameworks, Starlight stood out for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Static site generation with minimal JavaScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;: Excellent TypeScript support and hot reload&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Easy to extend and customize&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern&lt;/strong&gt;: Built for the current web ecosystem&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’m building additional demos in Next.js and Docusaurus to provide comprehensive framework comparisons for teams evaluating their options.&lt;/p&gt;
&lt;p&gt;Stay tuned for more practical migration strategies and platform evaluations!&lt;/p&gt;</content:encoded><category>astro-starlight</category><category>developer-portals</category><category>migration</category></item></channel></rss>