Sitecore Headless Next.js – Forwarding custom query parameters to Layout Service

A few weeks ago the following question came up by Robbert Hock on Sitecore Chat:

Contents Resolver question: with Context.HttpContext.Request.Params["item"] I can read the url of the item. But when for example we have a url like /news?page=1, how would I read the page querystring prop in a contents resolver? Somehow I can’t find it, since the request is like /sitecore/api/layout/render/default

Robbert Hock

This is a fair question, if you would like to process custom parameters in your custom Contents Resolver.

To support this behavior we need to extend the default RestLayoutService, to pass these custom query parameters to the Layout Service. The nicest way I have found is to inherit from the RestlayoutService and overwrite the getFetchParams.

import { RestLayoutService, RestLayoutServiceConfig } from '@sitecore-jss/sitecore-jss-nextjs';
interface FetchParams {
[param: string]: string | number | boolean;
sc_apikey: string;
sc_site: string;
sc_lang: string;
tracking: boolean;
}
export class ParameterizedRestLayoutService extends RestLayoutService {
constructor(private config: RestLayoutServiceConfig) {
super(config);
}
protected getFetchParams = (language?: string): FetchParams => {
return {
sc_apikey: this.config.apiKey,
sc_site: this.config.siteName,
sc_lang: language || '',
tracking: this.config.tracking ?? true,
custom_parameter: '🩹 lorem ipsum',
};
};
}

The FetchParams interface needs to be copied manually at the moment, because it’s not exported. The original getFetchParams method should be also copied because it can’t be called in the child class, even if it’s protected – I am not a TypeScript expert, but I found that the arrow/lambda functions are not becoming part of the prototype.

Of course this is not enough yet, we need to tell to the Sitecore to use this new ParameterizedRestLayoutService. For that, we need to navigate to the layout-service-factory.ts, and instead of the RestLayoutService, we just need to use the newly created ParameterizedRestLayoutService.

import { LayoutService } from '@sitecore-jss/sitecore-jss-nextjs';
import config from 'temp/config';
import { ParameterizedRestLayoutService } from './parameterized-rest-layout-service';
export class LayoutServiceFactory {
create(): LayoutService {
return new ParameterizedRestLayoutService({
apiHost: config.sitecoreApiHost as string,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
configurationName: 'default',
});
}
}
export const layoutServiceFactory = new LayoutServiceFactory();

Then on the Sitecore side you can retrieve the querystring using the HttpContextBase in your custom Contents Resolver.

public class CustomContentsResolver : RenderingContentsResolver
{
private readonly HttpContextBase _httpContext;
public CustomContentsResolver(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
protected override JObject ProcessItem(Item item, Rendering rendering, IRenderingConfiguration renderingConfig)
{
var querystring = _httpContext.Request.QueryString;
return base.ProcessItem(item, rendering, renderingConfig);
}
}

This way you can forward the parameters from the client side to the Layout Service or you can implement any custom business logic to forward different parameters.

One thought on “Sitecore Headless Next.js – Forwarding custom query parameters to Layout Service

  1. This looks like a good solution if those parameters you want to forward are static or from the config, but it doesn’t seem to answer Robbert’s question.

    I’m wondering if you can forward them from the request without having to rewrite the entire fetchLayoutData function.

    The way I got it to work was overriding fetchLayoutData so I could pass the request along.

    I don’t love this solution, but it does pass any querystring parameters along if they exist.

    Here’s how my ParameterizedRestLayoutService looks:

    “`
    import { AxiosDataFetcher, LayoutServiceData, RestLayoutService, RestLayoutServiceConfig } from ‘@sitecore-jss/sitecore-jss-nextjs’;
    import { IncomingMessage, ServerResponse } from “http”;
    import { parse, ParsedUrlQuery } from “querystring”;

    interface FetchParams {
    [param: string]: string | number | boolean;
    sc_apikey: string;
    sc_site: string;
    sc_lang: string;
    tracking: boolean;
    }

    export class ParameterizedRestLayoutService extends RestLayoutService {
    constructor(private config: RestLayoutServiceConfig) {
    super(config);
    }

    fetchLayoutData = (itemPath: string, language: string | undefined, req: IncomingMessage, res: ServerResponse) : Promise => {
    // grab request query string parameters, if none just do what it’d normally do
    const reqParams : ParsedUrlQuery = parse(req.url?.split(‘?’)[1] || “”);
    if (!req.url || !reqParams) return super.fetchLayoutData(itemPath, language, req, res);

    const querystringParams = this.getFetchParams(language, reqParams);
    const combinedParams = Object.assign({ item: itemPath }, querystringParams);
    const fetchParams: Record = {};
    Object.keys(combinedParams).forEach((key) => {
    if (key == “path”) return;
    fetchParams[key] = combinedParams[key].toString();
    });

    // build url and fetch with axios data fetcher
    const fetchUrl = this.resolveLayoutServiceUrl(‘render’);
    const url = this.buildUrl(fetchUrl, fetchParams);
    const axiosFetcher = new AxiosDataFetcher();
    const response = axiosFetcher.fetch(url);
    return response
    .then((response) => { return response.data })
    .catch((error:any) => { throw error });

    };

    private buildUrl = (urlBase: string, params: Record) => {
    const url = new URL(urlBase);
    url.search = new URLSearchParams(params).toString();
    return url.toString();
    };

    protected getFetchParams = (language?: string, params?: ParsedUrlQuery): FetchParams => {
    return {
    sc_apikey: this.config.apiKey,
    sc_site: this.config.siteName,
    sc_lang: language || ”,
    tracking: this.config.tracking ?? true,
    …params
    };
    };
    }
    “`

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s