<?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: Khaled waleed</title>
    <description>The latest articles on DEV Community by Khaled waleed (@khaledw).</description>
    <link>https://dev.to/khaledw</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%2F3475002%2Fb3607a94-5fdf-481f-a22d-be2b986013fe.JPG</url>
      <title>DEV Community: Khaled waleed</title>
      <link>https://dev.to/khaledw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaledw"/>
    <language>en</language>
    <item>
      <title>Connecting Headless WordPress RSS to Next.js: Building a Scalable Newsletter System</title>
      <dc:creator>Khaled waleed</dc:creator>
      <pubDate>Sat, 13 Sep 2025 09:39:04 +0000</pubDate>
      <link>https://dev.to/khaledw/connecting-headless-wordpress-rss-to-nextjs-building-a-scalable-newsletter-system-2ngd</link>
      <guid>https://dev.to/khaledw/connecting-headless-wordpress-rss-to-nextjs-building-a-scalable-newsletter-system-2ngd</guid>
      <description>&lt;p&gt;In modern web development, the headless CMS approach has become increasingly popular for its flexibility and performance benefits. Recently, we tackled an interesting challenge: connecting a headless WordPress RSS feed to a Next.js website for automated newsletter distribution through Mailchimp.&lt;/p&gt;

&lt;p&gt;This article walks through our journey, the challenges we faced, and the elegant solutions we implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: Headless WordPress + Next.js + Mailchimp
&lt;/h2&gt;

&lt;p&gt;Our architecture consisted of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordPress&lt;/strong&gt; as a headless CMS (hosted on &lt;code&gt;cms.example.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js website&lt;/strong&gt; as the frontend (hosted on Vercel at &lt;code&gt;example.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mailchimp&lt;/strong&gt; for newsletter distribution using RSS-to-Email campaigns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was to automatically pull content from WordPress RSS feeds and distribute them via beautifully designed newsletters that maintained our brand consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge 1: RSS Feed URL Mismatch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Our WordPress RSS feed generated URLs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://cms.example.com/sample-article-slug/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But our Next.js website used URLs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/news/sample-article-slug/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When newsletter subscribers clicked on articles, they were being redirected to the WordPress staging domain instead of our production Next.js site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Attempts
&lt;/h3&gt;

&lt;p&gt;We first tried using Mailchimp's &lt;code&gt;*|RSSITEM:SLUG|*&lt;/code&gt; merge tag to construct custom URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/news/*|RSSITEM:SLUG|*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read Article&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this approach failed because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:SLUG|*&lt;/code&gt; isn't a standard Mailchimp RSS merge tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:TITLE|*&lt;/code&gt; produces URL-encoded titles with spaces (&lt;code&gt;%20&lt;/code&gt;) instead of proper slugs&lt;/li&gt;
&lt;li&gt;Manual redirect mapping for each article wasn't scalable&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution: API-Based URL Redirection
&lt;/h3&gt;

&lt;p&gt;We implemented a scalable solution using Next.js API routes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Created an API route&lt;/strong&gt; (&lt;code&gt;src/app/api/redirect/route.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Extract slug from WordPress URL&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Redirect to proper news URL&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/news/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error parsing redirect URL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Fallback to news page&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Updated newsletter template&lt;/strong&gt; to use the API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/api/redirect?url=*|RSSITEM:URL|*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Read Full Article
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenge 2: Newsletter Design Consistency
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;The default Mailchimp RSS templates looked nothing like our website. We needed the newsletter to feel like an extension of our brand, matching the exact visual design of our Next.js site.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Custom HTML Template
&lt;/h3&gt;

&lt;p&gt;We created a custom Mailchimp template that replicated our website's design system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design Elements Matched:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Color Palette&lt;/strong&gt;: &lt;code&gt;#141414&lt;/code&gt; backgrounds, &lt;code&gt;#1c1c1c&lt;/code&gt; content areas, &lt;code&gt;#FFE52C&lt;/code&gt; accent colors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typography&lt;/strong&gt;: Inter font family with exact same font weights and sizes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component Styling&lt;/strong&gt;: Rounded corners (20px), card layouts, gradient overlays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt;: Mobile-first approach matching website breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key CSS Techniques:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.content-section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1c1c1c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.category-tag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FFE52C&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complete Newsletter Template Structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/1999/xhtml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;*|MC:SUBJECT|*&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;/* Email-safe CSS with !important declarations */&lt;/span&gt;
    &lt;span class="nc"&gt;.email-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#141414&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Inter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.content-section&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;.rss-image-container&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin: 0; padding: 0; background-color: #141414;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- RSS Feed Content --&amp;gt;&lt;/span&gt;
  *|RSSITEMS:START|*
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content-section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"categories"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"category-tag"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Newsletter&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"article-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/api/redirect?url=*|RSSITEM:URL|*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        *|RSSITEM:TITLE|*
      &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"meta-info"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;*|RSSITEM:DATE|*&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;By *|RSSITEM:AUTHOR|*&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rss-image-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      *|RSSITEM:IMAGE|*
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"article-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      *|RSSITEM:CONTENT_TEXT|*
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"text-align: center;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/api/redirect?url=*|RSSITEM:URL|*"&lt;/span&gt;
         &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"read-more-btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Read Full Article
      &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  *|RSSITEMS:END|*
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenge 3: RSS Image Extraction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Getting images from RSS feeds to display properly in email clients proved challenging. Different RSS merge tags (&lt;code&gt;*|RSSITEM:IMAGE|*&lt;/code&gt;, &lt;code&gt;*|RSSITEM:IMAGE_FULL|*&lt;/code&gt;, &lt;code&gt;*|RSSITEM:IMAGE_URL|*&lt;/code&gt;) either didn't work or weren't supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Simplified Approach
&lt;/h3&gt;

&lt;p&gt;After testing various approaches, we found that &lt;code&gt;*|RSSITEM:IMAGE|*&lt;/code&gt; works most reliably when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrapped in a container div for better control&lt;/li&gt;
&lt;li&gt;Styled with email-safe CSS using &lt;code&gt;!important&lt;/code&gt; declarations&lt;/li&gt;
&lt;li&gt;Given fallback styling for different email clients
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rss-image-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  *|RSSITEM:IMAGE|*
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.content-section&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.rss-image-container&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenge 4: Scalability Concerns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Initially, we considered several approaches that weren't scalable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual redirects for each article in &lt;code&gt;next.config.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hardcoded URL mappings&lt;/li&gt;
&lt;li&gt;Modifying WordPress site URLs (which would break the headless setup)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution: Dynamic URL Processing
&lt;/h3&gt;

&lt;p&gt;Our API route solution scales automatically because it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extracts slugs dynamically&lt;/strong&gt; from any WordPress URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires no manual configuration&lt;/strong&gt; for new articles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserves the headless architecture&lt;/strong&gt; without modifying WordPress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provides proper 301 redirects&lt;/strong&gt; for SEO benefits&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Final Architecture
&lt;/h2&gt;

&lt;p&gt;Here's how everything works together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress RSS Feed
       ↓
Mailchimp RSS Campaign
       ↓
Newsletter Email
       ↓
User Clicks Link
       ↓
example.com/api/redirect?url=cms.example.com/article-slug/
       ↓
Extract Slug from WordPress URL
       ↓
301 Redirect to /news/article-slug
       ↓
Next.js Page Loads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flow Example:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RSS URL&lt;/strong&gt;: &lt;code&gt;https://cms.example.com/sample-article-title/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Newsletter Link&lt;/strong&gt;: &lt;code&gt;https://example.com/api/redirect?url=https://cms.example.com/sample-article-title/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Final Destination&lt;/strong&gt;: &lt;code&gt;https://example.com/news/sample-article-title/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Email Client Limitations
&lt;/h3&gt;

&lt;p&gt;Email clients have significant CSS limitations compared to web browsers. Using &lt;code&gt;!important&lt;/code&gt; declarations and inline styles is often necessary for consistent rendering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email-Safe CSS Practices:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Always use !important for critical styles */&lt;/span&gt;
&lt;span class="nc"&gt;.newsletter-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Inline styles as backup */&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"background-color: #141414; padding: 20px;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. RSS Merge Tag Inconsistencies
&lt;/h3&gt;

&lt;p&gt;Mailchimp's RSS merge tags aren't always well-documented, and availability can vary between account types. Testing different variations is essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common RSS Merge Tags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:TITLE|*&lt;/code&gt; - Article title&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:URL|*&lt;/code&gt; - Article URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:DATE|*&lt;/code&gt; - Publication date&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:AUTHOR|*&lt;/code&gt; - Article author&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:CONTENT_TEXT|*&lt;/code&gt; - Article content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*|RSSITEM:IMAGE|*&lt;/code&gt; - First image from content&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Headless CMS URL Management
&lt;/h3&gt;

&lt;p&gt;When using headless CMSs, URL management becomes more complex. Planning for URL structure differences early in the project saves significant refactoring later.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. API Routes for Complex Logic
&lt;/h3&gt;

&lt;p&gt;Next.js API routes are perfect for handling complex URL transformations that can't be solved with simple redirects or middleware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benefits
&lt;/h2&gt;

&lt;p&gt;Our final solution provides several performance advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single 301 Redirect&lt;/strong&gt;: Users experience only one redirect, minimizing load time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached API Responses&lt;/strong&gt;: Vercel caches API route responses for improved performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO-Friendly&lt;/strong&gt;: Proper 301 redirects maintain link equity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: No performance degradation as content volume grows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the API Route
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/redirect/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+|&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/news/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error parsing redirect URL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create Custom Mailchimp Template
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to Mailchimp Templates&lt;/li&gt;
&lt;li&gt;Create new template → Code your own&lt;/li&gt;
&lt;li&gt;Paste the custom HTML template&lt;/li&gt;
&lt;li&gt;Configure RSS campaign to use this template&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Configure RSS Campaign
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create new RSS campaign in Mailchimp&lt;/li&gt;
&lt;li&gt;Enter your WordPress RSS feed URL: &lt;code&gt;https://cms.example.com/category/newsletter/feed/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select your custom template&lt;/li&gt;
&lt;li&gt;Configure sending frequency and audience&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Test the Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Send test newsletter&lt;/li&gt;
&lt;li&gt;Click article links&lt;/li&gt;
&lt;li&gt;Verify redirects work: &lt;code&gt;example.com/api/redirect?url=...&lt;/code&gt; → &lt;code&gt;example.com/news/article-slug&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Images Not Loading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ensure WordPress RSS feed includes proper image tags&lt;/li&gt;
&lt;li&gt;Test different RSS image merge tags (&lt;code&gt;*|RSSITEM:IMAGE|*&lt;/code&gt;, &lt;code&gt;*|RSSITEM:IMAGE_FULL|*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Check image URLs are absolute, not relative&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redirects Not Working
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Verify API route is deployed to production&lt;/li&gt;
&lt;li&gt;Check URL encoding in newsletter links&lt;/li&gt;
&lt;li&gt;Test API route directly in browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Newsletter Design Issues
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;!important&lt;/code&gt; for critical CSS&lt;/li&gt;
&lt;li&gt;Test in multiple email clients&lt;/li&gt;
&lt;li&gt;Provide fallback styles for unsupported properties&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  URL Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Validate URLs to prevent open redirects&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedDomains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cms.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowedDomains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/news&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Consider implementing rate limiting on the redirect API route to prevent abuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Basic rate limiting example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateLimitMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt; &lt;span class="c1"&gt;// 1 minute&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rateLimitMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recentRequests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recentRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRequests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too Many Requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;recentRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rateLimitMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recentRequests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Continue with redirect logic...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a scalable connection between headless WordPress RSS feeds and Next.js websites requires careful consideration of URL management, design consistency, and email client limitations. Our solution demonstrates that with the right architecture—using API routes for dynamic URL processing and custom HTML templates for brand consistency—you can create a seamless experience that scales automatically.&lt;/p&gt;

&lt;p&gt;The key is to embrace the flexibility of modern web architecture while understanding the constraints of email clients and RSS feed limitations. By treating the newsletter as an extension of your website rather than a separate entity, you can maintain brand consistency and provide a superior user experience.&lt;/p&gt;

&lt;p&gt;This approach has proven robust and scalable, handling growing content volume without requiring manual intervention for each new article. The investment in proper architecture pays dividends in reduced maintenance overhead and improved user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Have you implemented similar solutions for headless CMS + newsletter integration? What challenges did you face? Share your experiences in the comments below!&lt;/p&gt;

&lt;p&gt;If you found this helpful, consider following me for more JAMstack architecture insights and modern web development tutorials.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This implementation showcases the power of modern JAMstack architecture, demonstrating how headless CMSs, static site generators, and API-driven solutions can work together to create sophisticated, scalable web experiences.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>9 Things I Got Wrong About Building E-commerce Systems (That I Had to Learn the Hard Way)</title>
      <dc:creator>Khaled waleed</dc:creator>
      <pubDate>Tue, 02 Sep 2025 07:37:24 +0000</pubDate>
      <link>https://dev.to/khaledw/9-things-i-got-wrong-about-building-e-commerce-systems-that-i-had-to-learn-the-hard-way-4h5f</link>
      <guid>https://dev.to/khaledw/9-things-i-got-wrong-about-building-e-commerce-systems-that-i-had-to-learn-the-hard-way-4h5f</guid>
      <description>&lt;h2&gt;
  
  
  👋 A Quick Intro
&lt;/h2&gt;

&lt;p&gt;I've been building and contributing for e-commerce platforms for the majority of my career for startups, multi-vendor marketplaces, and fast-growing businesses. During this time I made a lot of mistakes, made huge money loses I'm not proud of :D but most importantly I solved even more problems thus I learned what works and what doesn't.&lt;/p&gt;

&lt;p&gt;This article is a simple summary of the key things I’ve learned. If you’re building an e-commerce system (whether for physical goods, digital goods, or services) I hope this helps you avoid the same mistakes and design better systems from the start.&lt;/p&gt;




&lt;h3&gt;
  
  
  💰 1. Just storing the final price is a long-term liability
&lt;/h3&gt;

&lt;p&gt;Most platforms only store what the customer paid. It feels enough — until finance starts asking questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“What was our margin after fees and discounts?”
&lt;/li&gt;
&lt;li&gt;“Why do two identical orders show different profitability?”
&lt;/li&gt;
&lt;li&gt;“How much did this campaign really cost us?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that’s when you realize… you didn’t track any of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If you only store the final paid amount, you lose visibility into how that price was built. And worse, if prices change over time, you have no way to go back and understand what the price was at the time of purchase. You’ll be blind to your own data — and reports will become guesswork.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Break the price down into its components: base cost, discounts, shipping, tax, fees, etc.&lt;br&gt;&lt;br&gt;
And just as importantly — store historical pricing at the time of the order. Even if your pricing model evolves, the record of what was sold, at what cost and margin, should never change.  &lt;/p&gt;




&lt;h3&gt;
  
  
  ⚙️ 2. Not everything needs to go through a sprint
&lt;/h3&gt;

&lt;p&gt;This one hits hard in growing teams.&lt;/p&gt;

&lt;p&gt;Sometimes ops just needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A way to export tracking numbers
&lt;/li&gt;
&lt;li&gt;A quick CSV parser
&lt;/li&gt;
&lt;li&gt;A hacky tool to clean vendor data or trigger a re-sync
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But instead, we say:&lt;br&gt;&lt;br&gt;
“Let’s write a ticket → estimate it → groom it → prioritize it → plan it → build it.”&lt;br&gt;&lt;br&gt;
And while we plan, ops suffers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The difference between a 2-week feature and a 2-hour tool can be one hour of autonomy — and your willingness to move fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When the solution is obvious and internal, build it. A dirty HTML file that exports shipment IDs can save days of manual work — and earn a lot of trust.  &lt;/p&gt;

&lt;p&gt;👉 Not everything needs a roadmap. Some things just need a quick win.&lt;/p&gt;




&lt;h3&gt;
  
  
  📦 3. Inventory is not a number — it’s a history
&lt;/h3&gt;

&lt;p&gt;“10 in stock” looks fine — until someone asks:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Where did the stock come from?”
&lt;/li&gt;
&lt;li&gt;“Was it sold or returned?”
&lt;/li&gt;
&lt;li&gt;“Why are we off by 3 units?”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there’s no way to answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Ops teams rely on traceability. If you can’t explain changes, your stock levels are just guesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Track every inventory movement — what changed, when, why, and who did it. You’ll prevent hours of manual tracing and avoid losing trust from internal teams.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧾 4. If you can’t reconcile it, you can’t trust it
&lt;/h3&gt;

&lt;p&gt;Everything looks good in the dashboard — until finance or vendors ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Why is Stripe reporting less than the platform?”
&lt;/li&gt;
&lt;li&gt;“Why didn’t this vendor receive their full payout?”
&lt;/li&gt;
&lt;li&gt;“Why is the total off by 3% this month?”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If your numbers can’t be matched with bank or gateway records, no one will trust your reports — no matter how nice they look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Track the entire money flow — including gateway fees, commissions, VAT, and vendor shares. Your system should act like a financial ledger behind the scenes.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚚 5. Delivery logic must be swappable
&lt;/h3&gt;

&lt;p&gt;You start with one delivery provider and build everything around them. Then ops says:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“We’re switching to another courier.”
&lt;/li&gt;
&lt;li&gt;“This one needs label printing.”
&lt;/li&gt;
&lt;li&gt;“We need to support restaurant delivery.”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now you’re rebuilding your delivery logic all over again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Tightly coupling your system to one provider makes you inflexible. Ops can’t move fast, and your dev team becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Design your delivery engine to support multiple providers. Think of each integration as pluggable — because they will change.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧮 6. ERP integration will expose everything you skipped
&lt;/h3&gt;

&lt;p&gt;At some point, someone will say:&lt;br&gt;&lt;br&gt;
“Can we sync orders and payouts into the accounting system?”  &lt;/p&gt;

&lt;p&gt;That’s when you realize:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discounts and fees are all lumped together
&lt;/li&gt;
&lt;li&gt;There’s no mapping to account codes or payment types
&lt;/li&gt;
&lt;li&gt;Tax is handled manually
&lt;/li&gt;
&lt;li&gt;General Ledger integration is near impossible
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You can’t close the books — or scale operations — without clean financial data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Even before ERP is in scope, structure your financial records to reflect reality: revenue, cost, tax, fees, commissions, etc.&lt;/p&gt;




&lt;h3&gt;
  
  
  🕓 7. Status history is more valuable than current status
&lt;/h3&gt;

&lt;p&gt;Everyone looks at the current order status. But when problems happen, people ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“When did it move to paid?”
&lt;/li&gt;
&lt;li&gt;“How long was it stuck in shipped?”
&lt;/li&gt;
&lt;li&gt;“Who changed it manually?”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you realize: you don’t know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A single status field tells you nothing about the journey. And that’s where the root causes live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Track every status change — timestamp, user, and reason. This is how you trace issues, measure SLAs, and answer vendor questions with confidence.&lt;/p&gt;




&lt;h3&gt;
  
  
  👀 8. No audit logs? You’ll regret it
&lt;/h3&gt;

&lt;p&gt;In small teams, you can ask around:&lt;br&gt;&lt;br&gt;
“Did someone edit this?”&lt;br&gt;&lt;br&gt;
“Who updated the payout?”  &lt;/p&gt;

&lt;p&gt;But in a growing company, across shifts and regions — that’s impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Without audit logs, everything becomes a guessing game. And that slows teams down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Log every change to sensitive data: orders, inventory, prices, vendors. This isn’t about control — it’s about trust, transparency, and traceability.&lt;/p&gt;




&lt;h3&gt;
  
  
  📊 9. Reporting is not analytics
&lt;/h3&gt;

&lt;p&gt;Early on, it feels enough to just show raw data in dashboards: sales totals, order counts, top products. But soon, stakeholders start asking:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Which marketing channel drove the highest-margin sales?”
&lt;/li&gt;
&lt;li&gt;“Which customers are profitable vs. loss-making?”
&lt;/li&gt;
&lt;li&gt;“What’s the true CAC vs. LTV?”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And your system can’t answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Dashboards with raw numbers don’t drive decisions. Without context and proper breakdowns, data becomes noise — not insight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do instead:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Design reporting with analytics in mind:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag data by source (campaign, channel, customer segment)
&lt;/li&gt;
&lt;li&gt;Track cohorts and profitability, not just revenue
&lt;/li&gt;
&lt;li&gt;Build the ability to drill down, not just sum up
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good analytics will drive better product decisions, marketing spend, and operational efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Final thoughts
&lt;/h2&gt;

&lt;p&gt;Most e-commerce systems don’t collapse because of a bug in the code. They collapse when teams are forced to scale on top of fragile foundations.&lt;/p&gt;

&lt;p&gt;In my experience, the systems that thrive over time are the ones that are easy to explain to others, trusted across departments, and resilient to change. You can trace every order. You can justify every payout. You can onboard new team members without a wall of confusion.&lt;/p&gt;

&lt;p&gt;These platforms aren’t just “built” — they’re designed with care, with structure, and with a clear understanding of what’s coming next.&lt;/p&gt;

&lt;p&gt;If you’re in the middle of building, scaling, or even cleaning up an e-commerce platform, I hope these lessons help you think a few steps ahead.  &lt;/p&gt;

&lt;p&gt;And if you’ve been through some of this already — I’d love to hear what you learned along the way.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>development</category>
      <category>community</category>
    </item>
  </channel>
</rss>
