---
title: "Migrate your Content SDK 1.3.1 Pages Router app to App Router"
slug: "migrate-your-content-sdk-1-3-1-pages-router-app-to-app-router"
abstract: "Migrate a Content SDK 1.3.1 Pages Router application to use App Router."
---
# Migrate your Content SDK 1.3.1 Pages Router app to App Router

This topic takes you through the steps required to migrate your Pages Router application to App Router. The topic assumes that you're using Content SDK Pages Router application on version 1.3.1. Content SDK 1.3.1 includes the general release of Next.js App Router and several other improvements and bug fixes. Note that version 1.3.0 was released and deprecated due to a critical vulnerability related to React Server Components.

> [!important]
> **Before you begin**
> -   Ensure that you're using Pages Router on Content SDK 1.3.1 before following this guide. If you haven't already upgraded to Content SDK 1.3.1, [follow the upgrade guide](https://doc.sitecore.com/sai/en/developers/content-sdk/10/upgrade-content-sdk-1-2-0-next-js-apps-to-version-1-3-1.html "Upgrade Content SDK 1.2.0 Next.js apps to version 1.3.1").
>
> -   Review the changelogs for [1.3.0](https://github.com/Sitecore/content-sdk/releases/tag/v1.3.0) and [1.3.1](https://github.com/Sitecore/content-sdk/releases/tag/v1.3.1). If your application is heavily customized, the changelog can provide guidance on what additional changes you need that are not covered in this topic.

This topic covers the following subjects:

1. [Create a template App Router application](https://doc.sitecore.com/sai/en/developers/content-sdk/10/migrate-your-content-sdk-1-3-1-pages-router-app-to-app-router.html#UUID-0241690a-10bc-efa9-6cf9-e76b392b0ffd_section-id235236416145565 "Create a template App Router application")
2. [Update the Next.js template files in your existing app](https://doc.sitecore.com/sai/en/developers/content-sdk/10/migrate-your-content-sdk-1-3-1-pages-router-app-to-app-router.html#UUID-0241690a-10bc-efa9-6cf9-e76b392b0ffd_section-id235236419785615 "Update the Next.js template files in your existing app")

## Create a template App Router application

To simplify the migration process as much as possible, create a Content SDK Next.js App Router application. You can then copy some files from this template app into your existing app.

To scaffold the new App Router template app, run the following command:

```shell
npx create-content-sdk-app@latest "nextjs-app-router"
```

## Update the Next.js template files in your existing app

The following sections explain how to synchronize files in your existing Content SDK application with corresponding files from the template App Router app.

### Update core Content SDK files

This sections updates your existing app's core files to match the new Content SDK app.

To update your core Content SDK files:

1. Start the migration by moving your logic into the new file structure. Copy the `src/app` folder from the sample App Router app into the app you are upgrading. Check the table below and take note of any custom logic in these files and the places this custom logic should move to:

    <table style="width:100%;" class="informaltable frame-void rules-rows"><thead><tr><th class="th"><p>Existing file</p></th><th class="th"><p>New file</p></th></tr></thead><tbody><tr><td class="td"><p><code class="code">pages/_app.tsx</code></p></td><td class="td"><p><code class="code">app/layout.tsx</code> / <code class="code">app/[site]/layout.tsx</code></p><p>If <code class="code">_app.tsx</code> has been importing CSS rules previously, move the import into <code class="code">app/globals.css</code></p><p>If you’ve been using Tailwind CSS, consider <a class="link" href="https://tailwindcss.com/docs/installation/using-postcss" target="_blank" rel="noopener">enabling it via PostCSS in the App Router app</a>.</p><p>The <a class="link" href="https://github.com/Sitecore/xmcloud-starter-js/tree/main/examples/kit-nextjs-skate-park" target="_blank" rel="noopener">Skate Park starter sample</a> in the <a class="link" href="https://github.com/Sitecore/xmcloud-starter-js" target="_blank" rel="noopener">xmcloud-starter-js</a> repository contains an example on how to do it.</p></td></tr><tr><td class="td"><p><code class="code">pages/[[…path]].tsx</code></p></td><td class="td"><p><code class="code">app/[site]/[…path]/page.tsx</code></p></td></tr><tr><td class="td"><p><code class="code">pages/500.tsx</code></p></td><td class="td"><p><code class="code">app/global-error.tsx</code></p></td></tr><tr><td class="td"><p><code class="code">pages/404.tsx</code></p></td><td class="td"><p><code class="code">app/[site]/[…path]/not-found.tsx</code></p></td></tr><tr><td class="td"><p><code class="code">pages/api/%path%/%api-route%.tsx</code></p></td><td class="td"><p><code class="code">app/api/%path%/%api-route%/route.ts</code></p></td></tr></tbody></table>

    If there’s no custom logic, remove the old files and keep the files from the newly scaffolded app into your existing app.

2. If you haven't customized `src/Bootstrap.tsx`, replace it with the one from the sample App Router app. Otherwise, make the following changes:
    - Add the following at the start of the file:

        ```ts
        'use client';
        ```

    - Remove the following import statement:

        ```ts
        import { SitecorePageProps } from '@sitecore-content-sdk/nextjs';
        ```

    - Find the following:

        ```ts
        const Bootstrap = (props: SitecorePageProps): JSX.Element | null => {
          const { page } = props;
        ```

    - Replace it with the following:

        ```ts
        const Bootstrap = ({
          siteName,
          isPreviewMode,
        }: {
          siteName: string;
          isPreviewMode: boolean;
        }): JSX.Element | null => {
        ```

    - Find the following `useEffect` portion:

        ```ts
        useEffect(() => {
          if (!page) {
            return;
          }

          const mode = page.mode;
          if (process.env.NODE_ENV === 'development') {
            console.debug('Browser Events SDK is not initialized in development environment');
          } else if (!mode.isNormal) {
            console.debug('Browser Events SDK is not initialized in edit and preview modes');
          } else {
            if (config.api.edge?.clientContextId) {
              CloudSDK({
                sitecoreEdgeUrl: config.api.edge.edgeUrl,
                sitecoreEdgeContextId: config.api.edge.clientContextId,
                siteName: page.siteName || config.defaultSite,
                enableBrowserCookie: true,
                // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com"
                cookieDomain: window.location.hostname.replace(/^www\./, ''),
              })
                .addEvents()
                .initialize();
            } else {
              console.error('Client Edge API settings missing from configuration');
            }
          }
          // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [page?.siteName]);
        ```

    - Replace it with the following:

        ```ts
        useEffect(() => {
          if (process.env.NODE_ENV === "development") {
            console.debug(
              "Browser Events SDK is not initialized in development environment"
            );
            return;
          }

          if (isPreviewMode) {
            console.debug(
              "Browser Events SDK is not initialized in edit and preview modes"
            );
            return;
          }

          if (config.api.edge?.clientContextId) {
            console.log("✨ Initializing CloudSDK for site:", siteName);
            CloudSDK({
              sitecoreEdgeUrl: config.api.edge.edgeUrl,
              sitecoreEdgeContextId: config.api.edge.clientContextId,
              siteName: siteName || config.defaultSite,
              enableBrowserCookie: true,
              cookieDomain: window.location.hostname.replace(/^www\./, ""),
            })
              .addEvents()
              .initialize();
          } else {
            console.error("Client Edge API settings missing from configuration");
          }
        }, [siteName, isPreviewMode]);
        ```

3. If you haven't customized `src/Layout.tsx`, replace it with the one from sample App Router app. Otherwise, make the following changes:
    - Find the following imports:

        ```ts
        import Head from 'next/head';
        import { Placeholder, Field, DesignLibrary, Page } from '@sitecore-content-sdk/nextjs';
        import SitecoreStyles from 'src/components/content-sdk/SitecoreStyles';
        ```

    - Replace them with the following imports:

        ```ts
        import { Field, Page } from '@sitecore-content-sdk/nextjs';
        import SitecoreStyles from 'components/content-sdk/SitecoreStyles';
        import { DesignLibraryApp } from "@sitecore-content-sdk/nextjs";
        import AppPlaceholder from 'components/content-sdk/Placeholder';
        import componentMap from '.sitecore/component-map';
        ```

    - Find the `RouteFields` interface and make it exportable as shown:

        ```ts
        export interface RouteFields {
          [key: string]: unknown;
          Title?: Field;
        }
        ```

    - Remove the following lines from the `Layout` function implementation, if present:

        ```ts
        const fields = route?.fields as RouteFields;
        ...
        const importMapDynamic = () => import('.sitecore/import-map');
        ...
        <Head>
            <title>{fields?.Title?.value?.toString() || 'Page'}</title>
            <link rel="icon" href="/favicon.ico" />
        </Head>
        ```

    - Find the `DesignLibrary` rendering:

        ```ts
        <DesignLibrary loadImportMap={importMapDynamic} />
        ```

    - Replace it with the following:

        ```ts
        <DesignLibraryApp
            page={page}
            rendering={route}
            componentMap={componentMap}
            loadServerImportMap={() => import(".sitecore/import-map.server")}
        />
        ```

    - Replace all the `Placeholder` usages with `AppPlaceholder`. For example, for `headless-header` placeholder, find the following:

        ```ts
        {route && <Placeholder name="headless-header" rendering={route} />}
        ```

    - Replace it with the following:

        ```ts
        {route && (
            <AppPlaceholder
              page={page}
              componentMap={componentMap}
              name="headless-header"
              rendering={route}
            />
        )}
        ```

4. If you haven't customized `src/Scripts.tsx`, replace it with the one from the sample App Router app. Otherwise, make the following changes:
    - Add the following at the start of the file:

        ```ts
        'use client';
        ```

    - Remove the following import:

        ```ts
        import FEAASScripts from 'components/content-sdk/FEAASScripts';
        ```

    - Remove the following line:

        ```ts
        <FEAASScripts />
        ```

5. Remove `src/NotFound.tsx` if present. Move any custom logic to `app/not-found.tsx`.
6. Copy `src/Providers.tsx` from the sample App Router app into your existing application.
7. In the app's `tsconfig.json` file:
    - Set `target` in `compilerOptions` to `ES2017`.
    - In the `plugins` section, add the `next` plugin as shown:

        ```json
        "plugins": [
          {
            "name": "next"
          }
        ],
        ```

    - In the `paths` section, add the `.sitecore` path as shown:

        ```json
        "paths": {
        ...
          ".sitecore/*": [
            ".sitecore/*"
          ],
        ...
        ```

### Migrate next.config.js to TypeScript

Rename the `next.config.js` file to `next.config.ts` and ensure the following is present in the file in addition to any customizations you may have done:

```ts
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {
  // Allow specifying a distinct distDir when concurrently running app in a container
  distDir: process.env.NEXTJS_DIST_DIR || '.next',
  // Enable React Strict Mode
  reactStrictMode: true,
  // Disable the X-Powered-By header. Follows security best practices.
  poweredByHeader: false,
  // use this configuration to ensure that only images from the whitelisted domains
  // can be served from the Next.js Image Optimization API
  // see https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'edge*.**',
        port: '',
      },
      {
        protocol: 'https',
        hostname: 'xmc-*.**',
        port: '',
      },
    ],
  },
  // use this configuration to serve the sitemap.xml and robots.txt files from the API route handlers
  rewrites: async () => {
    return [
      {
        source: '/sitemap:id([\\w-]{0,}).xml',
        destination: '/api/sitemap',
        locale: false,
      },
      {
        source: '/robots.txt',
        destination: '/api/robots',
        locale: false,
      },
    ];
  },
};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
```

### Update localization

Pages Router Content SDK uses the `I18nProvider` imported from the `'next-localization'` library out of the box to enable localization. App Router uses the `next-intl` package instead.

To migrate to the new implementation:

1. In your existing app's `package.json` file, replace the `"next-localization": "^0.12.0",` dependency with `"next-intl": "^4.3.5",`.
2. Copy the `src/i18n` folder from the sample App Router app into your existing app.
3. Use `NextIntlClientProvider` instead of `I18nProvider` by making the following changes:
    - Find the following import:

        ```ts
        import { I18nProvider } from 'next-localization';
        ```

    - Replace it with the following:

        ```ts
        import { NextIntlClientProvider } from 'next-intl';
        ```

    - Use `NextIntlClientProvider` as a wrapper context provider for your pages and components.
4. Use routing imported from `'src/i18n/routing'` to access routing informatin for your pages.

> [!note]
> Check `src\app\[site]\[locale]\[[...path]]\page.tsx` in the sample App Router app to see an example of the new localization implementation.

### Update SitecoreProvider

Built-in Sitecore contexts were previously applied via two providers (`ComponentPropsContext` and `SitecoreProvider`) as shown:

```ts
<ComponentPropsContext value={componentProps || {}}>
  <SitecoreProvider componentMap={components} api={scConfig.api} page={page}>
    <Layout page={page} />
  </SitecoreProvider>
</ComponentPropsContext>
```

Content SDK 1.3.1 streamlines this with the introduction of the `src/Providers.tsx` file. You can apply required contexts by importing `Providers` as shown:

```ts
import Providers from 'src/Providers';
```

To use them where both providers where applied before:

```ts
<Providers componentProps={componentProps} page={page}>
  <Layout page={page} />
</Providers>
```

> [!note]
> If you need to apply individual providers, you can still do so.

### Update BYOC functionality

If you're using BYOC, make the following changes:

1. In the `src/byoc/index.client.tsx` file, and the following at the top of the file:

    ```ts
    'use client';
    ```

2. In the `src/byoc/index.ts` file:
    - Find the following lines:

        ```ts
        const ClientBundle = dynamic(() => import('./index.client'), {
          ssr: false,
        });
        ```

    - Replace it with the following:

        ```ts
        const ClientBundle = dynamic(() => import('./index.client'));
        ```

### Update placeholders

You must replace `Placeholder` components throughout your app with `AppPlaceholder`.

To replace it for the `headless-header` placeholder in `Layout.tsx`:

1. Find the following:

    ```ts
    {route && <Placeholder name="headless-header" rendering={route} />}
    ```

2. Replace it with the following:

    ```ts
    {route && (
        <AppPlaceholder
          page={page}
          componentMap={componentMap}
          name="headless-header"
          rendering={route}
        />
    )}
    ```

    > [!important]
    > Ensure that you import the component map from `'.sitecore/component-map';` or your custom component-map location. You must also get the `page` prop either from the props passed into the server component or from the Sitecore Context via `useSitecore()` call in client components (`const { page } = useSitecore();`).

### Migrate API routes

If you don’t have any custom routes or modifications to the out-of-the-box Content SDK API routes located under `pages/api`, copy the `/src/app/api` folder and all the files under it from the sample App Router application into your existing application.

For any custom routes or customizations to the out-of-the-box routes, migrate them to the [new route structure introduced by App Router](https://nextjs.org/docs/app/getting-started/route-handlers).

### Update components

App Router provides the ability to fine tune your application by converting your components into server or client ones. To mark the built-in components as client components:

1. For all components under `src/components/content-sdk`, add the following at the start of the file:

    ```ts
    'use client';
    ```

2. You should mark client components appropriately in `component-map.ts` or add them to `component-map.client.ts`. This is only applicable if you're managing your own component-maps.

    To mark your client components in the component-map, use the following example:

    ```ts
    ['Title', {...Title, componentType: 'client'}],
    ```

3. Contexts, such as Sitecore Context, applied via `SitecoreProvider` and other React providers, will be unavailable in server components. Ensure you pass required data as props into server components.
4. You can only pass [serializable props](https://react.dev/reference/rsc/use-server#serializable-parameters-and-return-values) from server components into client components.
5. The `getServerSideProps` function can still work with App Router, but we strongly recommend you utilize [Server Functions](https://react.dev/reference/rsc/server-functions) or [server logic](https://nextjs.org/docs/app/getting-started/fetching-data#server-components) instead.