NetcaneTechnologies

Strapi

Migration Path: From Local MDX Blog to Strapi in Production

Moving a file-based MDX blog into Strapi without losing SEO equity or breaking a single URL — the field-mapping decisions, the migration script pattern, and the cutover sequence that avoids a traffic dip.

Yasir Haleem7 min read

Moving from a file-based MDX blog to Strapi is usually triggered by one specific pain point: someone who isn't a developer needs to publish or edit a post, and "open a pull request" isn't a workflow non-technical marketing teams will adopt. The migration itself is mechanical — script the transform, preserve the URLs, cut over — but "mechanical" only stays true if you get the field mapping and URL preservation right up front. Get either wrong and you either lose content fidelity in the transform or lose search rankings on cutover day, which is a much more expensive problem to notice after the fact than to prevent before.

Align the content model before writing a line of migration script

Design a Strapi collection type — call it Post — that maps as closely as possible to your existing MDX frontmatter shape: title, slug, excerpt, date, author, tags, and a body field. Resist the urge to "improve" the schema mid-migration; changing field names or structure at the same time you're moving platforms doubles the number of things that can go wrong and makes the migration script itself harder to write and verify.

The body field is the one real decision. Three options, in order of how much they preserve vs. how much they cost to implement:

  1. Strapi's rich text field, which stores and can output HTML or Markdown depending on configuration. Simplest to wire up; works fine if your MDX doesn't use custom components inside the body.
  2. A long-text field storing raw Markdown, rendered on the frontend with the same Markdown pipeline you're already using (or close to it). This preserves the exact authoring format and is the closest thing to a true one-to-one migration.
  3. A dynamic zone, if your MDX uses custom components inline (callout boxes, embedded code demos, custom layout blocks) that don't map to plain Markdown. This is the most work — each custom component needs a matching Strapi component — but it's the only option that doesn't lose those custom blocks or flatten them into plain text.

Pick based on how much your existing MDX actually uses custom components. A blog that's plain prose with headings and code blocks migrates cleanly to option 1 or 2; a blog built around custom interactive MDX components needs option 3, and that's worth knowing before you start scripting.

Export and transform: script it, don't hand-copy it

Write a script that reads every MDX file, parses frontmatter with gray-matter (the same library most MDX blog setups already use to read frontmatter), and creates a matching Strapi entry via the Content API or a direct database seed.

import fs from "fs";
import path from "path";
import matter from "gray-matter";
 
const files = fs.readdirSync("content/blog");
 
for (const file of files) {
  const raw = fs.readFileSync(path.join("content/blog", file), "utf8");
  const { data, content } = matter(raw);
  const slug = data.slug ?? file.replace(/\.mdx?$/, "");
 
  await strapiCreate("posts", {
    title: data.title,
    slug,
    excerpt: data.excerpt,
    publishedAt: data.date,
    content,
    // map tags/author to relations if your schema uses them,
    // rather than storing them as raw strings
  });
}

Handle images explicitly — this is where migrations most often lose fidelity. MDX blog images are typically relative paths into a local public/ folder; Strapi needs them either uploaded to its media library (via the Upload API) with the body's image references rewritten to the new media URLs, or left on the existing host with absolute URLs substituted in. Uploading properly is more work up front and the right choice if you want Strapi to own media going forward (see our Strapi media guide for the CDN and format pipeline that should sit behind it).

Make the script idempotent. Match on slug and update-or-skip existing entries rather than blindly creating duplicates — this lets you re-run the script after fixing a bug partway through a large batch, without manually cleaning up partial imports first. For large archives, batch the requests (50–100 at a time with a short delay) rather than firing hundreds of API calls simultaneously, which is a good way to trip rate limits or overload a freshly provisioned Strapi instance.

URLs and redirects: the step that protects your rankings

Use the exact same slug in Strapi as the MDX file had, so /blog/my-post stays /blog/my-post — this is the single highest-leverage decision in the entire migration, because it means zero URLs change and there's nothing to redirect for content that's staying at the same path.

If the old structure genuinely differs (say, migrating from /posts/my-post to /blog/my-post as part of the same project), add explicit 301 redirects at the Next.js or hosting level for every changed URL — not a blanket pattern-based redirect that might miss edge cases, but a generated map from old path to new path, the same discipline covered in our SEO launch checklist. Set canonical tags on the new URLs so search engines consolidate ranking signal onto the version you're keeping, rather than treating old and new as competing duplicates during the transition window.

Cutover: change the data source, not the routes

Once content is verified in Strapi, switch the Next.js app's data-fetching layer to pull from the Strapi API instead of the local filesystem. Keep the same page routes and rendering components — this should be a data-layer change, not a rebuild of the blog's frontend. If the migration is designed correctly, a visitor shouldn't be able to tell the switch happened at all: same URLs, same rendered output, same metadata.

Before flipping the switch in production:

  • Verify listing pages (pagination, sorting, category/tag filters if applicable) return the same posts as before, in the same order.
  • Verify individual post pages render body content correctly — this is where formatting mismatches between MDX and whatever body format you chose in Strapi show up, if they're going to.
  • Verify metadata — title tags, descriptions, Open Graph images — are generated from the new source correctly, since a silent metadata regression is invisible to a visual check but shows up in search results days later.

After cutover

Keep the old MDX files as a backup for a reasonable window — weeks, not days — even after you're confident the migration succeeded; it costs nothing to keep them and it's the fastest possible recovery path if something's discovered wrong after launch that a quick script re-run could fix. Archive or delete them once you're genuinely past the point of needing a fallback.

The migration, condensed

StepDo thisAvoid this
Content modelMatch existing frontmatter shape closelyRedesigning the schema mid-migration
Body fieldPick rich text, Markdown, or dynamic zone based on actual MDX complexityDefaulting to rich text without checking for custom components
ScriptIdempotent, slug-matched, batchedOne-shot script with no re-run safety
ImagesUpload to Strapi media or preserve absolute URLs deliberatelyLeaving relative paths broken after the switch
URLsSame slugs; 301s only for genuine changesRedirecting everything "just in case"
CutoverSwap data source only, same routes/componentsRebuilding the frontend at the same time as the migration

This is the exact sequence we run for clients moving off file-based content — including a 30,000+ product WordPress-to-Strapi migration that used the same idempotent, slug-preserving approach at much larger scale. It's a mechanical process once the field mapping is settled, which is exactly why getting that mapping right before scripting anything is worth the extra hour. If you're planning a similar migration and want a second set of eyes on the content model before you start, get in touch — or see how we scope these moves as part of headless CMS implementations.

About the author

Yasir Haleem is founder and lead engineer at Netcane Technologies. He builds production Next.js sites with headless CMS platforms — Strapi, Contentful, Sanity, and WordPress — with a focus on performance, SEO, and maintainable architecture.

Let's work together

Tell us about your project. We respond within one business day with a clear scope, timeline, and estimate.