The Problem
I was recently involved in a Slack conversation around the topic of reducing the size of the serialized payload in JSS. The conversation was prompted by a Sitecore Support ticket that was opened by a customer. The customer was experiencing issues with the Experience Editor failing to load when the serialized payload was too large.
The problem can be summarized as follows:
Let's say you have an item with a list field that references multiple items, and the items you are referencing have many fields. The default JSS behavior is to serialize all of those items' fields. You may not want this to be the case, either for performance reasons or because you don't want to expose that information to the client.
Experience Editor Issues
Have you ever seen the JS build logs warn you about serializes pages being too large? Take those warnings seriously; you may want to do some optimization.
If the serialized payload is too large (i.e. larger than 2MB), it can cause the Experience Editor to fail during load. Keep in mind that Experience Editor adds its own bloat to the payload in the form of editable chrome markups.
Sitecore Support recommended the following:
You can mark certain unnecessary fields as non-editable to trim down the serialized payload. To achieve this, you can create custom serializers for such field types extending from the BaseFieldSerializer class. Then you can override the RenderField method and set disableEditing to true for the unnecessary fields based on a condition (for example, field name or field id). The serializers will then need to be added to or replaced in the getFieldSerializer pipeline (under layoutService group) in Sitecore configuration.
One Approach
I propose a different approach that is arguably more powerful. We can override the GetMultilistFieldSerializer
with our own. Methods can then be overridden with logic just for the list fields. If the field isn't a list type, we can call the base.
This method allows us to only serialize the fields that we want. By following this guide, you should also be in a good place to "override the RenderField method and set disableEditing to true for the unnecessary fields".
Note that the examples below use dependency injection, which is a pattern I highly recommend for Sitecore solutions. The examples are intentionally incomplete to avoid cluttering the post with the usual minutia of dependency injection configuration.
Specify the Custom GetMultilistFieldSerializer
_11 <group groupName="layoutService">
_11 <getFieldSerializer performanceCritical="true">
_11 <processor type="Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetMultilistFieldSerializer, Sitecore.LayoutService" resolve="true">
_11 <patch:attribute name="type">Website.Foundation.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetMultilistFieldSerializer, Website</patch:attribute>
_11 </getFieldSerializer>
GetMultilistFieldSerializer.cs
_36using Sitecore.Abstractions;
_36using Sitecore.Diagnostics;
_36using Sitecore.LayoutService.Serialization;
_36using Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer;
_36using Sitecore.Services.GraphQL.EdgeSchema.Services.Multisite;
_36using Website.Foundation.LayoutService.Serialization.FieldSerializers;
_36using Website.Foundation.LayoutService.Services;
_36namespace Website.Foundation.LayoutService.Serialization.Pipelines.GetFieldSerializer
_36 public class GetMultilistFieldSerializer : Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetMultilistFieldSerializer
_36 private readonly BaseMediaManager _mediaManager;
_36 private readonly IMultisiteService _multisiteService;
_36 private readonly ISerializedTargetItemService _serializedTargetItemService;
_36 public GetMultilistFieldSerializer(
_36 IFieldRenderer fieldRenderer,
_36 BaseMediaManager mediaManager,
_36 IMultisiteService multisiteService,
_36 ISerializedTargetItemService serializedTargetItemService) : base(fieldRenderer, mediaManager, multisiteService)
_36 _mediaManager = mediaManager;
_36 _multisiteService = multisiteService;
_36 _serializedTargetItemService = serializedTargetItemService;
_36 protected override void SetResult(GetFieldSerializerPipelineArgs args)
_36 Assert.ArgumentNotNull(args, "args");
_36 Assert.IsNotNull(args.Field, "args.Field is null");
_36 Assert.IsNotNull(args.ItemSerializer, "args.ItemSerializer is null");
_36 args.Result = new MultiListFieldSerializer(args.ItemSerializer, FieldRenderer, _mediaManager, _multisiteService, _serializedTargetItemService);
Add a Custom MultiListFieldSerializer
MultilistFieldSerializer.cs
_37using System.Collections.Generic;
_37using Newtonsoft.Json.Linq;
_37using Sitecore.Abstractions;
_37using Sitecore.Data.Fields;
_37using Sitecore.Data.Items;
_37using Sitecore.LayoutService.Serialization;
_37using Sitecore.LayoutService.Serialization.ItemSerializers;
_37using Website.Foundation.LayoutService.Services;
_37namespace Website.Foundation.LayoutService.Serialization.FieldSerializers
_37 public class MultilistFieldSerializer : Sitecore.LayoutService.Serialization.FieldSerializers.MultilistFieldSerializer
_37 private readonly ISerializedTargetItemService _serializedTargetItemService;
_37 public MultilistFieldSerializer(
_37 IItemSerializer itemSerializer,
_37 IFieldRenderer fieldRenderer,
_37 BaseMediaManager mediaManager,
_37 ISerializedTargetItemService serializedTargetItemService) : base(itemSerializer, fieldRenderer, mediaManager)
_37 _serializedTargetItemService = serializedTargetItemService;
_37 protected override string GetSerializedTargetItem(Item item, MultilistField field, int depth)
_37 JObject? jObject = _serializedTargetItemService.GetSerializedTargetItemFields(item, depth);
_37 return jObject.ToString();
_37 return base.GetSerializedTargetItem(item, field, depth);
SerializedItemFieldsRepository.cs
_38using Newtonsoft.Json.Linq;
_38using Sitecore.Data.Items;
_38using Website.Foundation.LayoutService;
_38using Website.Foundation.LayoutService.Repositories;
_38namespace Website.Project.Main.Repositories
_38 public class SerializedItemFieldsRepository : ISerializedItemFieldsRepository
_38 public JObject? GetSerializedItemFields(Item item, int depth)
_38 // These are random IDs for example purposes
_38 if (item.TemplateID == new ID("{3CCEB664-AAC5-40D5-BB19-7FFF94C619B5}"))
_38 return GetFields(item, new[] {
_38 new ID("{0BEB9157-C202-4385-ABB1-0FE2CA2AA0A4}"),
_38 new ID("{AB10475E-D72B-4DD5-B068-24BAAE87A4BD}")
_38 private JObject GetFields(Item item, ID[]? fieldIds, int depth)
_38 var serializationOptions = new SerializationOptions { DisableEditing = true };
_38 if (fieldIds != null)
_38 return JObject.Parse(_itemSerializer.SerializeFields(item, fieldIds, serializationOptions, depth));
_38 return JObject.Parse(_itemSerializer.Serialize(item, serializationOptions));
Add a CustomItemSerializer
_11 <rendering type="Sitecore.LayoutService.Configuration.DefaultRenderingConfiguration, Sitecore.LayoutService">
_11 <itemSerializer patch:instead="*[@type='Sitecore.JavaScriptServices.ViewEngine.LayoutService.JssItemSerializer, Sitecore.JavaScriptServices.ViewEngine']" type="Website.Foundation.LayoutService.Serialization.ItemSerializers.CustomItemSerializer, Website" resolve="true">
_11 <AlwaysIncludeEmptyFields>true</AlwaysIncludeEmptyFields>
_41using Newtonsoft.Json;
_41using Sitecore.Data.Fields;
_41using Sitecore.Data.Items;
_41using Sitecore.JavaScriptServices.ViewEngine.LayoutService;
_41using Sitecore.LayoutService.Serialization;
_41using Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer;
_41namespace Website.Foundation.LayoutService.Serialization.ItemSerializers
_41 public class CustomItemSerializer : JssItemSerializer, ICustomItemSerializer
_41 public CustomItemSerializer(IGetFieldSerializerPipeline getFieldSerializerPipeline) : base(getFieldSerializerPipeline)
_41 // Add ability to serialize a defined set of fields.
_41 public virtual string SerializeFields(Item item, ID[] fieldIds, SerializationOptions options, int depth)
_41 using (StringWriter stringWriter = new StringWriter())
_41 using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter))
_41 jsonTextWriter.WriteStartObject();
_41 foreach (ID fieldId in fieldIds)
_41 Field? itemField = item.Fields[fieldId];
_41 if (itemField != null)
_41 SerializeField(itemField, jsonTextWriter, options, depth);
_41 jsonTextWriter.WriteEndObject();
_41 return stringWriter.ToString();
SerializedTargetItemService.cs
_38using System.Collections.Generic;
_38using Newtonsoft.Json.Linq;
_38using Sitecore.Data.Items;
_38using Website.Foundation.LayoutService.Repositories;
_38namespace Website.Foundation.LayoutService.Services
_38 public class SerializedTargetItemService : ISerializedTargetItemService
_38 private readonly IEnumerable<ISerializedItemFieldsRepository> _serializedItemFieldsRepositories;
_38 public SerializedTargetItemService(
_38 IEnumerable<ISerializedItemFieldsRepository> serializedItemFieldsRepositories)
_38 _serializedItemFieldsRepositories = serializedItemFieldsRepositories;
_38 public JObject? GetSerializedTargetItemFields(Item item, int depth)
_38 var jobj = new JObject();
_38 foreach (ISerializedItemFieldsRepository serializedItemFieldsRepository in _serializedItemFieldsRepositories)
_38 JObject? contents = serializedItemFieldsRepository.GetSerializedItemFields(item, depth);
_38 if (contents != null)
_38 jobj.Merge(contents);
Another Approach / More Reading
https://stackoverflow.com/questions/70503440/how-to-override-the-4mb-api-routes-body-size-limit/70504864#70504864
_85468 11:26:36 ERROR [JSS] Error occurred during POST to remote rendering host: `http://localhost:3000/api/editing/render`
_85468 11:26:36 ERROR The remote server returned an error: (413) Body exceeded 2mb limit.
_8Exception: System.Net.WebException
_8Message: The remote server returned an error: (413) Body exceeded 2mb limit.
_8 at System.Net.WebClient.UploadDataInternal(Uri address, String method, Byte[] data, WebRequest& request)
_8 at System.Net.WebClient.UploadString(Uri address, String method, String data)
_8 at Sitecore.JavaScriptServices.ViewEngine.Http.RenderEngine.Invoke[T](String moduleName, String functionName, Object[] functionArgs)
See if your solution contains this, and modify the value:
_9// Bump body size limit (1mb by default) for Sitecore editor payload
_9// See https://nextjs.org/docs/api-routes/api-middlewares#custom-config
_9export const config = {
Keep on building,
Marcel