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