Creating Solr core for sitecore using command prompt

We setup the solr cores using command “C:\solr8985\sc103solr-8.11.2\bin>solr.cmd create -c sitecore_sxa_web_index -d sitecore_configset” and followed the following for configsets https://stackoverflow.com/questions/33060048/sitecore-best-practices-for-setting-up-solr-core-for-multisite.

a) Used the following steps to create the cores

i. Copy conf folder solr\server\solr\sitecore_master_index\conf

ii. Paste into solr\server\solr\configsets\sitecore_configset <-- create this new folder

iii. Run command C:\solr8985\sc103solr-8.11.2\bin>solr.cmd create -c [your-custom-index] -d sitecore_configset

Preview graphql vs Edge graphql playground UI

The preview graphql playground is used to read the unpublished content and the edge graphql playground is used to read the published content.

Here is how preview playground looks




This is how edge playground ui looks


The Url of the edge playground will be same but at the bottom you see, you will have to pass the API Key from deploy.sitecorecloud.io specific to the dev, qa, uat environment.


The url of the preview playground will be different in terms of the hostname. You have to provide the cms hostname for which environment's cms you want to check the preview dev, qa or uat. Also, you need to get the API key, the sitecore item guid which remain in the folder /sitecore/system/Settings/Services/API Keys

Test Multi site setup with xm cloud and local next js app

 1. Verify multisite setup in Sitecore

In XM Cloud, make sure your cloned site has:

  • A new Site Definition item (in /sitecore/content/<your site>).

  • A hostname assigned under Site Grouping. This is important for routing.


If you have to create the site use the clone script in the SXA to create new website it will take care of referencing the new items wrt to your new site and new items automatically


2. ensure you have the multisite.ts middleware file with something like

// middleware/multisite.ts
import { MultisiteMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware';
import { siteResolver } from 'lib/site-resolver';
import type { MiddlewarePlugin } from '.';

export class MultisitePlugin implements MiddlewarePlugin {
  private multisiteMiddleware: MultisiteMiddleware;

  constructor() {
    this.multisiteMiddleware = new MultisiteMiddleware({
      siteResolver,
    });
  }

  async exec(req, res, next) {
    return this.multisiteMiddleware.getHandler()(req, res, next);
  }
}

Somewhere in your code there should be SiteResolver file also that decides which site to execute in the multisite.ts

There also should some code related to the multisite.ts as follows somewhere in your project like

import type { SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs/site';
import config from 'temp/config';
import type { SiteResolverPlugin } from '..';

class MultisitePlugin implements SiteResolverPlugin {
  exec(sites: SiteInfo[]): SiteInfo[] {
    // Add preloaded sites
    sites.push(...(JSON.parse(config.sites) as SiteInfo[]));

    return sites;
  }
}

export const multisitePlugin = new MultisitePlugin();

Below is the SiteResolver

import type { SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs/site';
import { SiteResolver } from '@sitecore-jss/sitecore-jss-nextjs/site';
import * as plugins from 'temp/site-resolver-plugins';

/*
  The site resolver stores site information and is used in the app
  whenever site lookup is required (e.g. by name in page props factory
  or by host in Next.js middleware).

  By default, the app is single-site (one JSS app per Sitecore site).
  However, multi-site is available with the `nextjs-multisite` add-on.
*/

export interface SiteResolverPlugin {
  /**
   * A function which will be called during sites collection
   */
  exec(sites: SiteInfo[]): SiteInfo[];
}

const sites = (Object.values(plugins) as SiteResolverPlugin[]).reduce(
  (sites, plugin) => plugin.exec(sites),
  []
);

export const siteResolver = new SiteResolver(sites);


3. Once you site is created then in the SXA Site Grouping you need to provide value in the sitename and hostname field


Because these setting will be used to confirm which site to execute.

4. Configure .env.local
SITECORE_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx // this is used for querying from preview

SITECORE_API_HOST=https://your-xmcloud-cmsinstance-url.sitecorecloud.io // this is preview graphql url in case to query from Edge url will be https://edge.sitecorecloud.io/

SITECORE_EDGE_CONTEXT_ID=your-edge-context-id // this is used for querying from edge

NEXT_PUBLIC_SITECORE_API_HOST=http://localhost:3000

GRAPH_QL_ENDPOINT=https://edge.sitecorecloud.io/api/graphql/v1 //this is Edge graphql url

GRAPH_QL_ENDPOINT=https://your-xmcloud-cmsinstance-url.sitecorecloud.io/sitecore/api/graph/edge // this is preview graphql url

Yellow setting are required for checking the preview mode site and amber are required to check the Edge (published) mode site.

5. If you noticed in the point 2, the sites information are read from 
config.sites, therefore you need to setup one more setting in the .env.local as 

#multisites
SITES=[{"name":"firstsitename","hostName":"firstsitename.localhost","language":"en"},{"name":"secondsitename","hostName":"secondsitename.localhost","language":"en"}]

6. Use multiple hostnames locally

Edit your hosts file (C:\Windows\System32\drivers\etc\hosts on Windows or /etc/hosts on Mac/Linux):

127.0.0.1 firstsitename.localhost
127.0.0.1 secondsitename.localhost

Now you can run your Next.js app with:

  • http://site1.localhost:3000 → serves Site 1

  • http://site2.localhost:3000 → serves Site 2

(based on your resolver logic).

7. Run locally

Start Next.js as usual:


npm run dev or npm run start:connected

8. Here it will work when you will hit http://firstsitename.localhost:3000 
-> it will go to your nextjs app because next js app is running on 3000 port 
-> it will go to the multisite.ts and will read the setting for config.sites 
-> config.site will give array of [{"name":"firstsitename","hostName":"firstsitename.localhost","language":"en"},{"name":"secondsitename","hostName":"secondsitename.localhost","language":"en"}] because you have set in the .env.local setting
-> based on the hostname it will get the sitename from the JSON's name property
-> graphql will internally pass the sc_site query with that site name 
-> based on your setting specific to the yellow or amber the data will be provided to you from cms.





Graphql playground in local sitecore cms

 To setup the graphql playgroun in the local sitecore CMS you need to install the sitecore headless package that you can download from Sitecore headless rendering link.

When you have installed it you can verify if the JSS is installed or not using following ways.

1. Check in the folder C:\inetpub\wwwroot\sc.dev.local\App_Config\Sitecore if LayoutServices folder, JavaScriptSerivces and Services.GraphQL folder is created, and

2. Check at the path /sitecore/system/Modules, Layout service and JavaScript Services are present in the CMS.



Once this is installed now you can enable the Playground locally. 

  1. There will be one file as Sitecore.Services.GraphQL.Content.Master.config.example in the folder C:\inetpub\wwwroot\sc.dev.local\App_Config\Sitecore\Services.GraphQL. it will be disabled but you can copy in the /Include/zzz folder and enable it.
  2. After doing try to hit the url https://sc.dev.local/sitecore/api/graph/items/master in the browser and check it is giving something except error. If it give unauthorize error then enable the graphql by using the following patch

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">

  <sitecore>
    <settings>
      <!-- Enables GraphQL Playground IDE in local environments -->
      <setting name="GraphQL.Enabled" value="true" />
    </settings>
  </sitecore>
</configuration>

3. If it still doesn't work then try to put the <path>/sitecore/api/graph/items</path> in the App_Config\Sitecore\Owin.Authentication\Sitecore.Owin.Authentication.config as follows:


After doing these changes now https://sc.dev.local/sitecore/api/graph/items/master should work and you can open the playground ui with url https://sc.dev.local/sitecore/api/graph/items/master/ui and try to run the following graphql to check if it is working. It should work otherwise check your API Key in the header below

{
  item(path: "/sitecore/content/Home", language: "en") {
    id
    name
    displayName
    fields {
      name
      value
    }
  }
}

or, in case you have SXA

query { 
  layout(language: "en", routePath: "/", site: "sitename") { 
    item { 
      rendered 
    } 
  } 



If you get unauthorize error message on browser then use the following patch for local only.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <api>
      <GraphQL>
        <defaults>
          <security>
            <systemService>
              <!-- CLI expects this service -->
              <requireAuthentication>false</requireAuthentication>
              <requireApiKey>true</requireApiKey>
            </systemService>
          </security>
        </defaults>
      </GraphQL>
    </api>
  </sitecore>
</configuration>

This is used to patch the config in the \App_Config\Sitecore\Services.GraphQL\Sitecore.Services.GraphQL.config




Pages without a rendering - Downloadable excel

Add-Type -AssemblyName "Sitecore.Kernel"

$rootPath = "/sitecore/content/Sites/my site/home/offices"
$templateId = "{xxxxxx-CE5E-42EE-902D-xxxxxxx}"
$renderingId = "{xxxxxxx-7433-4FEB-99F5-xxxxxx}"

# Export path (change as needed)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$exportPath = "C:\Temp\Offices_Missing_Rendering_$timestamp.csv"

# Get default device (typically 'default')
$device = [Sitecore.Context]::Device
if (-not $device) {
    Write-Host "Device context not available. Falling back to master device ID." -ForegroundColor Yellow
    $device = Get-Item "/sitecore/layout/Devices/Default"
}

Write-Host "Getting root item at: $rootPath"
$rootItem = Get-Item -Path $rootPath

if (-not $rootItem) {
    Write-Host "Root item not found. Exiting." -ForegroundColor Red
    return
}

$descendants = Get-ChildItem -Path $rootItem.Paths.FullPath -Recurse | Where-Object {
    $_.TemplateID.Guid -eq $templateId
}

Write-Host "Found $($descendants.Count) matching office items."

$itemsWithoutRendering = @()
$index = 1

foreach ($item in $descendants) {
    Write-Host "[$index/$($descendants.Count)] Checking item: $($item.Paths.FullPath)"

    $finalRenderingsField = $item.Fields["__Final Renderings"]

    if ($finalRenderingsField -and $finalRenderingsField.HasValue) {
        $layoutXml = [Sitecore.Data.Fields.LayoutField]::GetFieldValue($finalRenderingsField)

        if (![string]::IsNullOrWhiteSpace($layoutXml)) {
            try {
                $layoutDef = [Sitecore.Layouts.LayoutDefinition]::Parse($layoutXml)

                if ($layoutDef -ne $null) {
                    $deviceDef = $layoutDef.GetDevice($device.ID.ToString())

                    $hasRendering = $false

                    if ($deviceDef -and $deviceDef.Renderings) {
                        foreach ($rendering in $deviceDef.Renderings) {
                            if ($rendering.ItemID -eq $renderingId) {
                                $hasRendering = $true
                                break
                            }
                        }
                    }

                    if (-not $hasRendering) {
                        Write-Host "  -> Rendering NOT found in Final Layout." -ForegroundColor Yellow
                        $itemsWithoutRendering += [PSCustomObject]@{
                            Name = $item.Name
                            Path = $item.Paths.FullPath
                            ID   = $item.ID
                        }
                    } else {
                        Write-Host "  -> Rendering found." -ForegroundColor Green
                    }
                } else {
                    Write-Host "  -> LayoutDefinition parsing returned null." -ForegroundColor Yellow
                }
            }
            catch {
                Write-Host "  -> ERROR parsing layout XML: $_" -ForegroundColor Red
            }
        } else {
            Write-Host "  -> Final Renderings field is empty." -ForegroundColor Yellow
            $itemsWithoutRendering += [PSCustomObject]@{
                Name = $item.Name
                Path = $item.Paths.FullPath
                ID   = $item.ID
            }
        }
    } else {
        Write-Host "  -> Final Renderings field not found or empty." -ForegroundColor Yellow
        $itemsWithoutRendering += [PSCustomObject]@{
            Name = $item.Name
            Path = $item.Paths.FullPath
            ID   = $item.ID
        }
    }

    $index++
}

Write-Host "`nTotal items without rendering in Final Layout: $($itemsWithoutRendering.Count)" -ForegroundColor Cyan

# Export to CSV
$itemsWithoutRendering | Export-Csv -Path $exportPath -NoTypeInformation -Encoding UTF8

Write-Host "Export completed: $exportPath" -ForegroundColor Green

Pages without a rendering - Show List View

 Add-Type -AssemblyName "Sitecore.Kernel"


$rootPath = "/sitecore/content/Sites/mysite/home/offices"
$templateId = "{xxxxxx-CE5E-42EE-902D-xxxxxxxxx}"
$renderingId = "{xxxxxxx-7433-4FEB-99F5-xxxxxxxxxx}"

# Get the default device (typically 'default')
$device = [Sitecore.Context]::Device
if (-not $device) {
    Write-Host "Device context not available. Falling back to master device ID." -ForegroundColor Yellow
    $device = Get-Item "/sitecore/layout/Devices/Default"
}

Write-Host "Getting root item at: $rootPath"
$rootItem = Get-Item -Path $rootPath

if (-not $rootItem) {
    Write-Host "Root item not found. Exiting." -ForegroundColor Red
    return
}

$descendants = Get-ChildItem -Path $rootItem.Paths.FullPath -Recurse | Where-Object {
    $_.TemplateID.Guid -eq $templateId
}

Write-Host "Found $($descendants.Count) matching office items."

$itemsWithoutRendering = @()
$index = 1

foreach ($item in $descendants) {
    Write-Host "[$index/$($descendants.Count)] Checking item: $($item.Paths.FullPath)"

    $finalRenderingsField = $item.Fields["__Final Renderings"]

    if ($finalRenderingsField -and $finalRenderingsField.HasValue) {
        $layoutXml = [Sitecore.Data.Fields.LayoutField]::GetFieldValue($finalRenderingsField)

        if (![string]::IsNullOrWhiteSpace($layoutXml)) {
            try {
                $layoutDef = [Sitecore.Layouts.LayoutDefinition]::Parse($layoutXml)

                if ($layoutDef -ne $null) {
                    $deviceDef = $layoutDef.GetDevice($device.ID.ToString())

                    $hasRendering = $false

                    if ($deviceDef -and $deviceDef.Renderings) {
                        foreach ($rendering in $deviceDef.Renderings) {
                            if ($rendering.ItemID -eq $renderingId) {
                                $hasRendering = $true
                                break
                            }
                        }
                    }

                    if (-not $hasRendering) {
                        Write-Host "  -> Rendering NOT found in Final Layout." -ForegroundColor Yellow
                        $itemsWithoutRendering += [PSCustomObject]@{
                            Name = $item.Name
                            Path = $item.Paths.FullPath
                            ID   = $item.ID
                        }
                    } else {
                        Write-Host "  -> Rendering found." -ForegroundColor Green
                    }
                } else {
                    Write-Host "  -> LayoutDefinition parsing returned null." -ForegroundColor Yellow
                }
            }
            catch {
                Write-Host "  -> ERROR parsing layout XML: $_" -ForegroundColor Red
            }
        } else {
            Write-Host "  -> Final Renderings field is empty." -ForegroundColor Yellow
            $itemsWithoutRendering += [PSCustomObject]@{
                Name = $item.Name
                Path = $item.Paths.FullPath
                ID   = $item.ID
            }
        }
    } else {
        Write-Host "  -> Final Renderings field not found or empty." -ForegroundColor Yellow
        $itemsWithoutRendering += [PSCustomObject]@{
            Name = $item.Name
            Path = $item.Paths.FullPath
            ID   = $item.ID
        }
    }

    $index++
}

Write-Host "`nTotal items without rendering in Final Layout: $($itemsWithoutRendering.Count)" -ForegroundColor Cyan

$itemsWithoutRendering | Show-ListView -Title "Offices Missing SchemaOfficePageReview Rendering" -Property Name, Path, ID

Create self signed certificate and add to trusted root locally

 🟩 Step 1: Create the Certificate (PowerShell)

Open PowerShell as Administrator and run:

New-SelfSignedCertificate ` -DnsName "Domain.dev.local" ` -CertStoreLocation "cert:\LocalMachine\My" ` -FriendlyName "Domain Local Dev Cert" ` -NotAfter (Get-Date).AddYears(5)

This creates a certificate stored under Local Machine > Personal.

🟩 Step 2: Export the Certificate (for trusting)

Press Win + R → type mmc → Enter.
Go to File > Add/Remove Snap-in.
Add Certificates for Local Computer.
Go to Certificates > Personal > Certificates
Find "Domain Local Dev Cert" or Domain.dev.local.
Right-click it → All Tasks > Export (Base 64).
Choose No, do not export the private key.
Save it as a .cer file (e.g., domain.trust.cer).

🟩 Step 3: Trust the Certificate
In MMC, go to:
Certificates > Trusted Root Certification Authorities > Certificates
Right-click → All Tasks > Import.
Import the .cer file you just exported.

Now your system will trust https://Domain.dev.local/.

🟩 Step 4: Bind Certificate in IIS

Open IIS Manager.

Go to Sites > Domain.dev.local (or your site name).

On the right → click Bindings.

Add or Edit an HTTPS binding:

select the one you created (by Friendly Name or domain).

Getting data outside of the page layout using Sitecore GraphQL API

There will be cases when the data passed don’t actually change that often and because of that we would want to get this data from outside of the home item or somewhere from global location. This is where Next.js API Routes will come into the picture. Next.js has introduced a feature which allows creating API routes within Next.js without having to rely on backend integration. 


I had the requirement to get the data from the global item in the Layout.tsx so I tried to create the GraphQLRequestClient but it is not allowed and gave me the error as:

graphql_request__WEBPACK_IMPORTED_MODULE_0__.GraphQLClient is not a constructor

So I got the link https://www.getfishtank.com/insights/utilizing-nextjs-api-routes-for-sitecore-xm-cloud-graphql-queries that helped me a lot. You can follow this for more detail but on high level I will mention how can we achieve to get the global data in the Layout.tsx file.

So below are the high level steps

  1. Create an API in next js app, as graphql query can access the data in the API
  2. Use that API's response in the Layout.tsx and do your logic what you want to achieve.

  1. Create an API in next js app, as graphql query can access the data in the API
In the path \src\sxastarter\src\pages\api\ create a file (this file name will be api endpoint) getglobalitemsetting.ts and paste the following code.

import { gql, GraphQLClient } from 'graphql-request';
import type { NextApiRequest, NextApiResponse } from 'next';
import config from 'temp/config';

const getGlobalItemSettingApi = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
    const graphQLClient = new GraphQLClient(config.graphQLEndpoint);
    graphQLClient.setHeader('sc_apikey', config.sitecoreApiKey);
    console.log("method type: ", req.method)
    if (req.method === 'GET') {
        const query = gql`
        query{
            item(path: "/sitecore/content/MySiteSiteCollection/MySite/Settings" language:"en") {
                field(name: "your field name") {
                    value
            }
          }
        }
      `;

      const data = await graphQLClient.request(query);
      return res.status(200).json(data);
      } else {
        return res.status(400).json({ message: 'Invalid method used.' });
      }
   
};

export default getGlobalItemSettingApi;


This logic will return the response based on the graphql written in this method using http://localhost:3000/api/getglobalitemsetting. 

2. Use that API's response in the Layout.tsx and do your logic what you want to achieve.
Write this code in the 
const Layout = ({ layoutData }: LayoutProps): JSX.Element => {
 const [isMyFieldChecked, setIsMyFIeldChecked] = useState<boolean | null>(null);
  useEffect(() => {
    const fetchChatSetting = async () => {
      try {
        const response = await fetch("/api/getglobalitemsetting");
        if (!response.ok) {
          throw new Error("Failed to fetch global item setting");
        }
        const data = await response.json();
       
        // Extracting the value from the response
        const isValue = data?.item?.field?.value === "1"; // Assuming 1 means enabled
        setIsMyFIeldChecked(isValue);
      } catch (error) {
        console.error("Error fetching chat setting:", error);
      }
    };

    fetchChatSetting();
  }, []);

return (


<div>isMyFieldChecked</div>)

}

Thanks!


Creating Solr core for sitecore using command prompt

We setup the solr cores using command “C:\solr8985\sc103solr-8.11.2\bin>solr.cmd create -c sitecore_sxa_web_index -d sitecore_configset” ...