Migrate Sitecore presentation to Uniform

This guide shows how to migrate presentation layer from Sitecore to Uniform.

  1. Minimize your dependency on Sitecore and keep sourcing content from it. In other words, use Sitecore as a headless CMS. This simplifies migration and provides you with more future options.
  2. Use modern visual experience management tools from Uniform with integrated personalization and A/B testing.
  3. Build experiences with any front-end technology, not constrained by what Sitecore supports. Build with Next.js app router, Remix, etc.
  4. Combine content from other sources within a single component.

In this section you will perform the following migration:

Before migrationAfter migration
ContentManaged in SitecoreManaged in Sitecore
Front-end technologySitecore renderings & layouts built with MVCUniform components (React / Vue)
Layout instructionsSitecore presentation detailsUniform composition
Site deliverySitecore CD instancesCDN / front-end cloud

tip

This tutorial involves writing a bit of code. Uniform provides the finished code if you prefer not to have to write the code yourself.

  1. In Content Editor, navigate to sitecore > system > Uniform > Site Configurations > website > Configuration.

  2. Add an item using the template Configure: Canvas Integration Service...

  3. Set the following fields:

    • ApiKey: Use the value from when you created the Uniform API key
    • ProjectId: Use the project ID value from when you created the Uniform API key
    • Page Component Id: page

    About this step

    The Uniform connector uses these settings to communicate with the Uniform service. The one exception is the Page Component Id. This is a value that's used during the component migration process that you run in the next section.

  4. Publish the site.

info

Before the Canvas-specific settings can be configured, some other steps must be completed. You will return to the site configuration later.

The Uniform Migration Tool serializes the various presentation settings in Sitecore into a format so they can be pushed into Uniform Canvas.

  1. Open a browser window to /sitecore/admin/uniform/canvas/migration/components on your Sitecore instance.

  2. Tick the option Skip components not used on pages included into sitemap (MVC only).

    About this step

    This option ensures that only components that are used in your site are serialized. This setting can significantly reduce the number of items that need to be serialized.

  3. Tick the option Generate zip.

    About this step

    By default, the serialized components are generated as separate files on the Sitecore server. This option results in a zip file being generated that you can download. This enables you to push the serialized components to Uniform using the Uniform CLI.

  4. Click Run serialization.

  5. When the serialization process is finished, a zip file will be sent to your browser. Popup blocking may prevent the file from being available, so check your browser you will see a summary in the DEBUG section.

    popup-blocked
  1. On your local development machine, unzip the zip files to the current directory.

  2. Open a terminal to the folder where you downloaded the zip files from the previous section.

  3. Enter the following command:

    npm i -g @uniformdev/cli @uniformdev/canvas
  4. Create a file named .env

  5. Set the values from when you created the Uniform API key:

    UNIFORM_CLI_PROJECT_ID= CANVAS_CLI_API_KEY=
  6. Enter the following command:

    uniform canvas component push canvas_components
  7. You will see output that indicates 5 components were pushed to Canvas:

    [A] Page (pid: page) [A] Sample MVC Sublayout (pid: sampleMvcSublayout) [A] Sample MVC Layout (pid: sampleMvcLayout) [A] Sample MVC Inner Sublayout (pid: sampleMvcInnerSublayout) [A] Sample MVC Rendering (pid: sampleMvcRendering)
  8. In Canvas you will see the 5 components.

    component-library-after-push
  1. Open a browser window to /sitecore/admin/uniform/canvas/migration/compositions on your Sitecore instance.

  2. Tick the option Generate zip.

  3. Click Run serialization.

  4. When the serialization process is finished, a zip file will be sent to your browser. Popup blocking may prevent the file from being available, so check your browser you will see a summary in the DEBUG section.

  1. In the terminal, enter the following command (you will need to change the folder path where your extracted files are):

    uniform canvas composition push canvas_compositions_################/canvas_compositions
  2. You will see output that indicates 2 compositions were pushed to Canvas:

    [A] Pattern: [Sample/Sample Item] (pid: 76036f5e-cbce-46d1-af0a-4143f9b557aa) [A] Home (pid: 110d559f-dea5-42ea-9c1c-8a5df7e70ef9)
  3. In Canvas, navigate to the composition library.

  4. Click the 1 composition that's displayed.

    composition-library-after-push
  5. Click Save > Publish.

  6. Navigate to the pattern library.

  7. Click the 1 pattern that's displayed.

    pattern-library-after-push
  8. Click Save > Publish.

Now that you have the layout from Sitecore presentation details migrated to Canvas, you need to create an application that uses the layout and implements the front-end components used in that layout.

tip

This example uses Next.js as the front-end framework, but you can use any front-end technology you want. The layout and components defined in Canvas are technology agnostic.

Since you are migrating your site to a new front-end, you must create the web app where you will define that front-end.

  1. Open a command-line interface (CLI).

  2. Enter the following commands:

    npx create-next-app sample-mvc-site cd sample-mvc-site
  3. Create a file named .env

  4. Add a file .env and define the following variables:

    NameValue
    UNIFORM_API_SITENAMEwebsite
    UNIFORM_API_KEYYour Uniform API key. Use the same value as you configured on the Canvas Integration Service in Sitecore.
    UNIFORM_PROJECT_IDYour Uniform Project ID. Use the same value as you configured on the Canvas Integration Service in Sitecore.
    UNIFORM_API_URLYour Sitecore URL. Use the same value as you configured in the Uniform integration.
    SITECORE_API_KEYYour Sitecore API key or any GUID if you use Sitecore 8.2. Use the same value as you configured in the Uniform integration.
    UNIFORM_API_TOKENYour Uniform API token

    The file will look like the following:

    UNIFORM_API_TOKEN= UNIFORM_PROJECT_ID= UNIFORM_API_URL= UNIFORM_API_SITENAME=website UNIFORM_API_KEY= SITECORE_API_KEY=
  5. If your Sitecore instance uses self-signed SSL certificate, define the extra variable:

    • Name: NODE_TLS_REJECT_UNAUTHORIZED
    • Value: 0

To add Uniform functionality to your web app, you must add a number of npm packages. Some of these packages are publicly available and some require an npm access token.

Enter the following commands:

npm i dotenv @uniformdev/canvas @uniformdev/canvas-react @uniformdev/cli npm i @uniformdev/canvas-sitecore

You must configure Next.js to load Uniform settings when it runs.

  1. Open the file next.config.js.
  2. Change the contents to the following:
    const { uniformNextConfig } = require('@uniformdev/next-server'); module.exports = uniformNextConfig();

The Sitecore layout and components include some links to static assets in Sitecore. These must be ported to the Next.js application or else you will still have a dependency in your app to your Sitecore CD server.

  1. Get the contents the CSS file by opening your browser to https://your-sitecore-instance/default.css.

  2. In your Next.js app, replace the content of the file styles/globals.css with the CSS from the Sitecore server.

  3. Download the image from https://your-sitecore-instance/-/media/Default%20Website/cover.ashx.

  4. In your Next.js app, copy the image file to the folder public.

  5. In styles/global.css, find the following line:

    background: white url('-/media/Default Website/cover.ashx') no-repeat;
  6. Replace the previous line with the following:

    background: white url('/cover.jpeg') no-repeat;
  7. Download the image from https://your-sitecore-instance/-/media/Default Website/sc_logo.ashx.

  8. In your Next.js app, copy the image file to the folder public.

  9. Download the image from https://your-sitecore-instance/sitecore/images/favicon.ico.

  10. In your Next.js app, copy the image file to the folder public.

You need to create the React components that are used to implement the front-end. This involves porting code from Sitecore to React. The following files on your Sitecore server represent the presentation logic that you will port to React:

ComponentFile
Sample MVC Layout...\Website\Views\Shared\Sample MVC Layout.cshtml
Sample MVC Sublayout...\Website\Views\Sample\SampleMvcSublayout.cshtml
Sample MVC Inner Sublayout...\Website\Views\Sample\SampleMvcInnerSublayout.cshtml
Sample MVC Rendering...\Website\Views\Sample\SampleMvcRendering.cshtml

In this component, the webcontrol sc:placeholder is replaced with a React component UniformSlot. Create a file components/SampleLayout.jsx:

import Head from 'next/head'; import { UniformSlot } from '@uniformdev/canvas-react'; export function SampleLayout() { return ( <> <Head> <title>Welcome to Sitecore</title> </Head> <div id="MainPanel"> <UniformSlot name="main"></Slot> </div> </> ); }

tip

The Sitecore layout also includes a link to the CSS file /default.css, which is located on the Sitecore server at C:\inetpub\wwwroot\default.css. In order for these styles to be applied to your page, the contents of this css file can be added to the React file styles/global.css.

This file includes a style that sets an image. You must change the URL to be fully qualified by adding the hostname for your Sitecore server. For example, https://yoursitecore/-/media/Default Website/cover.ashx.

Create a file components/SampleSublayout.jsx:

import { UniformSlot } from '@uniformdev/canvas-react'; export function SampleSublayout() { return ( <div id="CenterColumn"> <UniformSlot name="centercolumn" /> </div> ); }

Create a file components/SampleInnerSublayout.jsx:

import { UniformSlot } from '@uniformdev/canvas-react'; export function SampleInnerSublayout() { return ( <div id="InnerCenter"> <div id="Header"> <img src="/sc_logo.png" alt="Sitecore" id="scLogo" /> </div> <div id="Content"> <div id="LeftContent"> <UniformSlot name="content" /> </div> </div> <div id="Footer"> <hr className="divider" />&#169; {new Date().getFullYear()} Sitecore </div> </div> ); }

This component displays fields from the Sitecore data source item. If the data source item isn't selected, it uses a context item (a page item) instead. Uniform makes the item available as a prop that's passed to the component. In the next section you will wire this up.

Create a file components/SampleRendering.jsx:

export function SampleRendering({ model }) { return ( <div> <h1 className="contentTitle">{model.Title}</h1> {/* rich text field needs to be rendered via dangerouslySetInnerHTML */} <div className="contentDescription" dangerouslySetInnerHTML={{ __html: model.Text }}></div> </div> ); }

note

The model field name was chosen by a convention that will be explained in next sections.

Each component defined in Canvas has a public ID. When you serialized the components from Sitecore, Uniform generated this value from the Sitecore item name.

When your web app reads composition definitions from Canvas, the composition and the components that are assigned to the composition are identified using this value.

You must provide instructions to your web app on how to map the public ID to the front-end implementation of that component. In your web app, React components provide that implementation.

Create a file lib/resolveRenderer.js:

import { SampleLayout } from '../components/SampleLayout'; import { SampleSublayout } from '../components/SampleSublayout'; import { SampleInnerSublayout } from '../components/SampleInnerSublayout'; import { SampleRendering } from '../components/SampleRendering'; import { DefaultNotImplementedComponent } from '@uniformdev/canvas-react'; const mapping = {}; mapping['sampleMvcLayout'] = SampleLayout; mapping['sampleMvcSublayout'] = SampleSublayout; mapping['sampleMvcInnerSublayout'] = SampleInnerSublayout; mapping['sampleMvcRendering'] = SampleRendering; export const resolveRenderer = (component) => { if (component?.type) { const implementation = mapping[component.type] if (implementation) return implementation; } return DefaultNotImplementedComponent };

In Canvas you have a composition that represents the home page and describes the layout for the page. You have a number of React components that represent the implementation of those components. Now you must configure the Next.js app to use the information from the Canvas composition in order to render the page using the React components you implemented.

Replace the contents of the file pages/index.jsx with the following:

import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; import { resolveRenderer } from "../lib/resolveRenderer"; export default function Home({ composition }) { return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer}> <UniformSlot name="layout" /> </UniformComposition> ); }

About this code

The component UniformComposition accepts props that represent the composition (which is retrieved from Canvas) and a function that resolves the React component that corresponds to the public id for a component from Canvas.

This code also adds a component to render the slot layout. When the Uniform migration tool serializes pages, it creates compositions for Canvas. Each composition includes a slot named layout. All the presentation details that the migration tool extracted from the Sitecore item is included in this slot.

  1. Add the following code to the top of the file pages/index.jsx:
    import { CanvasClient, } from "@uniformdev/canvas"; import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; import { resolveRenderer } from "../lib/resolveRenderer"; async function getComposition(slug) { const client = new CanvasClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const { composition } = await client.getCompositionBySlug({ slug, }); return composition; } export default function Home({ composition }) { return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer}> <UniformSlot name="layout" /> </UniformComposition> ); }
  2. Add the following code to the file pages/index.jsx:
    import { CanvasClient, } from "@uniformdev/canvas"; import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; import { resolveRenderer } from "../lib/resolveRenderer"; async function getComposition(slug) { const client = new CanvasClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const { composition } = await client.getCompositionBySlug({ slug, }); return composition; } export async function getStaticProps() { const slug = "/"; const composition = await getComposition(slug); return { props: { composition }, } } export default function Home({ composition }) { return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer}> <UniformSlot name="layout" /> </UniformComposition> ); }

When you retrieve the composition from Canvas, Sitecore item ids are included in the data. You must use an enhancer to retrieve the fields for those items.

  1. Add the following code to the file pages/index.jsx:
    import { CanvasClient, enhance, EnhancerBuilder, } from "@uniformdev/canvas"; import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; import { createItemEnhancer, getPageItemId, noopLogger, parseUniformServerConfig, } from "@uniformdev/canvas-sitecore"; ...
  2. Add the following code:
    export async function getStaticProps() { const slug = "/"; const composition = await getComposition(slug); const config = parseUniformServerConfig(process.env, noopLogger, true); return { props: { composition }, }; }
  3. Add the following code:
    export async function getStaticProps() { const slug = "/"; const composition = await getComposition(slug); const config = parseUniformServerConfig(process.env, noopLogger, true); const pageId = getPageItemId({ composition }); return { props: { composition }, }; }
  4. Add the following code:
    export async function getStaticProps({ preview }) { const slug = "/"; const composition = await getComposition(slug); const config = parseUniformServerConfig(process.env, noopLogger, true); const pageId = getPageItemId({ composition }); const itemEnhancer = createItemEnhancer({ pageId, config, isPreview: preview, }); return { props: { composition }, }; }
  5. Add the following code:
    export async function getStaticProps({ preview }) { const slug = "/"; const composition = await getComposition(slug); const config = parseUniformServerConfig(process.env, noopLogger, true); const pageId = getPageItemId({ composition }); const itemEnhancer = createItemEnhancer({ pageId, config, isPreview: preview, }); const enhancers = new EnhancerBuilder().component( [ "sampleMvcLayout", "sampleMvcSublayout", "sampleMvcInnerSublayout", "sampleMvcRendering", ], (builder) => builder.data("model", itemEnhancer) ); return { props: { composition }, }; }
  6. Add the following code:
    export async function getStaticProps({ preview }) { const slug = "/"; const composition = await getComposition(slug); const config = parseUniformServerConfig(process.env, noopLogger, true); const pageId = getPageItemId({ composition }); const itemEnhancer = createItemEnhancer({ pageId, config, isPreview: preview, }); const enhancers = new EnhancerBuilder().component( [ "sampleMvcLayout", "sampleMvcSublayout", "sampleMvcInnerSublayout", "sampleMvcRendering", ], (builder) => builder.data("model", itemEnhancer) ); await enhance({ composition, enhancers }); return { props: { composition }, }; }

Live preview enables you to view changes you make in Canvas almost immediately after you save them, without having to manually refresh the page.

  1. Create a file lib/useLivePreviewNextStaticProps.js:
    import { useRouter } from "next/router"; import { useCallback } from "react"; import { useCompositionEventEffect } from "@uniformdev/canvas-react"; export function useLivePreviewNextStaticProps(options) { const router = useRouter(); const effect = useCallback(() => { router.replace(router.asPath, undefined, { scroll: false }); }, [router]); return useCompositionEventEffect({ ...options, enabled: router.isPreview, effect, }); }
  2. Add the following code to the file pages/index.jsx:
    import { CanvasClient, CANVAS_DRAFT_STATE, CANVAS_PUBLISHED_STATE, enhance, EnhancerBuilder, } from "@uniformdev/canvas"; import { UniformComposition, UniformSlot } from "@uniformdev/canvas-react"; import { createItemEnhancer, getPageItemId, noopLogger, parseUniformServerConfig, } from "@uniformdev/canvas-sitecore"; import { useLivePreviewNextStaticProps } from "../lib/useLivePreviewNextStaticProps"; ...
  3. Make the following changes:
    async function getComposition(slug, state) { const client = new CanvasClient({ apiKey: process.env.UNIFORM_API_KEY, projectId: process.env.UNIFORM_PROJECT_ID, }); const { composition } = await client.getCompositionBySlug({ slug, state, }); return composition; }
  4. Make the following changes:
    export async function getStaticProps({ preview }) { const slug = "/"; const state = preview ? CANVAS_DRAFT_STATE : CANVAS_PUBLISHED_STATE; const composition = await getComposition(slug, state); const config = parseUniformServerConfig(process.env, noopLogger, true); const pageId = getPageItemId({ composition }); const itemEnhancer = createItemEnhancer({ pageId, config, isPreview: preview, }); const enhancers = new EnhancerBuilder().component( [ "sampleMvcLayout", "sampleMvcSublayout", "sampleMvcInnerSublayout", "sampleMvcRendering", ], (builder) => builder.data("model", itemEnhancer) ); await enhance({ composition, enhancers }); return { props: { composition }, }; }
  5. Make the following changes:
    export default function Home({ composition }) { useLivePreviewNextStaticProps({ compositionId: composition?._id, projectId: process.env.NEXT_PUBLIC_UNIFORM_PROJECT_ID, }); return ( <UniformComposition data={composition} resolveRenderer={resolveRenderer}> <UniformSlot name="layout" /> </UniformComposition> ); }
  6. In your .env file, add the following variable and set its value so it matches UNIFORM_PROJECT_ID:
    NEXT_PUBLIC_UNIFORM_PROJECT_ID=

When you run the web app, you should see the page from the default Sitecore site, but Sitecore isn't handling page rendering. Sitecore is only acting as a headless CMS.

  1. In the terminal, enter the following command:
    npm run dev
  2. Open your browser to http://localhost:3000

You can get the finished code described in this tutorial on GitHub.