Migrating From Wagtail To Astro (2): The Main Phases
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.
Recommended Sync Method
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 concept | Astro mapping |
|---|---|
| Post title, summary, body | title, description, Markdown body |
| Publish time, update time | pubDate, updatedDate |
| Category, column | category, column as frontmatter strings |
| Tags | tags as a frontmatter string array |
| Author | authors as a frontmatter string array |
| Hero or featured image | heroImage as a relative path or URL |
| StreamField blocks | Parse 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 type | Typical value | Markdown 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 |
image | image id or path | Resolve the media path and output  |
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
heroImageand inline Markdown images, and that the files are actually copied intopublic/. - 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.
Navigation And Data
- Use Astro’s
getCollectionto 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 buildin 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 buildlocally 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
Follow ZiCode on WeChat
If this post was useful, you can follow later updates on WeChat as well.
X / Twitter
Follow @ax2_zicode
Faster technical notes, short thoughts, and new-post alerts are posted on X.