<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>website-migration | Chris Titus Tech</title><description>Recent content from Chris Titus Tech</description><link>https://christitus.com/tags/website-migration/</link><language>en-US</language><item><title>I Rebuilt My Website in Astro - Was It Worth It?</title><link>https://christitus.com/i-rebuilt-my-website-in-astro-was-it-worth-it/</link><guid isPermaLink="true">https://christitus.com/i-rebuilt-my-website-in-astro-was-it-worth-it/</guid><description>&lt;p&gt;Four years ago, I built this website with Hugo. In August 2026, I deleted the
Hugo build, replaced it with Astro, and moved the entire production site without
changing the URLs people already use.&lt;/p&gt;
&lt;p&gt;Hugo was not broken. That is what made this migration interesting. The site had
simply grown from a blog into a publication, video archive, search engine,
download hub, newsletter funnel, and a pile of old links that absolutely could
not disappear. Rebuilding the easy 80 percent would have taken an afternoon.
Preserving the last 20 percent was the real project.&lt;/p&gt;

&lt;p&gt;So, was moving from Hugo to Astro worth it? Yes, but not for the reason most
framework comparisons give you.&lt;/p&gt;
&lt;h2&gt;Why Replace Something That Already Worked?&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&quot;/how-i-setup-my-website/&quot;&gt;original website setup&lt;/a&gt; was intentionally simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Markdown -&amp;gt; Hugo -&amp;gt; GitHub -&amp;gt; Cloudflare Pages
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That workflow served me well for years. Hugo is fast, dependable, and still one
of the best static site generators available. If you have a documentation site
or a straightforward blog, I am not going to tell you to rewrite it because a
JavaScript framework has a shinier home page.&lt;/p&gt;
&lt;p&gt;But christitus.com stopped being a straightforward blog.&lt;/p&gt;
&lt;p&gt;It now has hundreds of articles, category and tag archives, livestreams with
synchronized chat replays, local search, downloads, newsletter signup, feeds,
comments, ads, analytics, old redirects, and years of Markdown written against
Hugo behavior. The Hugo version worked, but every custom feature added another
template, partial, shortcode, SCSS rule, or vendor script to a stack that was
getting harder to reason about as one system.&lt;/p&gt;
&lt;p&gt;I wanted three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A frontend I could understand from the page down to the browser behavior.&lt;/li&gt;
&lt;li&gt;Tests that proved the old links and content still worked.&lt;/li&gt;
&lt;li&gt;A foundation that was easier to change without dragging a client framework
into every page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Astro fit that job. It gives me components and TypeScript at build time, ships
static HTML by default, and lets browser JavaScript stay small and local. There
is no React application hiding underneath this site. It is still a static
website deployed by Cloudflare Pages.&lt;/p&gt;
&lt;p&gt;The new publishing path is almost boring:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Markdown -&amp;gt; Astro -&amp;gt; GitHub -&amp;gt; Cloudflare Pages
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generator changed. The static-site philosophy did not.&lt;/p&gt;
&lt;h2&gt;The Website Was the Contract&lt;/h2&gt;
&lt;p&gt;The biggest mistake in any rebuild is treating the source code as the product.
It is not. The product is everything people and machines can already reach.&lt;/p&gt;
&lt;p&gt;Before changing the build, I captured Hugo&apos;s production output as a baseline.
That clean build contained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2,156 generated files&lt;/li&gt;
&lt;li&gt;813 Hugo pages&lt;/li&gt;
&lt;li&gt;125 paginator pages&lt;/li&gt;
&lt;li&gt;244 aliases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That included much more than article HTML. It included case-sensitive URLs,
category pages, tag pages, RSS feeds, sitemap entries, social metadata, search
records, redirects, images, downloads, and weird historical edge cases nobody
remembers until Google lands on one.&lt;/p&gt;
&lt;p&gt;One tracked image even had to remain byte-for-byte identical at its old public
path. Some old posts had empty categories. Others used historical capitalization
that new articles are no longer allowed to use. The archive contained Hugo
shortcodes, raw HTML, date-only front matter, timestamps with offsets, and posts
where an omitted &lt;code&gt;draft&lt;/code&gt; field meant published.&lt;/p&gt;
&lt;p&gt;That mess is not bad data. It is a public compatibility contract accumulated
over years.&lt;/p&gt;
&lt;p&gt;Instead of cleaning up the archive and hoping for the best, the migration taught
Astro how to read it. New content gets stricter rules. Old published content
keeps working.&lt;/p&gt;
&lt;h2&gt;Rebuilding Hugo Behavior Without Hugo&lt;/h2&gt;
&lt;p&gt;Astro content collections now load the existing Markdown with typed metadata.
The production build excludes drafts and future posts, but a separate local
preview command lets me see them while writing. A repository-owned post
scaffolder replaced Hugo archetypes and catches URL collisions before it writes
a file.&lt;/p&gt;
&lt;p&gt;The compatibility renderer handles the Hugo shortcodes that are actually in the
archive, including YouTube, notices, tables, X posts, and Shopify embeds. An
unknown active shortcode fails the build instead of quietly publishing broken
template syntax. Literal shortcode examples inside code blocks remain article
content.&lt;/p&gt;
&lt;p&gt;This is the unglamorous part of a successful migration. I did not run a giant
search-and-replace across old articles. I built a boundary around the history.&lt;/p&gt;
&lt;p&gt;Routes received the same treatment. The Astro build generates and checks the
homepage, posts, pagination, taxonomies, search index, feeds, sitemap,
livestreams, player pages, legal pages, and redirects. The validator rejects
collisions between content, static assets, redirects, feeds, and generated
routes.&lt;/p&gt;
&lt;p&gt;By the end, the new production artifact had a deterministic 1,183-route
compatibility contract and 312 searchable entries. That matters more to me than
whether one generator won a synthetic build-speed contest.&lt;/p&gt;
&lt;h2&gt;I Used the Migration to Delete Frontend Baggage&lt;/h2&gt;
&lt;p&gt;The Hugo site had grown a traditional frontend layer over time: Bootstrap,
Font Awesome bundles, Fuse, Mark.js, Shuffle, theme scripts, Shopify code, SCSS
partials, and many custom layout files.&lt;/p&gt;
&lt;p&gt;The Astro conversion removed that layer and replaced it with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reusable Astro components&lt;/li&gt;
&lt;li&gt;one custom CSS token system&lt;/li&gt;
&lt;li&gt;vanilla TypeScript for interactive behavior&lt;/li&gt;
&lt;li&gt;self-hosted fonts&lt;/li&gt;
&lt;li&gt;static pages with progressive enhancement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result supports dark and light themes, keyboard navigation, local search,
livestream browsing, synchronized chat, lazy comments, lazy ads, and an
intent-driven store integration. The important part is that a visitor does not
download a full client UI framework just to read an article.&lt;/p&gt;
&lt;p&gt;Third-party code also stopped being a requirement for the page to function.
Search uses a generated local index. The download page retains a direct store
link if Shopify is blocked. The livestream player works without chat. Ads,
comments, video APIs, and newsletter extras load only when they are needed.&lt;/p&gt;
&lt;p&gt;That is the web I want: the content works first, and the integrations enhance it
afterward.&lt;/p&gt;
&lt;h2&gt;Performance Was a Gate, Not a Vibe&lt;/h2&gt;
&lt;p&gt;Framework marketing loves a perfect Lighthouse screenshot. Real performance
work is less glamorous.&lt;/p&gt;
&lt;p&gt;The migration added a pinned mobile Lighthouse profile that runs three times on
the homepage, an article, a category page, and the livestream archive. During
the final review, those pages scored between 99 and 100 for performance, 100 for
accessibility, 100 for SEO, and 96 for best practices in the local fallback
environment. Median Largest Contentful Paint ranged from 1.58 to 1.95 seconds,
with zero layout shift on those runs.&lt;/p&gt;
&lt;p&gt;Those results did not appear automatically because the project used Astro. The
commit history shows the real work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stabilize Lighthouse performance&lt;/li&gt;
&lt;li&gt;stabilize mobile listing performance&lt;/li&gt;
&lt;li&gt;reduce webfont render delay&lt;/li&gt;
&lt;li&gt;inline the site stylesheet&lt;/li&gt;
&lt;li&gt;fix the remaining migration review findings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And after launch, I still changed the layout spacing, added missing support
paths to search and downloads, adjusted the Lighthouse threshold to account for
real run-to-run variance, and fixed the article table of contents so it actually
stays pinned while scrolling.&lt;/p&gt;
&lt;p&gt;That last part is important. A green migration does not mean the design is done.
It means the new foundation is safe enough to improve.&lt;/p&gt;
&lt;h2&gt;The Test Suite Became the Real Feature&lt;/h2&gt;
&lt;p&gt;The final Astro build is checked by much more than &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The repository validates formatting, Markdown, dependency policy, Astro types,
unit tests, deterministic output, the route contract, and the production build.
Playwright then exercises the site in Chromium, Firefox, mobile Chromium, and
WebKit. Browser tests cover the homepage, articles, taxonomy pages, search,
downloads, newsletter signup, livestreams, chat and no-chat player states,
redirects, the theme switcher, mobile navigation, keyboard access, and the 404
page.&lt;/p&gt;
&lt;p&gt;Accessibility checks found a real contrast failure during development. It was
fixed before the final run. That is exactly what I want from automation: not a
badge, but a specific problem found before users have to report it.&lt;/p&gt;
&lt;p&gt;The full clean validation produced a deterministic 1,285-page Astro build, ran
61 unit and workflow tests, exercised 66 applicable browser tests, and completed
12 Lighthouse runs. WebKit ran in its supported Playwright container because
the fallback binary did not match my Fedora host libraries.&lt;/p&gt;
&lt;p&gt;This is more ceremony than a personal blog needs. It is not more ceremony than
a production publication with years of inbound links needs.&lt;/p&gt;
&lt;h2&gt;The Hidden Migration Was GitHub Automation&lt;/h2&gt;
&lt;p&gt;The visible redesign was only half the job.&lt;/p&gt;
&lt;p&gt;Livestream and chat data had been updated by separate scheduled workflows that
pushed generated data. The migration consolidated that pipeline and added
guards around the exact commit being tested. Candidate automation changes are
limited to the generated data paths, checks attach to the exact validated SHA,
and interruption handling can reconcile the branch and pull request instead of
blindly starting over.&lt;/p&gt;
&lt;p&gt;Nobody watching the video will look at the homepage and say, &quot;Nice immutable
commit validation.&quot; That is fine. Infrastructure is successful when it makes
the boring failures boring to recover from.&lt;/p&gt;
&lt;p&gt;The same rule applied to deployment. The old Hugo output, Cloudflare settings,
redirect behavior, and repository state were captured before cutover. A rewrite
without a rollback plan is not confidence. It is gambling with extra steps.&lt;/p&gt;
&lt;h2&gt;What the Commit History Actually Shows&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/ChrisTitusTech/website/pull/273&quot;&gt;main pull request&lt;/a&gt;
contained 23 commits before it was merged. The sequence is more useful than the
final screenshot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;write the specification and roadmap
  -&amp;gt; close planning and cutover gaps
  -&amp;gt; capture the Hugo baseline
  -&amp;gt; build the Astro content foundation
  -&amp;gt; build the editorial experience
  -&amp;gt; finish infrastructure and automation
  -&amp;gt; review, fix, measure, and repeat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The merged change touched 175 files, added 49,426 lines, and removed 7,408. A
large part of those additions is recorded baseline and lockfile data, not
49,000 lines of shiny new application code. The deletions are more revealing:
Hugo configuration, templates, archetypes, old pipeline assets, vendor scripts,
and SCSS all left the production path.&lt;/p&gt;
&lt;p&gt;The project did not begin with &lt;code&gt;npm install astro&lt;/code&gt;. It began with nine planning
and review commits that defined what could not break. Only then did the history
move through baseline capture, content, design, infrastructure, performance,
and final review.&lt;/p&gt;
&lt;p&gt;That order is why this worked.&lt;/p&gt;
&lt;h2&gt;What Got Better&lt;/h2&gt;
&lt;p&gt;The obvious improvement is maintainability. A page is now composed from Astro
components instead of behavior being spread across Hugo templates, partials,
shortcodes, and global vendor scripts. TypeScript catches mistakes earlier, and
the browser code lives beside the feature that needs it.&lt;/p&gt;
&lt;p&gt;The editorial workflow is also safer. A new post command creates consistent
front matter, uses the correct date and URL shape, requires a valid category,
defaults to a draft, and refuses to overwrite or collide with an existing
route.&lt;/p&gt;
&lt;p&gt;Most importantly, I can make changes with evidence. If I touch search, routes,
metadata, a feed, or the article layout, the repository has a focused way to
prove whether I broke it.&lt;/p&gt;
&lt;h2&gt;What Astro Did Not Magically Fix&lt;/h2&gt;
&lt;p&gt;Astro did not make historical content clean. We had to model the exceptions.
It did not make third-party integrations reliable. We had to build fallbacks.
It did not guarantee accessibility or speed. We had to test both. It did not
make deployment safe. We had to capture the old state and design a rollback.&lt;/p&gt;
&lt;p&gt;It also moved the toolchain from one Hugo binary to Node, npm, a lockfile, Astro,
TypeScript, Vitest, Playwright, Lighthouse, and supporting packages. That is a
real dependency cost. The new stack is easier for me to extend, but it is not
smaller in every possible sense.&lt;/p&gt;
&lt;p&gt;If your Hugo site is fast, easy to maintain, and does everything you need, keep
it. Rewrites are expensive. A framework switch without a concrete maintenance
problem is just a new way to own the same website.&lt;/p&gt;
&lt;h2&gt;Was It Worth It?&lt;/h2&gt;
&lt;p&gt;For this site, yes.&lt;/p&gt;
&lt;p&gt;Not because Astro defeated Hugo. Hugo did its job for years, and it gave us the
stable static foundation that made a careful migration possible.&lt;/p&gt;
&lt;p&gt;The move was worth it because the website now matches how I build software:
plain-text content, static output, small client-side code, explicit contracts,
repeatable tests, independent review, and a rollback when reality ignores the
plan.&lt;/p&gt;
&lt;p&gt;The final lesson is not &quot;use Astro.&quot; It is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Never start a website rewrite by asking how to rebuild the homepage. Start by
asking what the old website is already promising.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Capture those promises. Turn them into tests. Then change the engine.&lt;/p&gt;
&lt;p&gt;That is how I replaced Hugo without replacing the website.&lt;/p&gt;
</description><pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Four years ago, I built this website with Hugo. In August 2026, I deleted the
Hugo build, replaced it with Astro, and moved the entire production site without
changing the URLs people already use.&lt;/p&gt;
&lt;p&gt;Hugo was not broken. That is what made this migration interesting. The site had
simply grown from a blog into a publication, video archive, search engine,
download hub, newsletter funnel, and a pile of old links that absolutely could
not disappear. Rebuilding the easy 80 percent would have taken an afternoon.
Preserving the last 20 percent was the real project.&lt;/p&gt;

&lt;p&gt;So, was moving from Hugo to Astro worth it? Yes, but not for the reason most
framework comparisons give you.&lt;/p&gt;
&lt;h2&gt;Why Replace Something That Already Worked?&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&quot;/how-i-setup-my-website/&quot;&gt;original website setup&lt;/a&gt; was intentionally simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Markdown -&amp;gt; Hugo -&amp;gt; GitHub -&amp;gt; Cloudflare Pages
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That workflow served me well for years. Hugo is fast, dependable, and still one
of the best static site generators available. If you have a documentation site
or a straightforward blog, I am not going to tell you to rewrite it because a
JavaScript framework has a shinier home page.&lt;/p&gt;
&lt;p&gt;But christitus.com stopped being a straightforward blog.&lt;/p&gt;
&lt;p&gt;It now has hundreds of articles, category and tag archives, livestreams with
synchronized chat replays, local search, downloads, newsletter signup, feeds,
comments, ads, analytics, old redirects, and years of Markdown written against
Hugo behavior. The Hugo version worked, but every custom feature added another
template, partial, shortcode, SCSS rule, or vendor script to a stack that was
getting harder to reason about as one system.&lt;/p&gt;
&lt;p&gt;I wanted three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A frontend I could understand from the page down to the browser behavior.&lt;/li&gt;
&lt;li&gt;Tests that proved the old links and content still worked.&lt;/li&gt;
&lt;li&gt;A foundation that was easier to change without dragging a client framework
into every page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Astro fit that job. It gives me components and TypeScript at build time, ships
static HTML by default, and lets browser JavaScript stay small and local. There
is no React application hiding underneath this site. It is still a static
website deployed by Cloudflare Pages.&lt;/p&gt;
&lt;p&gt;The new publishing path is almost boring:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Markdown -&amp;gt; Astro -&amp;gt; GitHub -&amp;gt; Cloudflare Pages
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generator changed. The static-site philosophy did not.&lt;/p&gt;
&lt;h2&gt;The Website Was the Contract&lt;/h2&gt;
&lt;p&gt;The biggest mistake in any rebuild is treating the source code as the product.
It is not. The product is everything people and machines can already reach.&lt;/p&gt;
&lt;p&gt;Before changing the build, I captured Hugo&apos;s production output as a baseline.
That clean build contained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2,156 generated files&lt;/li&gt;
&lt;li&gt;813 Hugo pages&lt;/li&gt;
&lt;li&gt;125 paginator pages&lt;/li&gt;
&lt;li&gt;244 aliases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That included much more than article HTML. It included case-sensitive URLs,
category pages, tag pages, RSS feeds, sitemap entries, social metadata, search
records, redirects, images, downloads, and weird historical edge cases nobody
remembers until Google lands on one.&lt;/p&gt;
&lt;p&gt;One tracked image even had to remain byte-for-byte identical at its old public
path. Some old posts had empty categories. Others used historical capitalization
that new articles are no longer allowed to use. The archive contained Hugo
shortcodes, raw HTML, date-only front matter, timestamps with offsets, and posts
where an omitted &lt;code&gt;draft&lt;/code&gt; field meant published.&lt;/p&gt;
&lt;p&gt;That mess is not bad data. It is a public compatibility contract accumulated
over years.&lt;/p&gt;
&lt;p&gt;Instead of cleaning up the archive and hoping for the best, the migration taught
Astro how to read it. New content gets stricter rules. Old published content
keeps working.&lt;/p&gt;
&lt;h2&gt;Rebuilding Hugo Behavior Without Hugo&lt;/h2&gt;
&lt;p&gt;Astro content collections now load the existing Markdown with typed metadata.
The production build excludes drafts and future posts, but a separate local
preview command lets me see them while writing. A repository-owned post
scaffolder replaced Hugo archetypes and catches URL collisions before it writes
a file.&lt;/p&gt;
&lt;p&gt;The compatibility renderer handles the Hugo shortcodes that are actually in the
archive, including YouTube, notices, tables, X posts, and Shopify embeds. An
unknown active shortcode fails the build instead of quietly publishing broken
template syntax. Literal shortcode examples inside code blocks remain article
content.&lt;/p&gt;
&lt;p&gt;This is the unglamorous part of a successful migration. I did not run a giant
search-and-replace across old articles. I built a boundary around the history.&lt;/p&gt;
&lt;p&gt;Routes received the same treatment. The Astro build generates and checks the
homepage, posts, pagination, taxonomies, search index, feeds, sitemap,
livestreams, player pages, legal pages, and redirects. The validator rejects
collisions between content, static assets, redirects, feeds, and generated
routes.&lt;/p&gt;
&lt;p&gt;By the end, the new production artifact had a deterministic 1,183-route
compatibility contract and 312 searchable entries. That matters more to me than
whether one generator won a synthetic build-speed contest.&lt;/p&gt;
&lt;h2&gt;I Used the Migration to Delete Frontend Baggage&lt;/h2&gt;
&lt;p&gt;The Hugo site had grown a traditional frontend layer over time: Bootstrap,
Font Awesome bundles, Fuse, Mark.js, Shuffle, theme scripts, Shopify code, SCSS
partials, and many custom layout files.&lt;/p&gt;
&lt;p&gt;The Astro conversion removed that layer and replaced it with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reusable Astro components&lt;/li&gt;
&lt;li&gt;one custom CSS token system&lt;/li&gt;
&lt;li&gt;vanilla TypeScript for interactive behavior&lt;/li&gt;
&lt;li&gt;self-hosted fonts&lt;/li&gt;
&lt;li&gt;static pages with progressive enhancement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result supports dark and light themes, keyboard navigation, local search,
livestream browsing, synchronized chat, lazy comments, lazy ads, and an
intent-driven store integration. The important part is that a visitor does not
download a full client UI framework just to read an article.&lt;/p&gt;
&lt;p&gt;Third-party code also stopped being a requirement for the page to function.
Search uses a generated local index. The download page retains a direct store
link if Shopify is blocked. The livestream player works without chat. Ads,
comments, video APIs, and newsletter extras load only when they are needed.&lt;/p&gt;
&lt;p&gt;That is the web I want: the content works first, and the integrations enhance it
afterward.&lt;/p&gt;
&lt;h2&gt;Performance Was a Gate, Not a Vibe&lt;/h2&gt;
&lt;p&gt;Framework marketing loves a perfect Lighthouse screenshot. Real performance
work is less glamorous.&lt;/p&gt;
&lt;p&gt;The migration added a pinned mobile Lighthouse profile that runs three times on
the homepage, an article, a category page, and the livestream archive. During
the final review, those pages scored between 99 and 100 for performance, 100 for
accessibility, 100 for SEO, and 96 for best practices in the local fallback
environment. Median Largest Contentful Paint ranged from 1.58 to 1.95 seconds,
with zero layout shift on those runs.&lt;/p&gt;
&lt;p&gt;Those results did not appear automatically because the project used Astro. The
commit history shows the real work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stabilize Lighthouse performance&lt;/li&gt;
&lt;li&gt;stabilize mobile listing performance&lt;/li&gt;
&lt;li&gt;reduce webfont render delay&lt;/li&gt;
&lt;li&gt;inline the site stylesheet&lt;/li&gt;
&lt;li&gt;fix the remaining migration review findings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And after launch, I still changed the layout spacing, added missing support
paths to search and downloads, adjusted the Lighthouse threshold to account for
real run-to-run variance, and fixed the article table of contents so it actually
stays pinned while scrolling.&lt;/p&gt;
&lt;p&gt;That last part is important. A green migration does not mean the design is done.
It means the new foundation is safe enough to improve.&lt;/p&gt;
&lt;h2&gt;The Test Suite Became the Real Feature&lt;/h2&gt;
&lt;p&gt;The final Astro build is checked by much more than &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The repository validates formatting, Markdown, dependency policy, Astro types,
unit tests, deterministic output, the route contract, and the production build.
Playwright then exercises the site in Chromium, Firefox, mobile Chromium, and
WebKit. Browser tests cover the homepage, articles, taxonomy pages, search,
downloads, newsletter signup, livestreams, chat and no-chat player states,
redirects, the theme switcher, mobile navigation, keyboard access, and the 404
page.&lt;/p&gt;
&lt;p&gt;Accessibility checks found a real contrast failure during development. It was
fixed before the final run. That is exactly what I want from automation: not a
badge, but a specific problem found before users have to report it.&lt;/p&gt;
&lt;p&gt;The full clean validation produced a deterministic 1,285-page Astro build, ran
61 unit and workflow tests, exercised 66 applicable browser tests, and completed
12 Lighthouse runs. WebKit ran in its supported Playwright container because
the fallback binary did not match my Fedora host libraries.&lt;/p&gt;
&lt;p&gt;This is more ceremony than a personal blog needs. It is not more ceremony than
a production publication with years of inbound links needs.&lt;/p&gt;
&lt;h2&gt;The Hidden Migration Was GitHub Automation&lt;/h2&gt;
&lt;p&gt;The visible redesign was only half the job.&lt;/p&gt;
&lt;p&gt;Livestream and chat data had been updated by separate scheduled workflows that
pushed generated data. The migration consolidated that pipeline and added
guards around the exact commit being tested. Candidate automation changes are
limited to the generated data paths, checks attach to the exact validated SHA,
and interruption handling can reconcile the branch and pull request instead of
blindly starting over.&lt;/p&gt;
&lt;p&gt;Nobody watching the video will look at the homepage and say, &quot;Nice immutable
commit validation.&quot; That is fine. Infrastructure is successful when it makes
the boring failures boring to recover from.&lt;/p&gt;
&lt;p&gt;The same rule applied to deployment. The old Hugo output, Cloudflare settings,
redirect behavior, and repository state were captured before cutover. A rewrite
without a rollback plan is not confidence. It is gambling with extra steps.&lt;/p&gt;
&lt;h2&gt;What the Commit History Actually Shows&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/ChrisTitusTech/website/pull/273&quot;&gt;main pull request&lt;/a&gt;
contained 23 commits before it was merged. The sequence is more useful than the
final screenshot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;write the specification and roadmap
  -&amp;gt; close planning and cutover gaps
  -&amp;gt; capture the Hugo baseline
  -&amp;gt; build the Astro content foundation
  -&amp;gt; build the editorial experience
  -&amp;gt; finish infrastructure and automation
  -&amp;gt; review, fix, measure, and repeat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The merged change touched 175 files, added 49,426 lines, and removed 7,408. A
large part of those additions is recorded baseline and lockfile data, not
49,000 lines of shiny new application code. The deletions are more revealing:
Hugo configuration, templates, archetypes, old pipeline assets, vendor scripts,
and SCSS all left the production path.&lt;/p&gt;
&lt;p&gt;The project did not begin with &lt;code&gt;npm install astro&lt;/code&gt;. It began with nine planning
and review commits that defined what could not break. Only then did the history
move through baseline capture, content, design, infrastructure, performance,
and final review.&lt;/p&gt;
&lt;p&gt;That order is why this worked.&lt;/p&gt;
&lt;h2&gt;What Got Better&lt;/h2&gt;
&lt;p&gt;The obvious improvement is maintainability. A page is now composed from Astro
components instead of behavior being spread across Hugo templates, partials,
shortcodes, and global vendor scripts. TypeScript catches mistakes earlier, and
the browser code lives beside the feature that needs it.&lt;/p&gt;
&lt;p&gt;The editorial workflow is also safer. A new post command creates consistent
front matter, uses the correct date and URL shape, requires a valid category,
defaults to a draft, and refuses to overwrite or collide with an existing
route.&lt;/p&gt;
&lt;p&gt;Most importantly, I can make changes with evidence. If I touch search, routes,
metadata, a feed, or the article layout, the repository has a focused way to
prove whether I broke it.&lt;/p&gt;
&lt;h2&gt;What Astro Did Not Magically Fix&lt;/h2&gt;
&lt;p&gt;Astro did not make historical content clean. We had to model the exceptions.
It did not make third-party integrations reliable. We had to build fallbacks.
It did not guarantee accessibility or speed. We had to test both. It did not
make deployment safe. We had to capture the old state and design a rollback.&lt;/p&gt;
&lt;p&gt;It also moved the toolchain from one Hugo binary to Node, npm, a lockfile, Astro,
TypeScript, Vitest, Playwright, Lighthouse, and supporting packages. That is a
real dependency cost. The new stack is easier for me to extend, but it is not
smaller in every possible sense.&lt;/p&gt;
&lt;p&gt;If your Hugo site is fast, easy to maintain, and does everything you need, keep
it. Rewrites are expensive. A framework switch without a concrete maintenance
problem is just a new way to own the same website.&lt;/p&gt;
&lt;h2&gt;Was It Worth It?&lt;/h2&gt;
&lt;p&gt;For this site, yes.&lt;/p&gt;
&lt;p&gt;Not because Astro defeated Hugo. Hugo did its job for years, and it gave us the
stable static foundation that made a careful migration possible.&lt;/p&gt;
&lt;p&gt;The move was worth it because the website now matches how I build software:
plain-text content, static output, small client-side code, explicit contracts,
repeatable tests, independent review, and a rollback when reality ignores the
plan.&lt;/p&gt;
&lt;p&gt;The final lesson is not &quot;use Astro.&quot; It is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Never start a website rewrite by asking how to rebuild the homepage. Start by
asking what the old website is already promising.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Capture those promises. Turn them into tests. Then change the engine.&lt;/p&gt;
&lt;p&gt;That is how I replaced Hugo without replacing the website.&lt;/p&gt;
</content:encoded></item></channel></rss>