SharePoint Site Pages
Modern SharePoint pages are canvas layouts with typed web parts, not HTML documents. MigrationFox migrates them as structured data through the Microsoft Graph Pages API so that the destination preserves sections, columns, web-part configuration, banner layout, and publish state. This page is the reference for what gets preserved, what needs manual review, and how to handle the edge cases.
What this feature does
For every modern site page in the source Site Pages library, MigrationFox reads the full canvas structure from the Graph Pages API, rewrites internal references (list IDs, library IDs, Site Assets image URLs) to the destination equivalents, re-creates the page with matching layout on the destination, publishes it, and replays the promoted-news-post state if the source page was a news post. The result is a visually identical page on the destination, not a copy-paste of rendered HTML.
What a modern page is (and why it matters)
In classic SharePoint, a page was essentially an HTML document stored in the Pages library. You could almost get away with copying the .aspx file across tenants. Modern pages — everything created under the SitePages content type — are different. They are structured canvas layouts backed by a JSON schema. A page has:
- Sections, each with a column layout (one-column, two-column, three-column, vertical section).
- Columns inside each section, each with a factor that controls relative width.
- Web parts inside each column, each with a strongly-typed ID and a JSON property bag.
- Page-level metadata (title, banner image, banner layout, description, promoted state).
When the browser opens a page, SharePoint deserializes the canvas JSON, looks up each web part by its type ID, and renders it. The HTML you see in devtools is the output, not the source of truth. The source of truth is the canvas JSON — which is why migrating pages as list-library files (copy the .aspx, paste it into the destination library) produces broken pages even when it appears to succeed. The canvas JSON lives in a hidden column that often exceeds list-field-length thresholds, embedded web parts reference source-tenant IDs that do not exist on the destination, and publish state is not part of the page file at all.
Web part serialization
The Graph pages endpoint returns each web part as a typed object with:
- The web-part type ID: a GUID identifying which web part it is.
- The instance ID: a per-page GUID so the canvas can reference it internally.
- The properties JSON: everything the user configured in the property pane.
- The server-processed content: for text parts, rich text; for image parts, the image reference.
- The column and order the part sits in.
MigrationFox preserves all of these for first-party web parts. For third-party or custom parts, preservation is partial — the type reference is carried across, but the part only renders if the matching package is installed on the destination. The tables below spell out exactly what that means.
Fully portable first-party web parts
| Element | Preserved | Notes |
|---|---|---|
| Canvas sections & columns | Yes | Full layout fidelity including vertical sections. |
| Text web parts | Yes | Rich text, formatting, inline links. |
| Image web parts | Yes | Image file re-uploaded into the destination Site Assets library; reference rewritten to the new URL. |
| Highlighted Content / List viewer | Yes | Source list ID rewritten to destination list ID (requires the list to have been migrated earlier). |
| Quick Links, People, Events | Yes | Internal references rewritten where the targets were migrated. |
| Banner image + layout | Yes | Including the banner’s focal point coordinates. |
| Promoted / News post state | Yes | Replayed via the Graph promoteToNewsPost call. |
| Page permissions (unique) | Yes | Applied against the cross-tenant UPN mapping. |
Web parts that need manual review
| Element | Preserved | Notes |
|---|---|---|
| Third-party SPFx web parts | Reference only | Package must be installed on the destination App Catalog. Until then the web part renders as an error tile. |
| Custom SPFx developed in-house | Reference only | Same model — the .sppkg has to be uploaded to the destination App Catalog and deployed to consuming sites by a tenant admin. |
| Page-level approvals / pending drafts | Partial | The currently published version migrates; draft workflow state does not. |
| Page version history | No | Graph does not expose a supported write path for prior versions. Current canvas migrates; history does not. |
| Page comments and likes | No | Graph has no supported write path for historical comments. Current comments do not carry. |
Cross-tenant URL rewriting
Canvas preservation handles the structural side. URLs inside the content are a separate problem. 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 the migration engine can identify with certainty. The destination tenant is newcompany.sharepoint.com, so the link is now broken.
MigrationFox takes a conservative approach:
- Rewrite obvious cases. If the link points at a source-tenant site that MigrationFox migrated in the same job, and the path matches cleanly (
/sites/<name>/<library>/<file>), the URL is rewritten to the destination equivalent. - Flag the rest on the migration report. Ambiguous cases — links that could be intentional external references to the old tenant, or to a page someone kept public on purpose — are listed in the report with the page name, the web-part instance ID, and the offending URL. A content owner fixes them after cutover.
- No fuzzy rewriting on rich-text bodies. The risk of rewriting an intentional external link is higher than the benefit of automating the long tail. Rewrites happen only where the evidence is unambiguous.
Reference rewriting requires phase ordering
Web parts reference data by ID, not by name. A Highlighted Content web part rolling up “the last five documents from Policies” stores the GUID of the source Policies library, not the word “Policies.” For the web part to work on the destination, that GUID must be rewritten to the destination library’s GUID — which means the list has to have been migrated before the page. The clean phase order is: site columns & content types, lists & libraries, list items & files, list views, site pages, permissions. MigrationFox sequences these automatically when the full site migration runs end-to-end.
Media references
Images and other media embedded in pages live in the site’s Site Assets library, not in the Pages library itself. A page references them by URL. Cross-tenant migration needs three things in sequence:
- The Site Assets library on the destination is provisioned (provisioning is automatic for every site created through MigrationFox; manually created destinations need it to exist).
- The source image files are uploaded into the destination Site Assets library with matching relative paths.
- The image web part references are rewritten to point at the destination Site Assets URL.
MigrationFox does all three automatically when a page migration is initiated for a site that is fully in scope. If you migrate a page from a site whose Site Assets library was not in scope — for example, cherry-picking a single page into a destination site — the image references point at the source tenant. They render until the source tenant revokes access or is retired, then they break. The migration report lists every cross-tenant image reference so you know which pages need a media fix.
Known limits
A few page capabilities are not currently shipped. Plan around them rather than expecting them to come across silently.
- Third-party SPFx packages must be uploaded to the destination App Catalog and deployed to the consuming sites by a tenant admin before the pages that reference them render correctly. This is a tenant-admin action in a system (the App Catalog) that a migration tool cannot write to without escalated privileges. MigrationFox enumerates every SPFx package referenced by any migrated page — package ID, display name, version, consuming pages — so the admin has a complete pre-flight list.
- Custom SPFx that is no longer available (the vendor shut down, the internal developer left and the package source is gone) cannot render on the destination because the
.sppkgdoes not exist to install. The web part shows as an error tile — the same experience it would have on the source if the package were removed there — and the migration report flags it for content cleanup. - Page version history does not migrate. Only the currently published version of each page is re-created on the destination. For records-retention use cases where the page history is itself a regulated artifact, capture the history as a sidecar on the source before migration and retain it separately.
- Draft unpublished versions do not migrate. If a page has an in-progress draft that has not been published, that draft is not preserved. The currently published version comes across.
- Page comments and likes do not migrate. Graph has no supported write path for historical comments on a freshly created page.
- Page-level approval workflow state is not preserved. If a page is in an approval pipeline on the source, the destination receives the currently published version and no queued approval state.
How MigrationFox walks a page
For each page in the source Site Pages library, the sequence is:
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, rewriting internal URLs
b. Rewrite list-viewer web parts to point at the destination list ID
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
Step 3b is where the earlier phase-ordering matters. Every list and library referenced by any web part must already exist on the destination with its new ID captured, so the rewrite can resolve. Step 5 is why a freshly migrated page appears live rather than as a draft — the publish call is part of the standard flow.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| A page appears in the destination but the layout is flat — no sections, no web parts visible | The page was migrated as a list-library file rather than through the Graph Pages API. This can happen if the source was scoped to just the Site Pages library contents, bypassing the canvas-aware flow. | Scope the migration to the whole site, or explicitly use the Pages migration step on the affected site. The naive file-copy path is not the right tool for canvas pages. |
| Web part shows as a grey error tile on the destination | The web part is a third-party SPFx package that is not installed in the destination App Catalog. | Check the migration report’s SPFx pre-flight list. A tenant admin uploads the .sppkg to the destination App Catalog and deploys it to the consuming sites. The web part renders on next page load. |
| Highlighted Content web part renders empty on the destination | The web part’s source list ID was not rewritten — typically because the list it references was not in scope for migration, so no destination list ID existed to rewrite to. | Add the list to the migration scope and re-run, or edit the web part on the destination to point at whatever list should be the source now. |
| Hyperlinks in text web parts still point at the source tenant | Cross-tenant URL rewriting is deliberately conservative — ambiguous cases are flagged rather than rewritten. | Check the migration report for the full list of flagged URLs by page. A content owner edits each text web part to update the link. |
| Images show as broken (red X) on the destination page | The image was referenced from a Site Assets library that was not in scope for the migration; the image file was not re-uploaded to the destination. | Expand the migration scope to include Site Assets, or re-upload the missing images to the destination Site Assets library manually. |
| News post status is lost — page is there but no longer marked as a news post | The promoteToNewsPost replay step failed, commonly because the destination site is a communication site that handles news differently, or because the credential lacks page-write scope. |
Re-run the pages phase for the affected site, or manually promote the page to a news post via the destination UI. |
| Page version history is empty on the destination | Expected. Page version history is not preserved by design — Graph does not expose a supported write path for prior versions. | Capture the version history as a sidecar on the source before migration if it is needed for records retention. |
Related
- SharePoint provisioning — destination site creation that runs before pages migrate
- Blocked file types — the tenant policy that can affect embedded content during page migration
- Skip junk files — scan-time filtering that keeps OS debris out of the Site Assets library