Migrating From Wagtail To Astro (2): The Main Phases
· 21 min read · Views --
Last updated on

Migrating From Wagtail To Astro (2): The Main Phases

Author: Alex Xiang


Migrating From Wagtail To Astro (2): The Main Phases

This is the second article in the Wagtail-to-Astro series. It explains how I split the migration into phases and what each phase needs to finish.

Overall Phase Plan

The migration can be divided into four main phases:

Phase 1: Sync data and assets -> Phase 2: Convert and import content -> Phase 3: Site structure and scripting -> Phase 4: Validate and switch traffic

Phase 1: Sync Data And Assets

Goal: Pull the online Wagtail data and media assets to local storage in a repeatable way, so later incremental updates are possible.

What Needs To Be Synced

  • Database: The SQLite database used by Wagtail, such as zicodedb, containing posts, categories, tags, columns, StreamField data, and related metadata.
  • Media files: Uploaded images and other files, usually under media/original_images/ or a similar path.
  • Necessary code and configuration: If the export logic must be run through Django locally, sync manage.py, requirements.txt, related apps, and templates as needed.

Use SSH + remote commands, such as scp or rsync, to pull files from the server:

  • Copy the entire database file into a local directory such as legacy-wagtail/.
  • Use rsync for the media directory to avoid repeatedly transferring large unchanged files.

It is worth writing a sync script, such as scripts/sync_legacy.py, with configuration for host, paths, and local destination. That makes future incremental syncs one command instead of a manual operation.

After this phase, the local machine should have a readable SQLite database, media files matching the online site, and, if necessary, a runnable Wagtail project copy.

Phase 2: Convert And Import Content

Goal: Convert Wagtail posts into Astro Markdown files with frontmatter, while fixing image paths.

Content Model Mapping

Wagtail conceptAstro mapping
Post title, summary, bodytitle, description, Markdown body
Publish time, update timepubDate, updatedDate
Category, columncategory, column as frontmatter strings
Tagstags as a frontmatter string array
Authorauthors as a frontmatter string array
Hero or featured imageheroImage as a relative path or URL
StreamField blocksParse into Markdown paragraphs, headings, lists, code blocks, images, and so on

Body Conversion Details

Wagtail StreamField stores a JSON array. Each element has a type and a value. Common block types can be converted like this:

Block typeTypical valueMarkdown output
paragraph{ "text": "..." }Output as a paragraph with blank lines around it
markdown{ "source": "..." }Keep the source as-is when possible
heading{ "text": "Title", "level": 2 }Output ## Title, based on the level
imageimage id or pathResolve the media path and output ![](/uploads/...)
code{ "language": "python", "code": "..." }Output a fenced code block
list{ "items": [...] }Output Markdown list items

Image blocks need special care. The import script should resolve the real file name from Wagtail’s media table or stored path, write a site path such as /uploads/original_images/xxx.png, and copy the file into public/uploads/original_images/.

If the source content is already Markdown, preserve it as much as possible. Avoid re-escaping Markdown, because that often breaks code blocks and links.

Import Script

Write an import script, for example scripts/import_wagtail.py, that:

  • Connects to the local SQLite database and reads post records.
  • Generates frontmatter plus Markdown body using the conversion rules above.
  • Writes each post into src/content/blog/imported/xxx.md.
  • Optionally copies used images into public/uploads/original_images/.

It is useful to keep a legacyId for each imported post. That makes later reconciliation or partial re-import much easier.

At the end of this phase, Astro’s getCollection('blog') should be able to see all historical posts, and layout, links, and images should work.

Common issues:

  • Image 404: Check that the target path used by the import script matches both heroImage and inline Markdown images, and that the files are actually copied into public/.
  • Chinese slug encoding: If Astro routes use Chinese slugs, keep URL encoding consistent. Navigation, breadcrumbs, and internal links should not mix encoded and raw forms unpredictably.

Phase 3: Site Structure And Scripting

Goal: Define URL structure, navigation, column/category/tag pages, and turn sync plus import into a repeatable command.

URL Design

  • Post pages: /blog/<slug>/, matching the Wagtail slug or a custom slug, so 301 redirects are easy if needed.
  • Category, column, and tag pages: /blog/category/<name>/, /blog/column/<name>/, and /blog/tag/<name>/. For Chinese names, use a consistent encoding or slug rule.
  • Use Astro’s getCollection to calculate categories, columns, and tags for the header, footer, and sidebar.
  • Keep category and column names consistent with Wagtail so internal links and reader habits remain stable.

Scripting

  • sync: Pull the latest database and media files from the server, for example python scripts/sync_legacy.py.
  • import: Read the local SQLite database and write Markdown into src/content/blog/imported/. Be careful not to delete hand-written new posts.
  • Optional: Chain sync -> import -> astro build in CI or a Makefile, so the whole old-site-to-static-build process can be run as one workflow.

Phase 4: Validate And Switch Traffic

Goal: Confirm the migration result is correct before switching production traffic or running both sites for a while.

Content Checks

  • Compare post count, titles, and dates with Wagtail admin or export lists.
  • Sample several posts and check body content, code blocks, images, and internal links.
  • Verify that category, column, and tag lists are complete and accessible.

Build And Deployment

  • Run astro build locally and check for errors or broken links.
  • Deploy dist/ to a test domain or subpath, then crawl key pages with a browser and simple scripts.

Cutover Strategy

  • Direct switch: Deploy the Astro build to the original domain root. Shut down Wagtail or keep it as read-only admin.
  • Parallel period: Run Astro on a subdomain or separate path, keep the old site, then switch the main domain and add 301 redirects later.

The main flow is: get the data, convert it to Markdown, define URLs and scripts, then validate before switching. Each phase can be rolled back or rerun independently. I recommend running pnpm build and pnpm preview after every major phase before moving on.

Series: Part 1: Why migrate and how I chose · Part 2: The main phases · Part 3: Adding new posts · Part 4: Deployment · Part 5: Data sync and scripting · Part 6: Theme and feature polish