Skip to content

Docusaurus

2 posts with the tag “Docusaurus”

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.

HagiCode in Practice: How to Use GitHub Actions for Docusaurus Automated Deployment

Adding GitHub Pages Automated Deployment Support to HagiCode

Section titled “Adding GitHub Pages Automated Deployment Support to HagiCode”

The project’s early codename was PCode, and it has now officially been renamed HagiCode. This article records how we introduced automated static site deployment for the project so publishing content becomes as easy as drinking water.

During HagiCode development, we ran into a very practical problem: as the amount of documentation and proposals kept growing, efficiently managing and presenting that content became increasingly urgent. We decided to use GitHub Pages to host our static site, but building and deploying manually was simply too much trouble. Every change required a local build, packaging, and then a manual push to the gh-pages branch. That was not only inefficient, but also error-prone.

To solve this problem (mostly because we wanted to be lazy), we needed an automated deployment workflow. This article documents in detail how we added GitHub Actions-based automated deployment support to the HagiCode project, so we can focus on creating content and leave the rest to automation.

Hey, let us introduce what we are building

We are developing HagiCode - an AI-powered coding assistant that makes development smarter, easier, and more enjoyable.

Smarter - AI assistance throughout the whole process, from ideas to code, multiplying coding efficiency. More convenient - multi-threaded concurrent operations that make full use of resources and keep the development workflow smooth. More enjoyable - gamification mechanisms and an achievement system that make coding less dull and far more rewarding.

The project is evolving quickly. If you are interested in technical writing, knowledge management, or AI-assisted development, feel free to check it out on GitHub~

Before getting started, we first need to clarify what exactly this task is supposed to accomplish. After all, sharpening the axe does not delay the work.

  1. Automated build: Automatically trigger the build process when code is pushed to the main branch.
  2. Automated deployment: After a successful build, automatically deploy the generated static files to GitHub Pages.
  3. Environment consistency: Ensure the CI environment matches the local build environment to avoid the awkward “it works locally but fails in production” situation.

Since HagiCode is built on Docusaurus (a very popular React static site generator), we can use GitHub Actions to achieve this goal.

GitHub Actions is the CI/CD service provided by GitHub. By defining workflow files in YAML format inside the repository, we can customize a variety of automation tasks.

We need to create a new configuration file in the .github/workflows folder under the project root, for example deploy.yml. If the folder does not exist, remember to create it manually first.

The core logic of this configuration file is as follows:

  1. Trigger condition: Listen for push events on the main branch.
  2. Runtime environment: The latest Ubuntu.
  3. Build steps:
    • Check out the code
    • Install Node.js
    • Install dependencies (npm install)
    • Build the static files (npm run build)
  4. Deployment step: Use the official action-gh-pages to push the build artifacts to the gh-pages branch.

Below is the configuration template we ultimately adopted:

name: Deploy to GitHub Pages
# Trigger condition: when pushing to the main branch
on:
push:
branches:
- main
# You can add path filters as needed, for example only build when docs change
# paths:
# - 'docs/**'
# - 'package.json'
# Set permissions, which are important for deploying to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Concurrency control: cancel older builds on the same branch
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Note: you must set fetch-depth: 0, otherwise the build version may be inaccurate
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20 # Recommended to match your local development environment
cache: 'npm' # Enabling cache can speed up the build process
- name: Install dependencies
run: npm ci
# Use npm ci instead of npm install because it is faster, stricter, and better suited for CI
- name: Build website
run: npm run build
env:
# If your site build requires environment variables, configure them here
# NODE_ENV: production
# PUBLIC_URL: /your-repo-name
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./build # Default Docusaurus output directory
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

Pitfalls Encountered During Implementation

Section titled “Pitfalls Encountered During Implementation”

In practice, we ran into a few issues. I am sharing them here in the hope that everyone can avoid them, or at least prepare solutions in advance.

When we first set things up, deployment kept failing with a 403 (Forbidden) error. After a long investigation, we discovered that GitHub’s default GITHUB_TOKEN did not have permission to write to Pages.

Solution: In the repository Settings -> Actions -> General -> Workflow permissions, make sure to choose “Read and write permissions”.

By default, Docusaurus puts the built static files in the build directory. However, some projects may use different configurations. For example, Create React App defaults to build, while Vite defaults to dist. If Actions reports that it cannot find files, remember to check the output path configuration in docusaurus.config.js.

If your repository is not a user homepage (that is, not username.github.io) but instead a project page (such as username.github.io/project-name), you need to configure baseUrl.

In docusaurus.config.js:

module.exports = {
// ...
url: 'https://hagicode.com', // Your Hagicode URL
baseUrl: '/', // Deploy at the root path
// ...
};

This detail is easy to overlook. If it is configured incorrectly, the page may load as a blank screen because the resource paths cannot be resolved.

After configuring everything and pushing the code, we can head to the Actions tab in the GitHub repository and enjoy the show.

You will see a yellow circle while the workflow is running. When it turns green, it means success. If it turns red, click into the logs to inspect the issue. Usually, you can track it down there, and most of the time it is a typo or an incorrect path configuration.

Once the build succeeds, visit https://<your-username>.github.io/<repo-name>/ and you should see your brand-new site.

By introducing GitHub Actions, we successfully implemented automated deployment for the HagiCode documentation site. This not only saves the time previously spent on manual operations, but more importantly standardizes the release process. Now, no matter which teammate updates the documentation, as long as the changes are merged into the main branch, the latest content will appear online a few minutes later.

Key benefits:

  • Higher efficiency: from “manual packaging and manual upload” to “code is the release”.
  • Fewer errors: removes the possibility of human operational mistakes.
  • Better experience: lets developers focus more on content quality instead of being distracted by tedious deployment steps.

Although setting up CI/CD can be a bit troublesome at first, especially with all the permissions and path issues, it is a one-time investment with huge long-term returns. I strongly recommend that every static site project adopt a similar automated workflow.


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.