bezzant.dev

Sitecore JSS 16.0 GeneralLink field, media links and alwaysIncludeServerUrl - very broken

We're currently at the tail end of a Sitecore 10.1 upgrade from 9.2, running the newly named JSS server package; 'Sitecore Headless Rendering 16.0.0'.

Something was off with the serialization of GeneralLink fields - internal and external links were serializing as normal, but media links where off - returning an absolute url and rather than a media url, an item path:

..
"fields": {
  "link": {
    "value": {
      "href": "https://www.site.co.uk/sitecore/media library/site/documents/pdfs/example",
      "text": "PDF Link",
      "linktype": "media",
      "target": "_blank",
      "id": "{794F5802-D2E5-4780-9E5A-98A3C9FD329A}"
    }
  }
}
..

I'd gone to lengths to ensure the server url is never included anywhere and it worked previously without problem. I quickly updated the config on the LinkProvider to the newer UrlBuilder patterns introduced in Sitecore 9.3 - assuming legacy config might have been loaded. No dice.

First step was to quickly spin up a new, clean 10.1 instance to verify if the problem exists there. It does, which is unfortunate, it's a Sitecore issue.

Next stop is the GeneralLinkFieldSerializer - let's see why it isn't using the options I have configured when fetching the link url to drop into the href property:

// jss v16/10.1
// Sitecore.LayoutService.Serialization.FieldSerializers.GeneralLinkFieldSerializer
protected virtual string GetLinkUrl(LinkField field)
{
	Item targetItem = field.TargetItem;
	if (targetItem == null)
	{
		return field.GetFriendlyUrl();
	}
	SiteContext site = Context.Site;
	ItemUrlBuilderOptions defaultUrlBuilderOptions = LinkManager.GetDefaultUrlBuilderOptions();
	if (!targetItem.Paths.Path.StartsWith(site.RootPath))
	{
		((UrlBuilderOptions)defaultUrlBuilderOptions).AlwaysIncludeServerUrl = ((bool?)true);
	}
	return LinkManager.GetItemUrl(targetItem, defaultUrlBuilderOptions);
}

LinkManager - whats going on in here!? LinkManager isn't going to ever return a media url using the media prefix and that path is never going to be within the root path of the site - hence the server url always being present. This is thoroughly broke.

A quick comparison with an earlier version of the Sitecore.LayoutService assembly confirms the original, previously working version was as simple as this:

// jss v14/9.2
// Sitecore.LayoutService.Serialization.FieldSerializers.GeneralLinkFieldSerializer
protected virtual string GetLinkUrl(LinkField field)
{
	return field.GetFriendlyUrl();
}

GetFriendlyUrl takes care of resolving the correct link type and uses the correct itemUrlBuilder or mediaUrlBuilder configuration.

We're already running a customized GeneralLinkFieldSerializer so a tactical fix to override GetLinkUrl was a quick win.

If you want to swap out the default serializer, you'll need to replace the GetGeneralLinkFieldSerializer processor in the getFieldSerializer pipeline to return your custom type:

configuration/sitecore/pipelines/group[@groupName="layoutService"]/pipelines/getFieldSerializer/processor[@type="Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetGeneralLinkFieldSerializer, Sitecore.LayoutService"]