🔀 Custom Routing in XM Cloud Starter Kit: Handling /services/* with a different Layout

 


By default, the Sitecore XM Cloud Starter Kit uses a single dynamic catch-all route ([[...path]].tsx) under src/pages/ to serve all pages. This setup uses Static Site Generation (SSG) and a common layout across the board.

However, in real-world scenarios, you might want specific routes—like /services/*—to:

  • Use a different layout

  • Implement custom data fetching logic

  • Possibly enable Server-Side Rendering (SSR)

In this post, we’ll walk through how to customize routing in the XM Cloud Starter Kit so that URLs like /services/test are handled by a separate catch-all route and layout, completely isolated from the default setup.

❗ This post focuses only on routing and layout customization — not SSR implementation (though this setup will make adding SSR easier later).


🧩 Use Case

Suppose your homepage has a /services/ folder with a few subpages like:

/services/test /services/consulting /services/development

You want:

  • /services/* to be handled differently than the default route

  • A different layout (e.g. without a hero banner or global nav)

  • The ability to fetch content dynamically (e.g. using GraphQL)

Let’s get started.


📁 Step 1: Create Folder Structure

First, we’ll mirror the default route inside a custom folder.

🔧 Create a services folder inside src/pages:

mkdir src/pages/services

📄 Duplicate [[...path]].tsx

📄 Duplicate Layout.tsx

Copy  it  into src/pages/services/.

You should now have:

src/ ├─ pages/ │ ├─ [[...path]].tsx # Default handler │ ├─ services/ │ │ ├─ [[...path]].tsx # Custom handler for /services/*
      ├─ Layout.tsx  





🛡️ Step 2: Update Middleware

To prevent /services/* routes from being caught by the default [[...path]].tsx, you need to update your middleware.ts.

✏️ Modify middleware.ts:Exxclude services/

import middleware from 'lib/middleware';
import type { NextFetchEvent, NextRequest } from 'next/server';

// eslint-disable-next-line
export default async function (req: NextRequest, ev: NextFetchEvent) {
  return middleware(req, ev);
}

export const config = {
  /*
   * Match all paths except for:
   * 1. /api routes
   * 2. /_next (Next.js internals)
   * 3. /sitecore/api (Sitecore API routes)
   * 4. /- (Sitecore media)
   * 5. /healthz (Health check)
   * 6. /feaas-render (FEaaS render)
   * 7. all root files inside /public
   */
  matcher: [
    '/',
    '/((?!api/|_next/|feaas-render|healthz|services/|sitecore/api/|-/|favicon.ico|sc_logo.svg).*)',
  ],
};


This change ensures that requests like /services/test are routed to pages/services/[[...path]].tsx instead of the default handler.


🧠 Step 3: Customize getStaticPaths

Inside pages/services/[[...path]].tsx, update the getStaticPaths method:

export const getStaticPaths: GetStaticPaths = async (_context) => { return { paths: [], fallback: 'blocking', // Pages will be generated on request }; };

Setting fallback: 'blocking' ensures the pages are built when requested — no need to prebuild them during the build process.


⚙️ Step 4: Custom getStaticProps with GraphQL

This is where we dynamically fetch data based on the URL.

export const getStaticProps: GetStaticProps = async (context) => { let props: SitecorePageProps; if (context.params) { console.log("itempath:", context); context.params.requestPath = context.params.path; context.params.path = ['services', '-w-']; // Placeholder to avoid prefetch } const bucketId: string = '{CFC315E3-724C-4214-A599-35D5CD9AD3F7}'; // ID of the folder containing the services pages service items const itemName = context?.params?.requestPath?.[0] || ''; console.log("requestpath:", context?.params?.requestPath); console.log("itemName:", itemName); const result = await GraphQLClient.GetWildcardPage(bucketId, itemName); console.log("customresult:", result); if (!result || !result.items) { // Fall back to standard Sitecore data props = await sitecorePagePropsFactory.create({ ...context, params: { ...context.params }, }); } else { // Use the resolved path from GraphQL props = await sitecorePagePropsFactory.create({ ...context, params: { ...context.params, path: result.items[0]?.url?.path?.toLowerCase(), }, }); } return { props, }; };

What’s happening here?

  • We simulate a dynamic path by modifying context.params.

  • We fetch item data using a custom GraphQL method(GetWildcardPage).

  • If the item exists, we pass its actual URL path to sitecorePagePropsFactory.

Note :Please change the bucket id of the variable based your folder structure. It can be the item id of home page or services folder , as the grapql code will search the item inside  the folder  item id.

const bucketId: string = '{CFC315E3-724C-4214-A599-35D5CD9AD3F7}'; // ID of the folder containing the services pages service items

🔌 Step 5: Create the GraphQL Query

Inside  lib ,create a graphql folder .This folder conatines client.ts file having the GetwildCardpage.Refer to the code repo for details



public static GetWildcardPage = async (
    bucketId: string,
    itemName: string
  ): Promise<SearchResultsWrapper<WildcardSearchResults> | null> => {
    if (!itemName || itemName.length <= 0) {
      return null;
    }

   
    const response = (await GraphQLClient.graphQlClient.request(wildcardPageQuery, {
      bucketId,
      itemName,
    })) as GraphQlSearchResponse<WildcardSearchResults>;
    console.log("reuuesquery:", wildcardPageQuery);
    console.log("responseonject:", response);
    return new SearchResultsWrapper(response);
  };

Types

You can define TypeScript interfaces for your response in:



src/types/graphql.ts

🎨 Step 6: Apply a Custom Layout

To use a different layout for /services/* pages, wrap your page component accordingly.

Example:


This makes your /services/* pages look and behave differently from the rest of your site.


✅ Final Result

When you navigate to:

http://localhost:3000/services/test



You should see:

  • A different layout

  • Custom GraphQL-driven data

  • Dynamically rendered page

  • Logs in the terminal for debugging


📝 Summary

StepWhat You Did
🔧 Folder SetupCreated pages/services/[[...path]].tsx
🛡️ MiddlewareExcluded /services/* from default routing
🧠 Static PathsDisabled pre-building using fallback: 'blocking'
🔌 Data FetchingImplemented custom GraphQL logic



Note: you need to copy below files to make it working. 

1. https://github.com/chandanitme/Wildcard-Routing-xmcloudstarterkit/tree/main/headapps/nextjs-starter/src/pages/services
2. https://github.com/chandanitme/Wildcard-Routing-xmcloudstarterkit/tree/main/headapps/nextjs-starter/src/types
3. https://github.com/chandanitme/Wildcard-Routing-xmcloudstarterkit/blob/main/headapps/nextjs-starter/src/middleware.ts
4. https://github.com/chandanitme/Wildcard-Routing-xmcloudstarterkit/tree/main/headapps/nextjs-starter/src/lib/graphql

Comments

Popular posts from this blog

Solrcloud With Zookeeper -Single server setup

Render Sitecore Experience Forms Using Sitecore XP 10.4 with a Headless Approach (Next.js + JSS SDK)

Next.js with XM Cloud EDGE and GraphQL