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"]