<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vachagan Achoyan</title>
    <description>The latest articles on DEV Community by Vachagan Achoyan (@metax).</description>
    <link>https://dev.to/metax</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3967597%2F375549de-3968-42c0-b82c-5fd95f91f845.png</url>
      <title>DEV Community: Vachagan Achoyan</title>
      <link>https://dev.to/metax</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/metax"/>
    <language>en</language>
    <item>
      <title>How I Fixed Next.js Serialization Errors in Payload CMS v3 by Building a Custom Icon Picker</title>
      <dc:creator>Vachagan Achoyan</dc:creator>
      <pubDate>Thu, 04 Jun 2026 06:17:34 +0000</pubDate>
      <link>https://dev.to/metax/how-i-fixed-nextjs-serialization-errors-in-payload-cms-v3-by-building-a-custom-icon-picker-4bhh</link>
      <guid>https://dev.to/metax/how-i-fixed-nextjs-serialization-errors-in-payload-cms-v3-by-building-a-custom-icon-picker-4bhh</guid>
      <description>&lt;p&gt;If you’ve recently migrated to &lt;strong&gt;Payload CMS v3&lt;/strong&gt;, you’re probably loving the deep integration with Next.js and the App Router. However, moving to a full React Server Components (RSC) architecture brings a few unique engineering hurdles—especially when dealing with dynamic UI elements like icons.&lt;/p&gt;

&lt;p&gt;Recently, while working on a project, I hit a frustrating roadblock with Next.js serialization limits and ended up building an open-source plugin to solve it properly. &lt;/p&gt;

&lt;p&gt;Here is the story behind &lt;a href="https://www.npmjs.com/package/payload-icon-picker" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;payload-icon-picker&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The Dreaded Serialization Error ❌
&lt;/h2&gt;

&lt;p&gt;I wanted to allow users to select and render custom icons directly within the Payload admin panel. My first instinct was to pass down dynamic icon components or raw SVG chunks. &lt;/p&gt;

&lt;p&gt;But as soon as you try to pass heavy icon objects or binary data from a Server Component down to a Client Component prop, Next.js throws the infamous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;Error: Only plain objects can be passed to Client Components from Server Components. Objects with toJSON methods are not supported.&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using quick hacks like &lt;code&gt;JSON.parse(JSON.stringify(data))&lt;/code&gt; might unblock your custom frontend queries, but it completely breaks down when you inject custom fields into Payload's core admin panel layout.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Designing a Safe Fallback Chain 🛠️
&lt;/h2&gt;

&lt;p&gt;To solve this natively, I built &lt;strong&gt;&lt;code&gt;payload-icon-picker&lt;/code&gt;&lt;/strong&gt;. The main architecture principle is simple: &lt;strong&gt;Never pass un-serializable components through the wire.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, the plugin captures the selected icon and saves it into the database as a clean, basic JSON object containing only the name and pre-rendered static SVG markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lucide-icon-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"svg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;svg&amp;gt;...&amp;lt;/svg&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This data shape is completely safe for Next.js RSC boundary crossings and loads instantly everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-Performance UI with TanStack Virtual
&lt;/h2&gt;

&lt;p&gt;One of the biggest traps when building an icon picker is loading performance. If you import a massive pack like Lucide or Phosphor Icons all at once, rendering thousands of DOM nodes inside a select dropdown or modal will completely lag the browser.&lt;/p&gt;

&lt;p&gt;To guarantee a premium, production-ready feel, I integrated @tanstack/react-virtual to handle virtualized grid rendering. Whether the user opens a dropdown or shifts to a sliding layout, the DOM only mounts the elements currently visible on the screen. It can render 2,000+ icons at a locked 60fps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Awareness: Global vs Collection-Specific Packs
&lt;/h2&gt;

&lt;p&gt;I took the plugin a step further by introducing context tracking via Payload's &lt;code&gt;@payloadcms/ui&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, the field dynamically tracks which collection it's currently rendering in. This allows you to configure a global fallback pack (e.g., Lucide) but strictly inject isolated icon sets into specific collections (e.g., using Phosphor Icons only for your Categories collection).&lt;/p&gt;

&lt;h2&gt;
  
  
  Check It Out! 🚀
&lt;/h2&gt;

&lt;p&gt;The plugin is fully production-ready, open-source, and available on npm right now &lt;a href="https://www.npmjs.com/package/payload-icon-picker" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/payload-icon-picker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building with Payload 3 and want an easy, native way to add highly optimized icon pickers without dealing with Next.js serialization bugs, feel free to grab it:&lt;/p&gt;

&lt;p&gt;🔗 GitHub Repository: &lt;a href="https://github.com/Metax7/payload-icon-picker" rel="noopener noreferrer"&gt;https://github.com/Metax7/payload-icon-picker&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'd love to hear your thoughts on the implementation, and any stars or feedback are highly appreciated! Let me know in the comments how you are handling asset pickers in Payload v3.&lt;/p&gt;

</description>
      <category>payloadcms</category>
      <category>nextjs</category>
      <category>react</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
