← Back to Blog

MIGRATION · April 14, 2026 · 11 min read

Migrating SharePoint Site Pages: why canvas layout matters

The first time someone migrates a SharePoint site with a tool that treats pages as list items, they get a surprise on Monday morning. The page is there. The URL resolves. The text shows up. But the layout is flat, the image web parts are gone, the Highlighted Content rollups are empty, and the custom SPFx hero web part renders as a grey error box. The migration technically succeeded. The page is technically broken.

This post explains why modern SharePoint pages break under naive migration, what the Graph pages API actually returns, and how MigrationFox’s SharePoint Site Migration preserves canvas layout and web-part configuration across tenants.

What a modern SharePoint page actually is

In classic SharePoint, a page was basically an HTML document stored in the Pages library. You could almost get away with reading the .aspx file, copying the body, and pasting it into a new file on the destination. It was ugly, but it roughly worked.

Modern pages — everything created since the SitePages content type replaced the classic Wiki Page model — are not HTML documents. They are structured canvas layouts backed by a JSON schema. A page has:

When you open a page in the browser, SharePoint deserialises that structure, looks up each web part by its type ID, and renders it. The HTML you see in devtools is the output of rendering, not the source of truth. The source of truth is the canvas JSON.

Why list-item migration breaks pages

A lot of generic migration tools treat the Site Pages library the same way they treat any document library: enumerate items, copy files, done. For document library files that approach is fine — a PDF is a PDF and its bytes are self-contained.

For a Site Pages library it fails in several ways at once:

What the user sees: a list of files in the destination Site Pages library that look right in the file listing but render as rubble when opened. And because list-item migration “succeeded”, there is nothing in the migration log pointing at the problem.

The right way: Graph Pages API

Microsoft exposes the canvas model through the Graph pages endpoint under /sites/{site-id}/pages. A GET returns the full page object, with the canvas structure as a typed JSON tree rather than an opaque HTML blob. For each web part it returns:

A POST to /sites/{site-id}/pages with the same structure creates a new page on the destination, with the same canvas layout, the same web parts, and the same properties. Followed by POST to .../publish and optionally .../promoteToNewsPost, and the page is live.

The Graph Pages API is the right abstraction for modern pages in the same way that contentTypes is the right abstraction for schema. Treating pages as list items is treating structured data as if it were a string.

How MigrationFox walks a page

For each page in the source Site Pages library, we do something close to this:

1. GET /sites/{source-site}/pages/{page-id}
2. Read the canvas sections, columns, and web parts
3. Walk each web part:
   a. Copy text/image/link web parts as-is
   b. Rewrite list-viewer web parts to point at the new list ID
      on the destination (we know this because we migrated
      the list earlier in the phase ordering)
   c. Flag SPFx web parts whose package ID is not installed
      on the destination for human review
4. POST the reconstructed page object to /sites/{dest-site}/pages
5. POST /publish to make it live
6. If the source was a promoted news post, POST /promoteToNewsPost

The important bit is step 3b. Web parts reference data by ID, not by name. If a Highlighted Content web part was rolling up “the last five documents from the Policies library”, the reference in the canvas is the GUID of the source Policies library, not the word “Policies”. For the web part to work on the destination, that GUID has to be rewritten to the destination library’s GUID — which means the list has to have been migrated first and its ID captured. That is why schema before items, items before pages is the mandatory phase order.

Cross-tenant URL caveats

Even with perfect canvas preservation, cross-tenant page migration has a class of reference that you cannot transparently fix: explicit hyperlinks in text web parts that point at full source-tenant URLs.

A rich-text web part might contain something like <a href="https://acme.sharepoint.com/sites/hr/handbook/welcome.aspx">Read the handbook</a>. That URL lives inside the rendered HTML of the text web part, not as a typed reference that the migration engine can identify with certainty. The destination tenant is newcompany.sharepoint.com, so the link is now broken.

MigrationFox’s approach is conservative: we rewrite obvious cases (the URL of a migrated site we know about, with a clean path match) and flag the rest in the migration report with the page name, the web-part ID, and the offending URL so a content owner can fix them. We explicitly do not do fuzzy URL rewriting on rich-text bodies because the risk of rewriting something that was a deliberate external link (to the old tenant, or to a SharePoint page someone kept public on purpose) is higher than the benefit of automating the 80% case.

What actually gets preserved

ElementPreservedNotes
Canvas sections & columnsYesFull layout fidelity including vertical sections
Text web partsYesRich text, formatting, inline links
Image web partsYesImage file re-uploaded into Site Assets on destination
Highlighted Content / List viewerYesSource list ID rewritten to destination list ID
Quick Links, People, EventsYesInternal references rewritten where possible
Banner image + layoutYesIncluding the banner’s focal point coordinates
Promoted / News post stateYesReplayed via promoteToNewsPost
Third-party SPFx web partsReference onlyPackage must be installed on destination; surfaced in report
Page-level approvals / pending draftsPartialCurrent published version migrates; draft workflow state does not
Page permissions (unique)YesVia the cross-tenant UPN mapping

The SPFx package problem

Custom SPFx web parts are the one category that cannot be fully automated, and it is worth being honest about why.

An SPFx web part is a packaged solution (.sppkg) that has to be uploaded to the destination tenant’s App Catalog and then deployed to the sites that use it. That is a tenant-admin action, and it happens in a system (the App Catalog) that a migration tool cannot write to without escalated privileges. What MigrationFox does instead is enumerate every SPFx web part referenced by any page in the source, look up its package ID and version, and produce a pre-flight list for the tenant admin. The list has:

The admin installs the missing packages before or during migration; the pages migrate with the web-part references intact; everything lights up on first page load. If a package is genuinely gone from the world (the vendor shut down, the internal developer left), the web part renders as an error tile on the destination — the same experience as on the source once the package was removed there — and the migration report flags it for content cleanup.

The migration order, with pages included

Site Pages are the penultimate phase in a clean SharePoint site migration, after items but before permissions:

  1. Site columns and content types
  2. Lists and libraries with bound content types
  3. List items and files
  4. List views
  5. Site Pages (including canvas, web parts, publish state)
  6. Permissions — applied against the cross-tenant UPN mapping

The reason pages come after items is the ID-rewriting requirement we described earlier: a page that references list X needs list X’s destination ID to be known at page-creation time. The reason pages come before permissions is that the permissions replay is the last thing to touch the site and we want all content to exist before we start locking it down.

What we are still improving

A few page-related capabilities are on the roadmap but not shipped. We are calling them out so you can plan around them:

For most migrations, these gaps are acceptable. For records-retention use cases where the page history is itself a regulated artefact, treat SharePoint pages the same way you would treat list items with version-history requirements: capture the history as a sidecar on the source before migration, and keep it queryable.

Related reading

Get started

Run a free pre-flight on your SharePoint site at app.migrationfox.com/register. The dry-run report enumerates every page, every web part, and every SPFx package the destination will need before cutover.

Migrate pages as pages, not as list items

Free account. Dry-run report in minutes.

Start Free →