Transform classic pages to modern client-side pages

Classic SharePoint sites typically have classic pages being wiki pages or web part pages and these pages cannot present themselves using a modern user interface. A classic site however can host modern client side pages and this is the solution here. After you've maximized the use of the modern list and library user interface and connected your site to an Office 365 Group transforming the pages to modern client side pages is the last task to fully transform your classic site into a modern group connected site.

The SharePoint PnP Modernization framework (Nuget, source code) does bring page transformation capabilities which will be explained in the upcoming chapters.

Important

The SharePoint PnP Modernization framework is continuously evolving, checkout the release notes to stay up to date on the latest changes. If you encounter problems please file an issue in the PnP Tools GitHub issue list.

Before you start

By default the modern site page capability is enabled on most sites but maybe it was turned off afterwards. If that's the case the SharePoint Modernization scanner will tell you which sites have turned of the modern page feature. To remediate this use below sample PnP PowerShell script:

$minimumVersion = New-Object System.Version("2.24.1803.0")
if (-not (Get-InstalledModule -Name SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -ErrorAction Ignore))
{
    Install-Module SharePointPnPPowerShellOnline -MinimumVersion $minimumVersion -Scope CurrentUser
}
Import-Module SharePointPnPPowerShellOnline -DisableNameChecking -MinimumVersion $minimumVersion

Connect-PnPOnline -Url "<your web url>"

# Enable modern page feature
Enable-PnPFeature -Identity "B6917CB1-93A0-4B97-A84D-7CF49975D4EC" -Scope Web -Force

Quick start to page transformation for .Net developers

The page transformation engine is built using .Net and is distributed as a nuget package. Once you've added the nuget package you'll see that 2 additional files are added to your solution:

page transformation solution files

Note

The minimal .Net Framework version for this solution to work is 4.5.1.

The webpartmapping.xml and webpartmapping_latestfrompackage.xml represent the transformation model that describes how the transformation will happen. You typically will tweak the webpartmapping.xml file to your needs by for example adding additional mappings to your own web parts. If you later on install an updated version of the nuget package your webpartmapping.xml will not be overwritten by default but the webpartmapping_latestfrompackage.xml will be. You can use this latter file to compare the latest out-the-box mapping with your mapping and take over the changes you need.

With the mapping file in place you now can use below snippet (coming from the Modernization.PageTransformation sample on GitHub) to transform all the pages in a given site:

string siteUrl = "https://contoso.sharepoint.com/sites/mytestsite";
string userName = "joe@contoso.onmicrosoft.com";
AuthenticationManager am = new AuthenticationManager();
using (var cc = am.GetSharePointOnlineAuthenticatedContextTenant(siteUrl, userName, GetSecureString("Password")))
{
    var pageTransformator = new PageTransformator(cc);
    var pages = cc.Web.GetPages();
    foreach (var page in pages)
    {
        PageTransformationInformation pti = new PageTransformationInformation(page)
        {
            // If target page exists, then overwrite it
            Overwrite = true,
            // Migrated page gets the name of the original page
            TargetPageTakesSourcePageName = true,
        };

        try
        {
            Console.WriteLine($"Transforming page {page.FieldValues["FileLeafRef"]}");
            pageTransformator.Transform(pti);
        }
        catch(ArgumentException ex)
        {
            Console.WriteLine($"Page {page.FieldValues["FileLeafRef"]} could not be transformed: {ex.Message}");
        }
    }
}

Quick start to page transformation using PowerShell

The page transformation engine can also be used from PowerShell. This allows it to be integrated in a site modernization script that besides page transformation also does other things like installing solution, connecting the site to an Office 365 group and applying tenant branding.

Important

To use PowerShell it's important to have all the needed binaries together in one folder. The easiest solution is to download the latest version and all dependencies from the shared SharePointPnP.Modernization binary package. Another way to get this list of binaries is to use Visual Studio, add the SharePointPnPModernizationOnline package and build the solution. Once that's done you can find the needed binaries in the solution's bin folder.

Below script shows how to call the transformation engine using PowerShell:


function Use-PnPModernizationFramework
{
    param(
        [string] $PathToModernizationBinaries
    )

    begin
    {
    }

    process
    {
        Add-Type -Path "$PathToModernizationBinaries\SharePointPnP.Modernization.Framework.dll"
    }

    end
    {
        return $PathToModernizationBinaries
    }
}

function Invoke-PnPModernizationPageTransformation
{
    param(
        [string] $PathToModernizationBinaries,
        [string] $WebPartMappingFile = $null,
        $Page,
        [bool] $Overwrite = $false,
        [bool] $HandleWikiImagesAndVideos = $true,
        [bool] $ReplaceHomePageWithDefaultHomePage = $false,
        [bool] $TargetPageTakesSourcePageName = $false
    )

    begin
    {

    }

    process
    {
        [bool] $transformOK = $true
        try 
        {            
            # Create the PageTransformationInformation object and populate it
            $pageTransformationInformation = New-Object -TypeName SharePointPnP.Modernization.Framework.Transform.PageTransformationInformation -ArgumentList $Page
            $pageTransformationInformation.Overwrite = $Overwrite
            $pageTransformationInformation.HandleWikiImagesAndVideos = $HandleWikiImagesAndVideos
            $pageTransformationInformation.ReplaceHomePageWithDefaultHomePage = $ReplaceHomePageWithDefaultHomePage
            $pageTransformationInformation.TargetPageTakesSourcePageName = $TargetPageTakesSourcePageName

            # Instantiate the page transformator
            $pageTransformator = $null
            if ($WebPartMappingFile -ne "")
            {
                $pageTransformator = New-Object -TypeName SharePointPnP.Modernization.Framework.Transform.PageTransformator -ArgumentList $Page.Context, "$WebPartMappingFile"
            }
            else 
            {
                $pageTransformator = New-Object -TypeName SharePointPnP.Modernization.Framework.Transform.PageTransformator -ArgumentList $Page.Context, "$PathToModernizationBinaries\webpartmapping.xml"
            }

            # Transform
            $pageTransformator.Transform($pageTransformationInformation)  
        }
        catch [Exception]
        {
            Write-Host $_.Exception.Message -ForegroundColor Red
            $transformOK = $false
        }

    }

    end
    {
        return $transformOK
    }
    
}

#######################################################
# MAIN section                                        #
#######################################################
# variables
$CAMLQueryByExtension = "<View Scope='Recursive'><Query><Where><Contains><FieldRef Name='File_x0020_Type'/><Value Type='text'>aspx</Value></Contains></Where></Query></View>"
$CAMLQueryByExtensionAndName = "<View Scope='Recursive'><Query><Where><And><Contains><FieldRef Name='File_x0020_Type'/><Value Type='text'>aspx</Value></Contains><BeginsWith><FieldRef Name='FileLeafRef'/><Value Type='text'>{0}</Value></BeginsWith></And></Where></Query></View>"

# !!!!!!!!!!!!
# Update below variable to point to the location that holds SharePointPnP.Modernization.Framework.dll and all depending dll's plus the webpartmapping.xml file
$binaryFolder = "C:\github\BertPnPTools\Solutions\SharePoint.Modernization\SharePointPnP.Modernization.Framework\bin\Debug"

# Load the SharePoint Modernization framework
Use-PnPModernizationFramework -PathToModernizationBinaries $binaryFolder

# Connect to site
Connect-PnPOnline -Url https://bertonline.sharepoint.com/sites/espctest2 -Verbose

# Get all pages
# [string] $query = $CAMLQueryByExtension

# Get specific aspx page(s)
[string] $query = [string]::Format($CAMLQueryByExtensionAndName, "contentbyquery.aspx")

# Load the pages
$pages = Get-PnPListItem -List SitePages -Query $query 

# Modernize the found pages
foreach($page in $pages)
{
    Write-Host "Modernizing " $page.FieldValues["FileLeafRef"] "..."    
    if (Invoke-PnPModernizationPageTransformation -Page $page -WebPartMappingFile "$binaryFolder\webpartmapping.xml" -Overwrite $true)
    {
        Write-Host "Done!" -ForegroundColor Green
    }
}

Page transformation high level architecture

Below picture explains the page transformation in 4 steps:

  1. At the start you need to tell the transformation engine how you want to transform pages and that's done by providing a page transformation model. This model is an XML file which describes how each classic web part needs to be mapped to a modern equivalent. Per classic web part the model contains a list of relevant properties and mapping information. See the Understanding and configuring the page transformation model article to learn more. If you want to understand how classic web parts compare to modern web parts it's recommended to checkout the Classic and modern web part experiences article.
  2. Next step is analyzing the page you want to transform: the transformation engine will break down the page in a collection of web parts (wiki text is broken down in one or more wiki text web parts) and it will try to detect the used layout.
  3. The information retrieved from the analysis in step 2 is often not sufficient to map the web part to a modern equivalent and therefor in step 3 we'll enhance the information by calling functions: these functions take properties retrieved in step 2 and generate new properties based upon the inputted properties from step 2. After step 3 we have all the needed information to map the web part...except we optionally need to call the defined selector to understand which mapping we'll need in case one classic web part can be mapped to multiple client side configurations.
  4. The final step is creating and configuring the client side page followed by adding the mapped modern client side web parts to it.

page transformation

See also