Handling Server Error (500) in a Sitecore JSS + Next.js Multisite Setup

“Oh no… it’s a 500!”

That all-too-familiar server error shows up and suddenly your site feels broken. The user sees a generic, ugly message — not the best experience, right?

But what if we could handle server errors gracefully, even in a multisite Sitecore JSS + Next.js environment?

This guide walks you through how to build a proper 500 error page setup that works across multiple sites and hosts — using Sitecore JSS, Next.js, and a touch of smart engineering.

🤔 Why Custom 500 Handling Matters

In large-scale multisite projects, an unhandled 500 means:

  • A broken user experience
  • Lost trust and engagement
  • Confusion about whether the site is down completely

Handling server errors gracefully isn’t just good UX — it’s also good for observability, debugging, and future-proofing your app.

Step 1: Create a 500 Page in Sitecore

Let’s start by creating a dedicated 500 error page within Sitecore Content Editor.

❗ Note: Sitecore JSS does not allow numeric-only routes like /500. Use a name like _500 instead.

  • Create a page under your site’s content tree (e.g., /site/pages/_500)
  • Set the Display Name to “500” (you can set as per the requirement or more content author friendly)

Step 2: Assign the 500 Page in Site Settings

Now that your 500 page exists, link it to the site’s error handling settings:

  1. Go to your Site Settings item in the content tree
  2. Scroll to the Error Handling section
  3. In the Server Error Page field, select the _500 page you created

This is the basic setup for error handling from Sitecore side now it’s time to jump on Next Js for further configuration.

Step 3: Handle Multisite Layout Data via API Route

In a multisite environment, we can’t rely on getStaticProps or hardcoded paths. We need to dynamically resolve the layout data for each site’s 500 page based on the domain.

To solve this, create a custom API route to serve 500 page data per site:

pages/api/error/content.ts

content.ts

import {
  ComponentPropsService,
  GraphQLErrorPagesService,
  LayoutServiceData,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { siteResolver } from 'lib/site-resolver';
import { NextApiRequest, NextApiResponse } from 'next';
import config from 'temp/config';

import clientFactory from 'lib/graphql-client-factory';
import { moduleFactory } from 'temp/componentBuilder';
import { sitecorePagePropsFactory } from 'lib/page-props-factory';

/**
 * Gets the ErrorPages content.  We have to fetch via API to be able to get site-specific content.
 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const domain = req.headers.host;
  let siteName;
  const context = {
    defaultLocale: 'en',
    locale: 'en',
    locales: ['en'],
  };
  // If no store id in cookie, let it grab default in page props factory

  // const rewritePath = getStoreIdRewritePath(, { storeId });
  try {
    const site = siteResolver.getByHost(domain ?? '');
    siteName = site.name;
  } catch {
    siteName = config.sitecoreSiteName;
  }

 
  const serverErrorData = await sitecorePagePropsFactory.create({
    params: {
      path: [`_site_${siteName}`, '_500'],
    },
    locale: context?.locale,
  });

  try {
    const resultErrorPages = await errorPagesService.fetchErrorPages();

    const layoutService = await new ComponentPropsService().fetchStaticComponentProps({
      context: context,
      layoutData: resultErrorPages?.notFoundPage?.rendered as LayoutServiceData,
      moduleFactory: moduleFactory,
    });

    res.setHeader('CDN-Cache-Control', `s-maxage=60, stale-while-revalidate=${60 * 60}`);

    // Cache settings for the browser.
    res.setHeader('Cache-Control', `max-age=60, stale-while-revalidate=${60 * 60}`);

    res.status(200).json({
      ...props,
      componentProps: layoutService,
      serverErrorData: serverErrorData,
    });
  } catch (error) {
    console.error('Error occurred while fetching error pages');
    console.error(error);
    res.status(500).json({ message: 'An error occured' });
  }
}

What’s Happening?

  • Domain-based site detection via a siteResolver
  • Dynamically resolves the 500 error page path per site
  • Fetches layout data and component props needed for rendering
  • Adds smart caching headers for better performance

Now call this API from 500 page.

Step 4: Create 500.tsx in Next.js App

Next, create a custom 500 error page that uses the API route we just built.

pages/500.tsx

import Head from 'next/head';
import Layout from 'src/Layout';
import { componentBuilder } from 'temp/componentBuilder';
import { useEffect, useState } from 'react';
import { SiteName } from 'src/helpers/Constants';
import { sitecorePagePropsFactory } from 'lib/page-props-factory';
import { SitecorePageProps } from 'lib/page-props';
import {
  ComponentPropsContext,
  ErrorPages,
  LayoutServiceData,
  SitecoreContext,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from '@sitecore-feaas/clientside/types/headless';

const ServerError = (): JSX.Element => (
  <>
    <Head>
      <title>500: Server Error</title>
    </Head>
    <div style={{ padding: 10 }}>
      <h1>500 Internal Server Error</h1>
      <p>There is a problem with the resource you are looking for, and it cannot be displayed.</p>
      <a aria-label="Go to the Home page" href="/">
        Go to the Home page
      </a>
    </div>
  </>
);

const Custom500: React.FC<SitecorePageProps> = () => {
  const [layoutData, setLayoutData] = useState<LayoutServiceData>();
  const [componentProps, setComponentProps] = useState<ComponentProps>();
  const [fetchError, setFetchError] = useState(false);
  const [componentContextData, setcomponentContextData] = useState<ComponentContextData>({});
 

  useEffect(() => {
    // Only execute if we didn't get data from the default.
    // Depending on requirements, we may want to always fetch.
    if (!layoutData) {
      fetch('/api/error/content')
        .then(async (res) => {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const data = (await res.json()) as ErrorPages | null;
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          if ((data as any)?.layoutData) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            setComponentProps((data as any)?.componentProps);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            setLayoutData((data as any)?.serverErrorData?.layoutData);
            window.history.replaceState(null, '', '/500');
          } else {
            // There was no custom error page, render the fallback.
            setFetchError(true);
          }
        })
        .catch(() => {
          // There was an error, render the fallback.
          setFetchError(true);
        });
    }
  }, []);

  if (fetchError) {
    return <ServerError />;
  }

  if (!layoutData || !componentProps) {
    return <></>;
  }

  return (
    <ComponentPropsContext value={componentProps}>
      <SitecoreContext
        componentFactory={componentBuilder.getComponentFactory()}
        layoutData={layoutData}
      >
        <Layout layoutData={layoutData} headLinks={[]} />
      </SitecoreContext>
    </ComponentPropsContext>
  );
};

What’s Going On Here?

  • The component fetches the 500 page layout dynamically from our API route
  • It renders your Sitecore-managed 500 content — with all the branding, components, and context you expect
  • If the API fails or data is missing, it gracefully falls back to a hardcoded static message

Tips

  • Cache Strategically: Set CDN and browser cache headers for the error API route to reduce load under high-traffic conditions.
  • Localization Support: Add locale detection and translation support in both API and client-side rendering.
  • Telemetry: Log 500 page hits to Application Insights, Sentry, or another observability tool for better incident tracking.
  • Fallback Component: Keep a styled and branded fallback error component for when even your error page fails.

If you found this helpful, let me know or feel free to share your own approach! Happy coding 👨‍💻👩‍💻

Part 2: Optimizing Sitecore Context Injection with Caching

In Part 1, we saw how to inject global configuration data into the Sitecore context using a page-props-factory-plugin. While this reduces redundancy, repeatedly fetching this data from an external source can still impact performance. In this post, we’ll introduce a caching layer to avoid unnecessary API calls and further optimize your Sitecore JSS application.

Why Add Caching?

Even if data is only fetched once per page load, doing so for every request can add latency. Caching allows you to:

  • Prevent duplicate fetches
  • Reduce server load
  • Improve response times

Step-by-Step: Add Caching to Your graphQl call

Step 1: Create a Cache Helper

Create a reusable utility to cache API responses in memory:

import cache from 'memory-cache';
export async function getOrSetCache<T>(
  cacheKey: string,
  getData: () => Promise<T>,
  duration = 5 * 60 * 1000 
) {
  const cachedResponse = cache.get(cacheKey) as T | null;

  if (cachedResponse !== null) {
    return cachedResponse;
  }
  const response = await getData();
  cache.put(cacheKey, response, duration);
  return response;
}
export async function getCache<T>(cacheKey: string) {
  const cachedResponse = cache.get(cacheKey) as T | null;
  return cachedResponse;
}

Step 2: Integrate It into Your Plugin

Modify your GraphqlDataPlugin to use the cache:

const cacheKey = `GraphqlDataPlugin:${siteName}:${language}`;
const [configuration] = await getOrSetCache(cacheKey, () =>
  Promise.all([
    getConfigData(siteName, language),
  ])
);
const cacheKey = `GraphqlDataPlugin:${siteName}:${language}`;

This line constructs a unique identifier—called a cache key—used to store and retrieve data from an in-memory cache efficiently.

Breaking It Down

  • GraphqlDataPlugin: — A prefix to namespace the cache key, ensuring it doesn’t collide with other keys in memory that may be used elsewhere in your application.
  • ${siteName} — Differentiates data based on the specific Sitecore site being rendered. Useful in multi-site environments.
  • ${language} — Ensures that cached data respects the language context (e.g., English vs. French), which is a common requirement in multilingual Sitecore implementations.

Now, configuration data is cached and only fetched if it’s not already available.

Example

If you’re rendering the site called storefront in the en language, the resulting key would be:

GraphqlDataPlugin:storefront:en

This ensures:

  • Cached configuration is scoped to the correct site and language
  • Avoids unnecessary duplicate GraphQL calls for repeated page loads
  • Ensures fast data retrieval from memory if the same data was already fetched within the cache duration

This approach optimizes performance, reduces backend load, and ensures contextually correct data is served across your Sitecore JSS app.

Conclusion: Context + Caching = Performance

By combining context-based data injection with in-memory caching, you achieve both scalability and performance. Your application benefits from:

  • Cleaner architecture
  • Faster load times
  • Reduced API usage

This setup is ideal for enterprise Sitecore JSS apps where global configuration data must be consistent and efficient. Together, the context and caching strategy elevate your application’s robustness and maintainability.

Part 1: Enhancing Sitecore JSS with Global Context Data Injection

When developing with Sitecore JSS, managing global data efficiently becomes a critical consideration for enterprise applications. While component-level GraphQL queries and page-specific resolvers have their place, there are scenarios where you need consistent data available across your entire application without repetitive fetching. This article explores how to enhance your Sitecore implementation by customizing the Sitecore context to include global configuration data, resulting in a more efficient and maintainable codebase.

Why Customize Sitecore Context Data?

Modern web applications often require global settings and configuration that need to be accessible throughout the application. Rather than fetching this data repeatedly at the component level or passing it down through numerous props, injecting it directly into the Sitecore context provides a clean solution.

By leveraging middleware plugins or page-props-factory plugins, you can seamlessly integrate data from various sources—GraphQL, OrderCloud, custom APIs, or even static data—into the Sitecore context. This data then becomes accessible application-wide via the useSitecoreContext() hook.

Implementation Guide

Let’s implement this approach using a page-props-factory-plugin. This allows us to intercept and enhance the page props before they’re delivered to the page components.

Step 1: Create a Plugin File

Navigate to /src/lib/page-props-factory/plugins and create a new TypeScript file for your context data resolver. In this example, we’ll create graphql-data.ts to fetch account settings for authentication purposes:

import { SitecorePageProps } from 'lib/page-props';
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next';
import { Plugin } from '..';
import { ConfigurationData, getConfigData } from '../graphql/configuration';

class GraphqlDataPlugin implements Plugin {
  order = 1.5; // define the execution order

  async exec(
    props: SitecorePageProps,
    _context: GetServerSidePropsContext | GetStaticPropsContext
  ) {
    const siteName = props.site.name; //sitename
    const language = props.layoutData.sitecore.context.language ?? 'en';  //language
    //if data is not available in cache then it will be new call
    const configuration = await getConfigData(siteName, language)
		//finally set data on sitecore context
    props.layoutData.sitecore.context.graphQlData = {
      configuration,
    };

    return props;
  }
}

//types for the response
export interface GraphQlData {
  configuration: ConfigurationData | null;
}

export const graphqlDataPlugin = new GraphqlDataPlugin();

This plugin implements the Plugin interface, which requires:

  • An order property to define execution priority
  • An exec function that runs during page props generation

Step 2: Configure Data Retrieval

Create a dedicated configuration file to handle GraphQL queries. In this example, we’re retrieving account settings:

import graphqlClientFactory from 'lib/graphql-client-factory';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ConfigurationData = Record<string, any>;

export async function getConfigData(
  siteName: string,
  language: string
): Promise<ConfigurationData> {
  const path = `/sitecore/content/[your_tenentName]/${siteName}/Data/Commerce/Accounts`;

  const gqlClient = graphqlClientFactory({});
  const response = await gqlClient.request<ConfigurationGraphQLResponse>(accountSettingGraphQl, {
    path,
    language,
  });

  const responseObj: ConfigurationData = {};
  response?.item?.fields.forEach(({ name, value }) => {
    responseObj[name] = value;
  });
  return responseObj;
}

//define a response type
type ConfigurationGraphQLResponse = {
  item: {
    fields: {
      name: string;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      value: any;
    }[];
  };
};

//graphQL query 
const accountSettingGraphQl = `
    query AccountsSetting($path: String!, $language: String!) {
    item(path: $path, language: $language) {
      fields(ownFields: true) {
        name
        value
      }
    }
  }
  `;

After this you just need to save you changes and run the npm command npm run start:connected

After page load you can see you data on console using __NEXT_DATA__

Step 4: Access Your Data

After implementing the plugin, the data becomes available in your components through the useSitecoreContext hook:

import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';

const MyComponent = () => {
  const { sitecoreContext } = useSitecoreContext();
  const graphQlData = sitecoreContext.graphQlData
  
  // Access your configuration data
  const configuration = graphQlData?.configuration;
  
  return (
    // Your component JSX
  );
};

Conclusion: A Foundation for Scalable Context Management

By customizing the Sitecore context with global data, you set the foundation for a scalable, maintainable application. This approach empowers developers to access configuration data across the entire app without excessive fetch calls or deeply nested props. In the next part, we’ll take this further by implementing a smart caching mechanism to improve performance.

Happy Coding 🎉

Sitecore Setup

Enable Internet Information Services (IIS)

The first step is to enable the Internet Information Services (IIS) server on your machine. Here’s how you can do it:

  1. Navigate to the Control Panel on your Windows machine.
  2. Click on “Programs.”
  3. Select “Turn Windows Features On or Off.”

You’ll see a pop-up window with a list of features. Ensure the following checkboxes are enabled:

Install WebDeploy

Download and install the WebDeploy_amd64_en-us from the below link:

https://www.microsoft.com/en-us/download/details.aspx?id=43717

Install .NET Framework and .NET SDK

Download and install the .NET Framework and SDK from the below link:

https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48

Download runtime and developer pack both.

Install SQL Server

Sitecore uses SQL Server for its database. Follow these steps to install SQL Server (Express version):

https://www.microsoft.com/en-us/sql-server/sql-server-downloads

During the installation, select the Custom installation option. Set the path for the setup files and proceed with the installation.

We willl set the path of the setup files to be downloaded and then click the install button:

The downloading process will be started.

Now, we will select the New SQL Server stand-alone installation or add features to an existing installation option to start the installation:

Accept the license terms

The Global Rules screen will be launched and, the SQL Server Setup will be checked the computer configurations:

The Product Update screen checks the latest updates of the SQL Server Setup

In the Install Rules screen click the Next button.

A new page is the Azure Extension for SQL Server, Uncheck on Azure Extension for SQL Server check box if it is checked :

In the Feature Selection screen select the following Features:

  • Database Engine Services
  • SQL Server Replication
  • Or you can chek all if needed.

In the Instance Configuration screen, we will give a name to the SQL instance but we will not change the default name of the installation. Click the Next button:

Then you can configure the service account. Typically I leave everything as the default.

In the Database Engine Configuration screen select the Mixed Mode and provide the password (store the password for future requirement.) which we will us in SQL server management studio to login sa user. And click the Next button.

We’re almost done. We get a final overview and then we can click on the Install button.

Grab a Tea or Coffee of your choice and wait till the installation finishes.

After some time, the setup (hopefully) finishes successfully. You might get the pop-up window for restart your PC.

Install SQL Server Management Studio 2019

Download and install the SQL Server Management Studio 2019 from the below link:

https://aka.ms/ssmsfullsetup

Select Install

To enable sa user in SSMS follow below page:

https://www.getfishtank.com/blog/enable-sa-user-in-sql

Test connection to SQL Server Express

Once you install and enable the sa user check with it login credentials.

Download Sitecore 10.3 xp

In below link you will find the Setup of Sitecore 10.3xp as well lincence file and all the other setup which are requires to install Sitecore in your machine.

Unzip the sitecore 10.3 package.

  1. Double-click on the Sitecore setup you will see the below screen.
  1. On Prerequisite screen click on Install button. This will install various components, such as .Net Framework 4.8.0, essential Windows Server prerequisites including IIS, as well as other required modules.

  1. Solr search service and then proceed by clicking the Install button. This action will initiate the installation of Solr version 8.11.2 on your system.
  • Solr Port: 8983 (Ensure the port is available and not in use)
  • Windows Service and Path Prefix: You can choose your preferred prefix
  • Installation Path: Default is C:\Solr (You can browse your preferred path)

Once the Solr search service successfully installed click on Next .


  1. Input the Sitecore configurations and then proceed by selecting the Next .
  • Installation Prefix: Name of the Sitecore instance
  • Sitecore Admin Password: Use ‘b‘ 😄 or as your preferance.
  • Sitecore License File: Choose the license file which is already in the setup folder

  1. Input the SQL server details and proceed by clicking Next.
  • SQL Server Name: SQLSERVER or whatever your SQL server name is.
  • Microsoft SQL Server Admin User Name: sa
  • Microsoft SQL Server Admin Password: [Enter the password]

  1. Solr details will be auto-populated. If not, enter the SOLR details and click Next.
  • Solr Service URL – Local Solr URL, For example – https://localhost:8983/solr
  • Solr file system root – Go to C:\Solr or the path you used earlier and copy solr path

  1. If necessary, you can choose to select the checkbox for Sitecore SXA modules, and then proceed by clicking Next.

  1. Examine the summary and then proceed by selecting Next.

  1. Validate and click on Install

  1. Once the installation completed click on Next.

Now click on Launch Sitecore.

Now, access Sitecore by logging in using the administrator password you provided in 4th step:

That’s it! You’re now ready to explore Sitecore and start building applications. Enjoy your Sitecore journey! 🚀🎉

Implementing Range Facets in Sitecore Search with Next.js

Range facets are powerful tools for filtering numerical data like prices, dates, or quantities in your search interface. This guide walks through implementing a range facet in Sitecore Search and integrating it with a Next.js application.

Part 1: Configuring Range Facets in Sitecore Search

In first step , we will create a “range” facet in Sitecore Search, In my dataset I have party_price attribute field, which is already configured within Sitecore Search. These attributes are readily accessible through the Domain Settings,

which you can find from Administration → Domain Settings → Attributes

Then you need to select a product entity or All from Entity dropdown

Click on party_price field

you will find some configuration inside it

To use this field as facet, navigate to the Use For Features and check the Facet checkbox

Save the changes and publish it. (Most forgettable 😄)

Now, our next step is to configuring our Facet , so for that we will again navigate to Domain settings and then click on the Feature Configuration

You can see list features like, API Response, Facets, Filters, Personalization etc. As we want to configure the Facet will navigate to the Facet feature.

From the facet list you can find the newly created facet in my case It will be party_price

click on the party_price facet for configuration.

  1. Ensure that:
    • AGGREGATION TYPE is set to Histogram.
    • OPERATOR is set to Dynamic OR.
  2. Save and publish the changes.

I’m skipping the Sitecore Configuration part as this is all about the “Range” facet. (I’ll cover in Facet configuration part).

This is all from Siirecore Search facet configuration. Now we’ll jump into Next js configuration part.

Part 2: Implementing Range Facets in Next.js

Configure Search Results Widget:

To setup the range filter first of all you need to pass the config while calling the useSearchResults widget.

const searchResults = useSearchResults<ProductSearchResultModel, InitialState>({
    config: {
      facets: {
        party_price: { type: 'range' },
      },
    },

in above code snippet you can see I have configured the party_price as type of range. If you are not configure the specific type it would behave differently !

Create the Range Facet Component:

Now we will create the Price Facet Component where we will implement our facet things.

const PriceFacet = ({ facet }: FacetProps) => {
  const min = Math.floor(facet?.value?.[0].min);
  const max = Math.floor(facet?.value?.[facet?.value.length - 1].max);
  return (
    <SearchResultsFacetValueRange
      max={max}
      min={min}
      autoAdjustValues={false}
      className={clsx(styles['sitecore-range-facet-root'], '!mt-4')}
    >
      <RangeFacet.Track
        className={clsx(
          styles['sitecore-range-facet-track'],
          '!block !bg-color-brand-primary-1-base !h-2'
        )}
      >
        <RangeFacet.Range className={clsx(styles['sitecore-range-facet-range'])} />
      </RangeFacet.Track>
      <RangeFacet.Start
        className={clsx(
          styles['sitecore-range-facet-start'],
          'hover:!bg-white hover:!border-[1px] hover:!border-color-brand-primary-1-base '
        )}
      >
        {(value) => <span className="!top-[-20px]">${value}</span>}
      </RangeFacet.Start>
      <RangeFacet.End
        className={clsx(
          styles['sitecore-range-facet-end'],
          'hover:!bg-white hover:!border-[1px] hover:!border-color-brand-primary-1-base '
        )}
      >
        {(value) => <span className="!top-[-20px]">${value}</span>}
      </RangeFacet.End>
    </SearchResultsFacetValueRange>
  );
};

You can also override the style based on you desiIn requirement in is codebase i’m using some tailwind 🧑🏻‍🎨 classes for customisation.

Render the Range Facet Conditionally:

As this is range facet, for rendering it we have to do one additional check while rendering the regular facet.

<AccordionFacets.Facet
      facetId={facet.name}
      key={facet.name}
      className={facetRoot({ className: 'border-b-[0px]' })}
    >
      <FacetHeader facet={facet} actions={actions} settings={settings} />
      <AccordionFacets.Content>
        {facet.name === 'party_price' ? (
          <PriceFacet facet={facet} actions={actions} settings={settings} />
        ) : (
          <StandardFacet facet={facet} actions={actions} settings={settings} />
        )}
      </AccordionFacets.Content>
    </AccordionFacets.Facet>

As of now i’m compering with the static value, but there is also an other way to do this.

Build a Custom Range Label:

Here some more things to keep in your note that we also have to create separate label for the range it will not work like other facet.

for that you can export the function and can reuse it.

export const buildRangeLabel = (min: number | undefined, max: number | undefined): string => {
// Constructs the label string for the range (e.g., "$10 - $100", "< $10", "> $100").
  return typeof min === 'undefined'
    ? `< $${max}`
    : typeof max === 'undefined'
    ? ` > $${min}`
    : `$${min} - $${max}`;
};

export const buildFacetLabel = (selectedFacet: any, overridenLabel: string) => {
  if ('min' in selectedFacet || 'max' in selectedFacet) {
    return `${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`;
  }
  return `${overridenLabel}`;
};

here i’m also passing the overridenLabel if you want to override the existing label from Sirecore CMS side. otherwise you can remove it.

Implement a Custom Facet Header:

as I’m using separate FacetHeader component i’ll be going use buildFacetLabel for dynamic label. (which are shown as selected facet at top of the facet section)

//Implement a Custom Facet Header
function FacetHeader({ facet, actions, settings }: FacetProps) {
  const selectedFacets = useSearchResultsSelectedFacets();
  const selectedFacetIds = selectedFacets.map((x) => x.id);
  const isFacetSelected = selectedFacetIds.includes(facet.name);
  const dictionary = useDictionary();
  return (
    <AccordionFacets.Header className={'flex flex-col justify-between'}>
      <AccordionFacets.Trigger className={styles['sitecore-accordion-trigger'] + ' group'}>
        <span className={facetLabel()}>
   -->     {buildFacetLabel(selectedFacets, settings?.facetLabelOverride || facet.label)}
        </span>
        {isFacetSelected ? (
          <span
            className={clearLabel()}
            // This is a span instead of a button because we are already inside a button
            onClick={(e) => {
              e.stopPropagation();
              return actions.onRemoveFilter({ facetId: facet.name, type: 'range' });
            }}
          >
            {dictionary.getDictionaryValue('ClearFacetPLP', 'Clear')}
          </span>
        ) : null}
        <IconHelper className={facetIcon()} icon={'chevron-down'} />
      </AccordionFacets.Trigger>
    </AccordionFacets.Header>
  );
}

Final result 🥳

You can also deep dive some additional configuration ⚙️

Default facet selection

To set the selected filter on the url (so, you can use as default selected filter on page load), we can do something like:

function facetToUrl(selectedFacets: FacetValue[]) {
// Converts selected facets to a URL hash string.  Handles range facets specifically.
  if (!selectedFacets.length) {
    return '';
  }
  const facets: Record<string, string> = {};
  selectedFacets.forEach((facet) => {
    let value = facet.valueLabel ?? facet.facetValueText;
    if (facet?.type === 'range') {
      value = `${facet?.min}-${facet.max}`;
    }
    // Depending on when it's called, sometimes it comes as facet.valueLabel, other times it's facet.facetValueText
    if (!value) {
      return;
    }
    const key = FACET_PREFIX + facet.facetId;
    if (!facets[key]) {
      facets[key] = '';
    } else {
      facets[key] += '|';
    }
    facets[key] += value;
  });
  const params = new URLSearchParams(facets);

  return params.toString();
}

//Hook to keep facets synchronized with the URL.
export const useEnsureFacetUrl = (
  actions: SearchResultsWidget['ActionProps'],
  facets: SearchResponseFacet[]
) => {
  const router = useRouter();
  const selectedFacetsFromApi = useSearchResultsSelectedFilters();

  const selectedFacetsFromApiUrl = decodeURI(facetToUrl(selectedFacetsFromApi));

  const [prevFacetUrl, setPrevFacetUrl] = useState<string>();

  useEffect(() => {
    if (prevFacetUrl === undefined) {
      setPrevFacetUrl(router.asPath.split('#')[1] ?? '');
    } else {
      if (prevFacetUrl !== selectedFacetsFromApiUrl) {
        setPrevFacetUrl(selectedFacetsFromApiUrl);
        router.push(
          {
            pathname: window.location.pathname,
            // window.location.search includes the '?' if there is a querystring.
            // This caused extra '?' to be added each time.
            // If there is no querystring, there is no '?' so this issue wasn't caught earlier.
            query: window.location.search.replace(/^\\?/, ''),
            hash: selectedFacetsFromApiUrl,
          },
          undefined,
          { scroll: false }
        );
      }
    }
  }, [actions, facets, prevFacetUrl, router, selectedFacetsFromApiUrl]);
};

final step for set up the selected facet in url is using this hook inside your search component

// In your search component:
// To ensure that facets are synced with the URL:
  useEnsureFacetUrl(actions, facets);

to use as default selection you need one more configuration to convert url to facet value

export function urlToFacet(hash: string) {
  const query = new URLSearchParams('?' + hash);

  const facets: FacetValue[] = [];

  for (const [key, value] of query.entries()) {
    if (!key.startsWith(FACET_PREFIX)) {
      continue;
    }
    const facetId = key.split(FACET_PREFIX)[1];
    const valueArray = value.split('|');
    valueArray.forEach((x) => {
      if (facetId == filterByPrice.partyPrice) {
        const minMax = x?.split('-');
        facets.push({ facetId, min: Number(minMax[0]), max: Number(minMax[1]) });
      } else {
        facets.push({ facetId, facetValueText: x });
      }
    });
  }
  return facets;
}

use use it you can call it from useSearchResults in state config as selectedFacets

 state: () => {
      const hasWindow = typeof window !== 'undefined';
      const hash = hasWindow ? window.location.hash.replace(/^#+/, '') : '';
      const facetsFromUrl = urlToFacet(hash); // Get selected facets from the URL.
      return {
        selectedFacets: facetsFromUrl, //Set these as the initial selected facets.
      };
    },
    

Conclusion

With this implementation, you’ll have a fully functional range facet in your Sitecore Search-powered Next.js application. The range facet supports:

  • Dynamic min/max values
  • Visual range slider
  • URL synchronization
  • Clear formatting of selected ranges
  • Proper integration with existing facet infrastructure

Happy coding! 🚀

Creating Your First OrderCloud App with Next.js: A Step-by-Step Guide

👋🏻 Hello

In this blog, you’ll take your first step with OrderCloud. You’ll learn some basics and see a demo on how to create your very first app using OrderCloud. So, let’s dive deep and explore the treasures of OrderCloud! 🪜 This is your first step towards mastering OrderCloud.

I’ll guide you through the essential concepts needed to create and connect your first OrderCloud app with Next.js. Let’s get started! 🏃🏻‍♂️

Creating Your First Marketplace

What is a Marketplace?

In the context of commerce, a marketplace is your central hub for managing everything—sellers, buyers, admins, products, and more.

Login/Sign-up in OrderCloud 🔑

You can create a free OrderCloud account and use the sandbox environment.

  1. Open the OrderCloud Portal.
  2. If you already have an account, log in with your credentials. Otherwise, register yourself by clicking here.

After a successful registration, you will be redirected to the OrderCloud Portal and see the OrderCloud Dashboard. Here, you can find previously created marketplaces and create a new one.

Click on the “New Marketplace” button to open the creation screen where you’ll provide essential information such as:

  1. Region
  2. Environment
  3. Marketplace Identifier
  4. Marketplace Name

After filling in the details, click “Create Marketplace”. Your first marketplace is now successfully created, showing necessary information and an option to delete the marketplace.

Next, you will learn about other aspects of OrderCloud, such as security profiles, API clients, buyers, and users.

Security Profile 🔐

Security profiles are collections of roles, each containing specific permissions that control access to various parts of an application. Think of them as predefined sets of keys, each unlocking different doors.

Creating a Security Profile

In the sidebar, find the “API Console” menu where you can interact with OrderCloud.

First, select a context (Marketplace). Choose the practice marketplace we created earlier.

  1. Select the “Security Profile” option.
  2. Click “Create New Security Profile”.

Here, we’ll create a security profile for buyers and assign specific roles, such as accessing the commerce app, creating user profiles, and viewing product lists and details.

  1. Name: Provide a name for your security profile (e.g., BUYER APP).
  2. ID: Provide a unique ID for your security profile.
  3. Assign the necessary roles for a buyer user and click “Create New Security Profile”. I’ve assigned three roles which are essential for a buyer.

API Client

API Clients are unique gateways to your marketplace’s data. They control how different parties (like customers, admins, or suppliers) interact with your data. Each API Client has specific rules and permissions determining who can access what information and how.

Creating an API Client

Let’s create an API Client to access our OrderCloud app.

  1. Go to the “API Client” menu and create a new API Client.

Provide the following information:

  • Name: Provide a name (e.g., BUYER API CLIENT).
  • Enabled: Set to True (default is True, can be disabled for specific purposes).
  • Client Secret (Optional): If provided, it also needs to be configured in your frontend app.
  • Token Duration: Set expiration times for access and refresh tokens (default: 600 minutes for access tokens and 0 for refresh tokens).

Client Access Configuration: Select client access for specific users. Here, I selected “No supplier” (no seller user can use this API Client) and “Allow All buyers” (all Buyer users can use this client ID).

Default Context User: Provide a default user (used to generate the token to access the buyer app) which we will create next.

Anonymous Buyer: Allows anonymous buyer users.

Finally, click on the “Create New API Client” button.

Creating a Buyer and Their User

Buyers

A user group under which you can create users who can access an application. For example, we are end users for companies like Amazon, Flipkart, Myntra, etc. Here, this is a user group for all buyer users, meaning if we assign a security profile to this buyer group, all users created under it will automatically get their roles.

Creating a Buyer Group

To create a Buyer Group:

  1. Navigate to the “Buyer” menu in the sidebar.
  2. Select an option from the API request.

Provide the following information:

  1. ID: Provide a unique ID (e.g., BUYER GROUP).
  2. Active: Set to true (default is false, ensure it’s true to access the end-user app).
  3. Name: Provide a respective name and hit the send button.

You have successfully created a Buyer Group. Now, let’s move to the next step: creating a user for our app.

Assigning a Security Profile to Buyer Group

Assign a Security Profile to the Buyer Group, meaning users created under this group will automatically receive the roles specified in the BUYER APP Security Profile.

Go to the Security Profile and then select the below request

Then Assign BUYER_APP Security profile to the BUYER_GROUP API Client.

Creating a Buyer User

Under the Buyer menu, you can see an option to create a new buyer user.

  • Select a POST request to create a new user.
  • Provide necessary information like Name, ID, Active status, Username, Password, First Name, Last Name, Email, and Phone. There is also an Extended Properties (xp) field for additional information like user image, gender, birthdate, etc.

This user has the AvailableRoles property, provided in our security profile.

😊 Hope you’re enjoying the read. Let’s move to the next step.

Connecting Your Next.js App with OrderCloud

OrderCloud provides a starter kit to easily create a new application. Clone it from the GitHub link.

  1. Open the folder in your favorite IDE.
  2. Go to your project directory.

Folder structure:

Then, Create a new .env file and paste the following variables:

NEXT_PUBLIC_OC_CLIENT_ID=6DAEFB90-A497-4532-907E-9EB5F9EAE7D5 
NEXT_PUBLIC_OC_SCOPE=Shopper,MeAddressAdmin,MeAdmin
NEXT_PUBLIC_OC_BASE_API_URL=https://sandboxapi.ordercloud.io
NEXT_PUBLIC_OC_ALLOW_ANONYMOUS=false

Provide the API Client ID you created for the buyer.

Run the npm run dev command, and open localhost:3000.

If you face an error like below, you might have missed selecting a default context user in your API Client.

To resolve this, assign a default context user in your API Client. I created a new user under the same Buyer Group and assigned it to the API Client.

After restarting the app, you should see the following screen:

Congratulations! You have created your first OrderCloud app.

References

I hope you found this guide helpful.

Next.js Scaffolding Script: Simplifying Component Setup and Configuration

What is Scaffolding?

Scaffolding is the process of generating a component based on predefined templates. Typically, when we create a new component, we start from scratch and follow the same repetitive steps each time. This approach can be time-consuming. Scaffolding, on the other hand, provides a basic template with essential code, saving you the effort of rewriting the same code repeatedly.

How Can We Achieve Scaffolding?

Sitecore JSS provides an out-of-the-box (OOTB) Component Scaffolding feature, but it has some limitations. By leveraging the code provided by Sitecore, we can enhance the scaffolding to include additional files, such as Storybook and mock-data files, when creating a scaffolded component. With some configurations, we can also generate various other files.

To create a new component using scaffolding, use the following command:

jss scaffold ComponentName

or

npm run scaffold ComponentName

Scaffolding Script

You can find the scaffolding script in your Next.js src directory:

/src/[project_name]/scripts/scaffold-component/index.ts

Inside the index.ts file, the first step is to define the component name format. You can modify the regular expression based on your requirements:

const nameParamFormat = new RegExp(/^((?:[\\\\w\\\\-]+\\\\/)*)([A-Z][\\\\w-]+)$/);

This means component names should start with a capital letter and contain only letters, numbers, underscores, or dashes.

const componentArg = process.argv[2];
const args = process.argv.slice(3);
// These lines will process the arguments provided in the CLI command.

Next, specify the default configuration, including the component template, component path, and component name.

const defaultConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateComponentSrc,
args: args,
nextSteps: [],
};

generateComponentSrc is the template name imported from another file.

import generateComponentSrc from 'scripts/templates/component-src';

This is the location of the component template (you can create this file in another location if needed). Finally, you’ll find the implementation of the component execution config.

const config = (Object.values(plugins) as ScaffoldComponentPlugin[])
.sort((p1, p2) => p1.order - p2.order)
.reduce((config, plugin) => plugin.exec(config), defaultConfig);

Component Script

Inside the /src/[project_name]/scripts/scaffold-component/plugins directory, you’ll find the component.ts file, which includes the component configuration.

import path from 'path';
import { scaffoldFile } from '@sitecore-jss/sitecore-jss-dev-tools';
import { ScaffoldComponentPlugin, ScaffoldComponentPluginConfig } from '..';
class ComponentPlugin implements ScaffoldComponentPlugin {
  order = 99;
 exec(config: ScaffoldComponentPluginConfig) {
    const { componentName, componentPath } = config;
    const filename = `${componentName}.tsx`;
    const componentRoot = componentPath.startsWith('src/') ? '' : 'src/components/' + componentName;
    const outputFilePath = path.join(componentRoot, componentPath, filename);
    const template = config.componentTemplateGenerator(componentName);
    const componentOutputPath = scaffoldFile(outputFilePath, template);
    return {
      ...config,
      componentOutputPath,
    };
  }
}
export const componentPlugin = new ComponentPlugin();

This script retrieves the component name and path from the config file, determines the output directory, and uses the scaffoldFile function provided by Sitecore JSS to generate the component based on the template.

Component Template File

Inside the src/[project_name]/scripts/templates directory, you’ll find the component-src.ts file where you can define your component snippet. You can also use the OOTB file. Here’s an example that also includes a Tailwind variant snippet:

function generateComponentSrc(componentName: string): string {
const component = componentName.charAt(0).toLowerCase() + componentName.slice(1);
return `import React from 'react';
import { withDatasourceCheck } from '@sitecore-jss/sitecore-jss-nextjs';
import { tv } from 'tailwind-variants';
import { ComponentProps } from 'lib/component-props';

export type ${componentName}Props = ComponentProps & {
fields: unknown;
};

const ${component}Variants = tv({
slots: {
base: ['${component}'],
},
compoundSlots: [{ slots: [], class: [] }],
variants: {
size: {
mobile: {},
desktop: {},
},
},
}, { responsiveVariants: ['lg'] });
const ${componentName}: React.FC<${componentName}Props> = ({ fields, params }) => {
const { base } = ${component}Variants({ size: { initial: 'mobile', lg: 'desktop' } });
if (!fields) return <></>;
return (
<div className={base({ className: params?.Style ?? '' })}>
${componentName}
</div>
);
};
export default withDatasourceCheck()<${componentName}Props>(${componentName});
`;
}
export default generateComponentSrc;

Adding Storybook and Mock-Data Files

If your project requires story book then here the example which describe how to scaffold these files also using the scaffold command.

To add Storybook and mock-data files, first create two new templates in the src/[project_name]/scripts/templates directory:

  1. story-src.ts
  2. mock-src.ts

story-src.ts

function generateStorySrc(componentName: string): string {
return `import type { Meta, StoryObj } from '@storybook/react';
import ${componentName}, { ${componentName}Props } from '../../../components/${componentName}/${componentName}';
import defaultData from './${componentName}.mock-data';
const meta: Meta<typeof ${componentName}> = {
title: 'Components/${componentName}',
component: ${componentName},
tags: ['autodocs'],
argTypes: {},
};
export default meta;
type Story = StoryObj<typeof ${componentName}>;
export const Default: Story = {
render: (args) => {
return <${componentName} {...args} />;
},
args: defaultData,
};
`;
}
export default generateStorySrc;

mock-src.ts

function generateMockSrc(componentName: string): string {
return `import { ${componentName}Props } from 'components/${componentName}/${componentName}';const defaultData: ${componentName}Props = {
rendering: {
componentName: '${componentName}',
dataSource: '{00000000-0000-0000-0000-000000000000}',
},
params: {},
fields: {},
};
export default defaultData;
`;
}
export default generateMockSrc;

Next, create two files similar to component.ts inside the plugins folder, configuring these files with the template path and output directory path. For an example, mock.ts:

import path from 'path';
import { scaffoldFile } from '@sitecore-jss/sitecore-jss-dev-tools';
import { ScaffoldComponentPlugin, ScaffoldComponentPluginConfig } from '..';
import generateMockSrc from 'scripts/templates/mock-src';
class MockPlugin implements ScaffoldComponentPlugin {
order = 80;
exec(config: ScaffoldComponentPluginConfig) {
const { componentName, componentPath } = config;
const filename = `${componentName}.mock-data.ts`;
const componentRoot = componentPath.startsWith('src/')
? ''
: 'src/stories/components/' + componentName;
const outputFilePath = path.join(componentRoot, componentPath, filename);
const componentOutputPath = scaffoldFile(outputFilePath, generateMockSrc(componentName));
return {
...config,
componentOutputPath,
};
}
}
export const mockPlugin = new MockPlugin();
Create a similar file for Storybook, changing the output destination and template imports.

Finally, register your new .ts files in your index.ts file:

const storyConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateStorySrc,
args: args,
nextSteps: [],
};

storyConfig.nextSteps.push(
chalk.green(`
Scaffolding of ${storyConfig.componentName} complete.
Next steps:`)
);

const mockConfig: ScaffoldComponentPluginConfig = {
componentPath: regExResult[1],
componentName: regExResult[2],
componentTemplateGenerator: generateMockSrc,
args: args,
nextSteps: [],
};

mockConfig.nextSteps.push(
chalk.green(`
Scaffolding of ${mockConfig.componentName} complete.
Next steps:`)
);

Add the above configuration inside the index.ts file. The nextSteps property provides feedback to confirm successful file creation.

Congratulations! You’ve now created your own scaffolding files and can customize them further to suit your needs.

Configuring Sitecore CLI

Setting up Sitecore CLI is an essential step to streamline your Sitecore development workflow. Here’s a guide to configuring Sitecore CLI for your project:

1. Install .NET SDK: First, make sure you have .NET SDK version 4.8 installed on your machine to match the requirements for Sitecore 10.3. You can refer to the Sitecore Compatibility Table for Sitecore XP 9.0 and later to ensure compatibility. Sitecore Compatibility Table

2. Install Sitecore Management Services: Download and install Sitecore Management Services into your Sitecore instance.

3. Open the Command Line: Navigate to your project directory and open the Command Prompt (CMD) or your terminal.

4. Install Sitecore CLI: Run the following commands in your CMD or terminal:

dotnet new tool-manifest
dotnet nuget add source -n Sitecore https://nuget.sitecore.com/resources/v3/index.json
dotnet tool install Sitecore.CLI

5. Initialize Your Project: After installing Sitecore CLI, initiate your project with the following command within your project folder:

dotnet sitecore init

This command sets up the project configuration and creates a sitecore.json file in your project directory.

6. Configure Project Structure: In your sitecore.json file, you can specify your project’s module and plugin configurations. Customize it according to your project’s needs. Here’s a sample sitecore.json structure:

{
  "$schema": "./.sitecore/schemas/RootConfigurationFile.schema.json",
  "modules": [
    "./serialization/*/*.module.json"
  ],
  "plugins": [
    "Sitecore.DevEx.Extensibility.Serialization@4.2.0",
    "Sitecore.DevEx.Extensibility.Publishing@4.2.0",
    "Sitecore.DevEx.Extensibility.Indexing@4.2.0",
    "Sitecore.DevEx.Extensibility.ResourcePackage@4.2.0"
  ],
  "serialization": {
    "defaultMaxRelativeItemPathLength": 100,
    "defaultModuleRelativeSerializationPath": "serialization",
    "removeOrphansForRoles": true,
    "continueOnItemFailure": false,
    "excludedFields": []
  }
}

7. Create Module Folders: Organize your project by creating module folders within a “serialization” directory. Typically, you’ll have Feature, Project, and Foundation folders if you’re following the Helix architecture. Customize this structure as needed. For example, you can create a “Feature” folder and add a .module.json file inside it:

{
    "namespace": "[YOUR NAMESPACE NAME]",
    "description": "[DESCRIPTION OF THE MODULE]",
    "tags": ["content"],
    "items": {
        "includes": [
            {
                "name": "SCSSolution.SiteContent",
                "path": "/sitecore/Templates/Feature"
            }
        ]
    }
}

8. Connect to a Sitecore Instance: Use the following command to connect to your Sitecore instance. Replace <Sitecore identity server> and <Sitecore instance> with your actual server URLs:

dotnet sitecore login --authority https://<Sitecore identity server> --cm https://<Sitecore instance> --allow-write true

This will open your Sitecore instance’s login screen in a browser tab. Log in and allow access.

After Succefull login you will see the message in your terminal :

9. Pull Updates from Sitecore: To retrieve updated items from your Sitecore instance, run the following command:

dotnet sitecore ser pull

This command will pull all the templates from your Feature folder and generate corresponding .yaml files.

With these steps completed, you’re all set to streamline your Sitecore development with Sitecore CLI. Happy coding! 😊

Sitecore JSS 10.3 Leprechaun Setup

Setting Up Leprechaun in Sitecore JSS 10.3

Published by Vadherravi on August 26, 2023

In this article, we’ll walk you through the process of setting up Leprechaun in your development environment and explore how it can help you generate types for your Next.js application in a Sitecore JSS project. But before diving into the setup, let’s clarify what Leprechaun is and why it’s essential in the context of Sitecore JSS.

What is Leprechaun?

Leprechaun is a code generator specifically designed for Sitecore templates. It reads serialized YAML files from Sitecore serialized items and is a command-line tool for generating models for Sitecore JSS projects.

Why Use Leprechaun?

When you’re working with a Next.js application in Sitecore JSS, creating types or interfaces for field data can be a time-consuming task. Leprechaun streamlines this process by automatically generating the necessary TypeScript types for a particular template. This significantly reduces the effort required for setting up your Next.js application.

Prerequisites for Using Leprechaun

Before you get started with Leprechaun, you’ll need to have the following prerequisites in place:

  1. Sitecore CLI Setup: Ensure that you have the Sitecore CLI properly configured in your environment: Sitecore CLI
  2. Serialized Sitecore Templates: Leprechaun relies on the .yaml files generated from serialized Sitecore templates. Make sure you have these files available.

Now, let’s dive into the steps to set up Leprechaun.

Setting Up Leprechaun

Step 1 – Install Leprechaun in Your Project

To install Leprechaun in your project, follow these steps:

  • Open a PowerShell window in administrator mode.
  • Navigate to your project directory.
  • Run the following command:
  dotnet tool install --no-cache Leprechaun.Cli

You’ll receive a success message indicating that the installation was successful. You can now invoke the tool using commands like ‘dotnet tool run leprechaun’ or ‘dotnet leprechaun.’

Step 2 – Download the Code Generator File

Download the code generator file (.csx) from the following link and save it in your project directory:

Code Generator File

Step 3 – Configure the module.json File

In your module.json file, provide Leprechaun configuration. Ensure that the ‘@extends’ value matches your leprechaun.config file configuration name. Here’s an example of what this configuration may look like:

{
  "namespace": "Training.jss",
  "items": {
    "includes": [
      {
        "name": "feature-templates", 
        "path": "/sitecore/templates/Feature/next-jss"
      }
    ],
    }
  },
"leprechaun": {
      "configuration": {
        "@extends": "Training.jss.Base", // Same name as in your leprechaun.config file
        "@name": "Feature.feature-templates" // Define where you want to save your TypeScript file.
      }
}

Step 4 – Setup leprechaun.config File

After installing Leprechaun, you need to download or copy the code from the leprechaun.config file provided in this link. Ensure that your leprechaun.config file and your sitecore.json file are in the same level.

You’ll need to configure the ‘configurations’ node in the leprechaun.config file. Here’s what a section of that configuration might look like:

<configurations import="./serialization/Feature/*.module.json">
    <configuration name="Training.jss.Base" abstract="true">
        <codeGenerator
        scripts="./CodeGen/JssTypeScript.csx"
        outputFile="$(configDirectory)\models\$(layer).$(module).Model.ts" />

        <templatePredicate type="Leprechaun.InputProviders.Sitecore.Filters.SitecoreTemplatePredicate, Leprechaun.InputProviders.Sitecore"
        rootNamespace="$(layer).$(module)" singleInstance="true">
        <include name="feature-templates" path="/sitecore/templates/Feature/next-jss"/>
        </templatePredicate>
    </configuration>
</configurations>

You will also find another <templatePredicate> under the <Defaultss> node that you need to comment out.

Make sure to match the ‘@extends’ value in the and in <Include node> also name should same as “name”: “feature-templates” in module.json file .module file, provide the script name you downloaded in Step 2, , the specify the output directory where your code will be saved, and use the same ‘name’ as in your .module file.

Step 5 – Run the Leprechaun Command

Finally, open your PowerShell window in administrator mode, navigate to your project directory, and run the following command:

dotnet leprechaun -c "C:\Demo\Demo\Leprechaun.config"

Replace the path with the actual location of your leprechaun.config file.

Congratulations! Leprechaun is now set up and ready to generate the required code. You should see a message indicating that the code generator has loaded and successfully generated files from your templates. 🎉