Skip to content

Site Migration

1 post with the tag “Site Migration”

Docusaurus 3.x to Astro 5.x Migration in Practice: Using Islands Architecture to Improve Both Performance and Build Speed

From Docusaurus 3.x to Astro 5.x: A Retrospective on the HagiCode Site Migration

Section titled “From Docusaurus 3.x to Astro 5.x: A Retrospective on the HagiCode Site Migration”

This article looks back on our full migration of the HagiCode official website from Docusaurus 3.x to Astro 5.x. We will take a deep dive into how Astro’s Islands Architecture helped us solve performance bottlenecks while preserving our existing React component assets, delivering improvements in both build speed and loading performance.

In January 2026, we performed a “heart transplant” on the HagiCode official site by fully migrating its core framework from Docusaurus 3.x to Astro 5.x. This was not an impulsive rewrite, but a carefully considered technical decision.

Before the migration, our site was functionally complete, but it had begun to show some classic “luxury problems”: bloated build artifacts, excessive JavaScript payloads, and less-than-ideal page load speed on complex documentation pages. As an AI coding assistant project, HagiCode needs frequent documentation and feature updates, so build efficiency directly affects release speed. At the same time, we wanted the site to be more search-engine-friendly (SEO) so more developers could discover the project.

To solve these pain points, we made a bold decision: rebuild the entire system on Astro. The impact of that decision may be even bigger than you expect. I will get into the details shortly.

The site migration approach shared in this article comes from our hands-on experience in the HagiCode project.

HagiCode is an AI coding assistant focused on improving development efficiency. We care not only about iterating on core features, but also about the developer experience. This site refactor was also meant to give users the fastest possible experience when browsing our docs and official website.

Why Leave the Mature Docusaurus Ecosystem?

Section titled “Why Leave the Mature Docusaurus Ecosystem?”

Within the React ecosystem, Docusaurus has long been the “standard answer” for documentation sites. It works out of the box, offers a rich plugin ecosystem, and has an active community. But as HagiCode gained more features, we also felt its limitations:

  1. Performance bottlenecks: Docusaurus is fundamentally a React SPA (single-page application). Even if you only write static pages, the client still needs to load the React runtime and hydrate the page, which is unnecessarily heavy for simple docs pages.
  2. Large asset size: Even when a page contains very little content, the bundled JS size stays relatively fixed. That is not ideal for mobile users or poor network conditions.
  3. Limited flexibility: Although it is extensible, we wanted more low-level control over the build pipeline.

Astro arrived at exactly the right time to solve these problems. It introduced a new “Islands Architecture”: by default, Astro generates static HTML with zero JavaScript, and only components that require interactivity are “activated” and load JS. That means most of our site becomes pure HTML and loads extremely fast.

Core Migration Strategy: A Smooth Architectural Transition

Section titled “Core Migration Strategy: A Smooth Architectural Transition”

Migration was not just copy and paste. It required a shift in mindset. We moved from Docusaurus’s “all React” model to Astro’s “Core + Islands” model.

First, we had to move from docusaurus.config.ts to astro.config.mjs. This was not just a file rename, but a rewrite of routing and build logic.

In Docusaurus, everything is a plugin. In Astro, everything is an integration. We needed to redefine the site’s base path, build output mode (static vs SSR), and asset optimization strategy.

Before migration:

docusaurus.config.ts
export default {
title: 'HagiCode',
url: 'https://hagicode.com',
baseUrl: '/',
// ... more configuration
};

After migration:

astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
integrations: [react()],
site: 'https://hagicode.com',
base: '/',
// Optimization settings for static assets
build: {
inlineStylesheets: 'auto',
},
});

2. What to Keep and What to Refactor in React Components

Section titled “2. What to Keep and What to Refactor in React Components”

This was the most painful part of the migration. Our existing site had many React components, such as Tabs, code highlighting, feedback buttons, and more. Throwing them away would be wasteful, but keeping everything would make the JavaScript payload too heavy.

HagiCode adopted a progressive hydration strategy:

  • Pure static components: For presentational content such as headers, footers, and plain text documentation, we rewrote them as Astro components (.astro files) and rendered them directly to HTML at build time.
  • Interactive islands: For components that must remain interactive, such as theme switchers, tab switching, and code block copy buttons, we kept the React implementation and added client:load or client:visible directives.

For example, our commonly used Tabs component in the documentation:

src/components/Tabs.jsx
import { useState } from 'react';
import './Tabs.css'; // Import styles
export default function Tabs({ items }) {
const [activeIndex, setActiveIndex] = useState(0);
// ... state logic
return (
<div className="tabs-wrapper">
{/* Rendering logic */}
</div>
);
}

When used in Markdown, we explicitly tell Astro: “This component needs JS.”

src/content/docs/example.mdx
import Tabs from '../../components/Tabs.jsx';
<!-- Load JS only when the component enters the viewport -->
<Tabs client:visible items={...} />

This way, interactive components outside the viewport do not compete for bandwidth, which greatly improves first-screen loading speed.

3. Adapting the Styling System: From CSS Modules to Scoped CSS

Section titled “3. Adapting the Styling System: From CSS Modules to Scoped CSS”

Docusaurus supports CSS Modules by default, while Astro encourages Scoped CSS through the <style> tag. The core idea behind both is style isolation, but the syntax is different.

During the HagiCode migration, we converted most complex CSS Modules into Astro’s scoped styles. This actually turned out to be a good thing, because in .astro files the styles and templates live in the same file, which makes maintenance more intuitive.

Before refactoring:

Tabs.module.css
.wrapper { background: var(--ifm-background-color); }

After refactoring (Astro Scoped):

Tabs.astro
<div class="tabs-wrapper">
<slot />
</div>
<style>
.tabs-wrapper {
/* Use CSS variables directly to adapt to the theme */
background: var(--bg-color);
padding: 1rem;
}
</style>

At the same time, we unified the global CSS variable system and used Astro’s environment-aware capabilities to ensure dark mode switches smoothly across pages.

Pitfalls We Hit in Practice and How We Solved Them

Section titled “Pitfalls We Hit in Practice and How We Solved Them”

During the actual HagiCode migration, we ran into quite a few issues. Here are several of the most typical ones.

1. Path and Environment Variable Pain Points

Section titled “1. Path and Environment Variable Pain Points”

HagiCode supports subpath deployment, such as deployment under a GitHub Pages subdirectory. In Docusaurus, baseUrl is handled automatically. In Astro, however, we need to be more careful when handling image links and API requests.

We introduced an environment variable mechanism to manage this consistently:

// Handle paths in the build script
const getBasePath = () => import.meta.env.VITE_SITE_BASE || '/';

Be sure not to hardcode paths beginning with / in your code. In development versus production, or after configuring a base path, doing so can cause 404s for assets.

Our old site had some Node.js scripts used for tasks such as automatically fetching Metrics data and updating the sitemap, and they were written in CommonJS (require). Astro and modern build tools have fully embraced ES Modules (import/export).

If you also have similar scripts, remember to refactor them all to ES Modules. That is the direction the ecosystem is moving, and the sooner you make the change, the less trouble you will have later.

// Old way
const fs = require('fs');
// New way
import fs from 'fs';

Search engines have already indexed HagiCode’s old Docusaurus pages. If you switch directly to Astro and the URL structure changes, you may end up with a large number of 404s and a major drop in search ranking.

We configured redirect rules in Astro:

astro.config.mjs
export default defineConfig({
redirects: {
'/docs/old-path': '/docs/new-path',
// Map old links to new links in bulk
}
});

Or you can handle this at the server configuration layer. Make sure old links can be 301 redirected to the new addresses, because this is critical for SEO.

For HagiCode, migrating from Docusaurus to Astro was not just a framework upgrade. It was also a practical implementation of a “performance first” philosophy.

What we gained:

  • Outstanding Lighthouse scores: After the migration, the HagiCode site’s performance score easily approached a perfect score.
  • Faster build speed: Astro’s incremental build capabilities cut the release time for documentation updates in half.
  • Preserved flexibility: With Islands Architecture, we did not sacrifice any interactive features and could still use React where needed.

If you are also maintaining a documentation-oriented site and are struggling with bundle size or load speed, Astro is well worth trying. Although the migration process does require some surgery, such as renaming PCode to HagiCode and moving components over one by one, the silky-smooth user experience you get in return makes it absolutely worthwhile.

The build system shared in this article is the exact approach we developed through real trial and error while building HagiCode. If you find this approach valuable, that says something about our engineering strength, and HagiCode itself is probably worth a closer look too.

If this article helped you, feel free to give us a Star on GitHub. Public beta has already begun!


Thank you for reading. If you found this article useful, click the like button below 👍 so more people can discover it.

This content was created with AI-assisted collaboration, reviewed by me, and reflects my own views and position.