<?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>Forem: Hugo Campañoli</title>
    <description>The latest articles on Forem by Hugo Campañoli (@campadev).</description>
    <link>https://forem.com/campadev</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%2F3856393%2Fe4274eee-4db9-4d70-b2ee-e711457aedb5.png</url>
      <title>Forem: Hugo Campañoli</title>
      <link>https://forem.com/campadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/campadev"/>
    <language>en</language>
    <item>
      <title>The Lindy Effect: Why Web Standards Are a Safer Financial Investment Than the Latest JS Framework</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Wed, 29 Apr 2026 14:38:48 +0000</pubDate>
      <link>https://forem.com/campadev/the-lindy-effect-why-web-standards-are-a-safer-financial-investment-than-the-latest-js-framework-pnn</link>
      <guid>https://forem.com/campadev/the-lindy-effect-why-web-standards-are-a-safer-financial-investment-than-the-latest-js-framework-pnn</guid>
      <description>&lt;p&gt;Every dependency you add is a bet that someone else won't abandon their project. If the code you write today relies on an abstraction born yesterday, you are buying technical debt with a short expiration date. Choosing tools based on "hype" isn't a criterion, it's gambling. &lt;/p&gt;

&lt;p&gt;Experience looks for probability of survival.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the Lindy Effect tell us about technical debt?
&lt;/h2&gt;

&lt;p&gt;In software, time acts as the ultimate quality filter. Unlike living beings, ideas and technologies gain "life expectancy" for every year they survive in production. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Lindy Effect&lt;/strong&gt; states that the life expectancy of non-perishable technologies is proportional to their current age.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;HTML/CSS:&lt;/strong&gt; 30+ years old. Highly likely to survive another 30.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;JS Frameworks:&lt;/strong&gt; 2-5 years old. High risk of breaking changes and deprecation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing code coupled to the native browser API means the code from 10 years ago &lt;strong&gt;still works today&lt;/strong&gt; without needing to run &lt;code&gt;npm audit fix&lt;/code&gt; or fighting broken dependencies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Takeaway:&lt;/strong&gt; Web standards aren't boring: they are infrastructure, not an expense. They don't eliminate maintenance, but they eliminate external volatility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why is choosing a framework a financial gamble?
&lt;/h2&gt;

&lt;p&gt;Choosing a framework is not a technical decision. It's signing a future contract that includes three mandatory costs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Forced migrations&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Technological lock-in&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Constant team retraining&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frameworks are not the problem; the problem is using them where the standard is already enough.&lt;/p&gt;

&lt;p&gt;If you can't justify why that extra layer of abstraction will outlast the product's own lifecycle, you are gambling with your client's money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-term ROI: The 0kb Approach
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JS Abstraction&lt;/th&gt;
&lt;th&gt;Native Web Standards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ephemeral&lt;/td&gt;
&lt;td&gt;High Stability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical Annuity&lt;/td&gt;
&lt;td&gt;Compound Interest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant &lt;code&gt;npm update&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Just works&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Investing in web standards is buying compound interest. Investing in ephemeral frameworks is paying a technical annuity just to keep the ship afloat.&lt;/p&gt;

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

&lt;p&gt;The best code is the one that keeps working when no one is maintaining it. If you need constant updates just to keep your stack alive, you didn't build an investment: you bought a subscription. &lt;/p&gt;

&lt;p&gt;What depends less, lasts longer.&lt;/p&gt;




&lt;p&gt;Want to dive deeper into engineering strategies for long-term ROI? Follow me here on Dev.to or visit my digital garden.&lt;/p&gt;

&lt;p&gt;This post was originally published on &lt;a href="https://campa.dev/en/til/efecto-lindy-estandares-web/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=efecto-lindy-estandares-web" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>TypeScript DeepPath: Achieving Indestructible Types in Nested Objects</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Tue, 28 Apr 2026 17:59:45 +0000</pubDate>
      <link>https://forem.com/campadev/typescript-deeppath-achieving-indestructible-types-in-nested-objects-44ne</link>
      <guid>https://forem.com/campadev/typescript-deeppath-achieving-indestructible-types-in-nested-objects-44ne</guid>
      <description>&lt;p&gt;If you're writing TypeScript code but still using &lt;code&gt;string&lt;/code&gt; to reference keys in nested objects (like translation files or configurations), you're missing out on half of the compiler's power.&lt;/p&gt;

&lt;p&gt;Mastering &lt;strong&gt;Recursive Template Literal Types&lt;/strong&gt; allows you to create strict accesses that the IDE understands perfectly, rooting out silent "undefined" errors at runtime. It's not about over-typing; it's about designing a developer experience (DX) where the editor won't let you make mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why you need DeepPath
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real Validation:&lt;/strong&gt; &lt;code&gt;"api.endpionts.users"&lt;/code&gt; stops compiling automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full path autocompletion:&lt;/strong&gt; The IDE natively suggests all possible combinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance:&lt;/strong&gt; If you change a key in the config, the compiler breaks wherever it's used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The silent bug
&lt;/h2&gt;

&lt;p&gt;Before looking at the solution, let's see the problem. Using &lt;code&gt;string&lt;/code&gt; to access deep properties hides bugs that TypeScript should easily catch:&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;// Typical in i18n or configs&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles perfectly, fails in production&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api.enpoints.users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// The error? You wrote "enpoints" instead of "endpoints". &lt;/span&gt;
&lt;span class="c1"&gt;// TS doesn't warn you, and your app returns undefined at runtime.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The pattern: Recursive DeepPath
&lt;/h2&gt;

&lt;p&gt;The secret lies in combining recursion with &lt;code&gt;Template Literal Types&lt;/code&gt;. See how we can extract all possible paths from an object as if they were a dot-joined string:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DeepPath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&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="nx"&gt;K&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&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;K&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&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;K&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;DeepPath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;K&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="p"&gt;}[&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Usage example:&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nl"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ConfigPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DeepPath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: "api" | "theme" | "api.endpoints" | "api.timeout" | "api.endpoints.users" | "api.endpoints.posts"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Developer Evolution: Using &lt;code&gt;satisfies&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I often see configurations annotated manually (&lt;code&gt;const config: AppConfig = ...&lt;/code&gt;). This "flattens" the type and erases the specific inference of your actual data.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;DeepPath&amp;lt;T&amp;gt;&lt;/code&gt; to work perfectly, it needs to know the exact literal structure. That's where the &lt;code&gt;satisfies&lt;/code&gt; operator comes in: it validates that you meet the interface but &lt;strong&gt;keeps the literals intact&lt;/strong&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="c1"&gt;// The "Wizard" Path&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 'config.theme' remains as the literal "dark"&lt;/span&gt;
&lt;span class="c1"&gt;// and we can navigate any path with full safety:&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeepPath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation logic...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💥 Bonus Track: Return Type Inference (DeepValue)
&lt;/h2&gt;

&lt;p&gt;Validating the input string is great, but true level 500 is inferring the exact &lt;strong&gt;return type&lt;/strong&gt; based on that string. If I request &lt;code&gt;"api.timeout"&lt;/code&gt;, TypeScript should know it returns a &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We combine &lt;code&gt;DeepPath&lt;/code&gt; with another recursive type called &lt;code&gt;DeepValue&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DeepValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;K&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;infer&lt;/span&gt; &lt;span class="nx"&gt;Rest&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;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;DeepValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;Rest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Pure magic:&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;DeepPath&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DeepValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// implementation (e.g., using lodash/get)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api.endpoints.users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// inferred as string&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api.timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// inferred as number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The cost of magic (Trade-offs)
&lt;/h2&gt;

&lt;p&gt;No advanced pattern comes for free. In production, abusing this recursion has real technical limits that will hit you if the object grows too large.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zero typo-related runtime errors.&lt;/td&gt;
&lt;td&gt;Compiler performance: generates massive unions that can slow down the IDE.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100% safe and traceable refactoring.&lt;/td&gt;
&lt;td&gt;Recursion limits: TS might halt if the object is excessively deep.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native autocompletion without extra plugins.&lt;/td&gt;
&lt;td&gt;Complexity: Dynamic arrays make inference much harder.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;This technique drastically reduces headaches. By implementing these types, you force the compiler to validate the actual structure of your data before the build. If you work on mid-sized projects with predictable configurations or i18n systems, this pattern saves you hours of debugging.&lt;/p&gt;




&lt;p&gt;If you're interested in taking type safety to the next level, check out my other posts on &lt;a href="https://campa.dev/en/til/typescript-deep-paths/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=typescript-deep-paths" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>dx</category>
    </item>
    <item>
      <title>Your Slow Website is a Legal Risk: Why Technical Debt is Now a Liability</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Thu, 23 Apr 2026 12:08:31 +0000</pubDate>
      <link>https://forem.com/campadev/your-slow-website-is-a-legal-risk-why-technical-debt-is-now-a-liability-551o</link>
      <guid>https://forem.com/campadev/your-slow-website-is-a-legal-risk-why-technical-debt-is-now-a-liability-551o</guid>
      <description>&lt;p&gt;&lt;em&gt;Hey DEV community! 👋 After auditing dozens of enterprise platforms, I’ve noticed a dangerous trend. We talk a lot about JS bloat ruining Core Web Vitals, but there's a much darker side to heavy payloads in 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Web performance is no longer a vanity metric. As we prepare for the full enforcement of strict data protection frameworks—like Europe's GDPR and Paraguay's upcoming Law 7593/2025—the efficiency of your software is a matter of operational sovereignty and legal compliance. &lt;/p&gt;

&lt;p&gt;If your platform dispatches megabytes of useless JavaScript, you aren't just frustrating users on 4G networks; you are hiding attack surfaces and monitoring risks that expose your company to massive fines.&lt;/p&gt;

&lt;h3&gt;
  
  
  The TL;DR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Excessive JS increases the surface area&lt;/strong&gt; where unaudited third-party integrations can hide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency in rural areas&lt;/strong&gt; halts B2B logistics and burns money in downtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;'Vibe Coding'&lt;/strong&gt; (unsupervised AI generation) is inflating technical debt to unsustainable levels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A technical performance audit&lt;/strong&gt; is the first practical step toward making compliance verifiable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Latency in the Field Burns Money
&lt;/h2&gt;

&lt;p&gt;When evaluating the technical infrastructure of private ports and agro-exporters, the pattern repeats itself. Hundreds of trucks wait during peak harvest while the freight loading system—built on a heavy framework no one asked for—takes 15 seconds to respond. The 4G signal is unstable, and the driver's smartphone freezes trying to parse 5MB of redundant JavaScript.&lt;/p&gt;

&lt;p&gt;The truck doesn't move. The line grows. Efficiency plummets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Cost of Inefficiency:&lt;/strong&gt; 15s+ blocking time on mid-range devices under rural networks.&lt;br&gt;
&lt;strong&gt;Conversion Impact:&lt;/strong&gt; ~15% sales loss per second of latency based on 2026 industry benchmarks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't about user patience. It's a hard technical blockade. Your software cannot rely on a fiber-optic connection to function in a rural silo. &lt;strong&gt;Performance is a design constraint, not an afterthought optimization.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy by Design: How to audit hidden trackers in the bundle
&lt;/h2&gt;

&lt;p&gt;Excessive JavaScript doesn't just slow down your site; it drastically increases the attack and monitoring surface where unaudited third-party integrations can hide, often leaking data without oversight.&lt;/p&gt;

&lt;p&gt;When auditing corporate systems and B2B platforms, the finding is almost universal: forgotten marketing pixels, analytics from agencies that no longer work with the company, and widgets that read user IPs and behavior without oversight. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Industry benchmarks show that while third-party scripts might only represent 15% of the total page weight, they can monopolize the browser's CPU execution time&lt;/strong&gt;, often hiding data leaks behind performance bottlenecks. Under modern privacy laws, this is a massive legal liability.&lt;/p&gt;

&lt;p&gt;These laws demand &lt;strong&gt;Privacy by Design&lt;/strong&gt;. If you don't know what those 3MB of scripts are loading in your client's browser, you are operating blind. Auditing the Network Payload is the only practical way to verify what data is actually leaving your system. Without that visibility, compliance is purely theoretical. If you cannot audit the origin of every bit you send, you do not have sovereignty over your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The systemic impact of AI-driven "Vibe Coding"
&lt;/h2&gt;

&lt;p&gt;Generating code with Claude or Copilot is easy; understanding its systemic consequences is the real job. The trend of &lt;strong&gt;"Vibe Coding"&lt;/strong&gt;—accepting AI-generated code without reviewing new dependencies, bundle size impact, or data flow—is creating systems that are functionally correct but architecturally obese.&lt;/p&gt;

&lt;p&gt;An AI agent can deliver a functional portal in 48 hours, but it will hand it to you coupled and full of redundant dependencies that no one will know how to maintain in two years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Trade-offs of Vibe Coding:&lt;/strong&gt;&lt;br&gt;
✅ &lt;strong&gt;Pros:&lt;/strong&gt; Aggressive Time-to-Market, Almost instant prototyping.&lt;br&gt;
❌ &lt;strong&gt;Cons:&lt;/strong&gt; Technical debt multiplied, Payloads that destroy INP and mobile UX, 'Ghost' dependencies that no one audited.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI writes fast, but your infrastructure pays the bill slowly.&lt;/em&gt; A Senior Architect has never been more necessary to filter the noise of generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack Autopsy
&lt;/h2&gt;

&lt;p&gt;At campa.dev, we don't audit to get a 100 on Lighthouse; that's just the baseline. We audit to make your business resilient:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Real Stress Metrics:&lt;/strong&gt; We test INP (Interaction to Next Paint) on devices people actually use in the field, not on an M3 MacBook Pro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload Cleansing:&lt;/strong&gt; We identify which scripts violate data protection laws by sending data to external servers without consent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Path:&lt;/strong&gt; We evaluate if your bloated SPA should become a static site in Astro 6 to save 40% on server costs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most companies don't have a performance problem. They have a visibility problem.&lt;br&gt;
They don't know what's running on their frontend.&lt;br&gt;
They don't know what data is leaving.&lt;br&gt;
They don't know what they are breaking.&lt;/p&gt;

&lt;p&gt;A sovereign, fast, and legal system is your right to operate without friction, but you cannot fix what you haven't measured. Do you know what your code is doing right now?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If your business handles critical data and you can't afford to be legally exposed, let's talk. Check out the original deep-dive on my blog: &lt;a href="https://campa.dev/en/blog/auditoria-rendimiento-soberania-paraguay/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=auditoria-rendimiento-soberania-paraguay" rel="noopener noreferrer"&gt;campa.dev - Performance is Compliance&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>performance</category>
      <category>security</category>
    </item>
    <item>
      <title>Edge Validation: Why Zod 4 is the Standard for SEO</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Tue, 21 Apr 2026 13:57:36 +0000</pubDate>
      <link>https://forem.com/campadev/edge-validation-why-zod-4-is-the-standard-for-seo-4g75</link>
      <guid>https://forem.com/campadev/edge-validation-why-zod-4-is-the-standard-for-seo-4g75</guid>
      <description>&lt;p&gt;If you want Google, Perplexity, and ChatGPT to cite your content with mathematical precision, you can't leave your metadata to chance. Data integrity is the absolute foundation of modern technical authority.&lt;/p&gt;

&lt;p&gt;Validating data isn't just about avoiding a terminal error; it's about ensuring your GEO entities and JSON-LD schemas are bulletproof from the server. Zod 4 solves this directly at the Edge or at build time, shielding your ranking without shipping a single byte to the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Metadata Slop" Problem
&lt;/h3&gt;

&lt;p&gt;We're tired of seeing sites injecting broken JSON-LD because an author forgot to close a quote or put a relative URL in the frontmatter instead of an absolute one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Cost of Failure:&lt;/strong&gt; A malformed schema isn't just ignored in 2026; LLMs assume the source is unreliable, discard your content, and look for the answer on your competitor's domain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Zod 4 to the Rescue
&lt;/h3&gt;

&lt;p&gt;With the &lt;strong&gt;Astro 6 Content Layer&lt;/strong&gt;, you can process and validate all your content before it even touches the HTML. Zod 4 is the gatekeeper of this frontier.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Performance Pro-Tip:&lt;/strong&gt; Zod 4 uses a JIT (Just-In-Time) compilation engine that makes it up to 14 times faster than v3. But beware: schema compilation has a cost. &lt;strong&gt;Hoist your schemas:&lt;/strong&gt; always define them outside your collections so they compile only once, not on every loader cycle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Look at how we build a bulletproof schema using the new official import:&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;// src/content.config.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;defineCollection&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="s2"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;z&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="s2"&gt;astro/zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="c1"&gt;// Hoist the schema to leverage Zod 4 JIT &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seoSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;canonicalURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="na"&gt;geoEntities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Minimum 2 entities&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/content/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;seoSchema&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;That &lt;code&gt;.transform()&lt;/code&gt; is pure magic. It normalizes data (trimming spaces and forcing lowercase) ensuring that your &lt;code&gt;&amp;lt;Schema /&amp;gt;&lt;/code&gt; generating component receives exactly what it needs to build the perfect knowledge graph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact on Performance and Authority
&lt;/h3&gt;

&lt;p&gt;Thanks to the new JIT engine, &lt;strong&gt;parsing speed is 14 times faster&lt;/strong&gt; than in Zod 3.&lt;/p&gt;

&lt;p&gt;Unlike previous versions, Zod 4 unifies synchronous and asynchronous execution paths without the "double execution" penalty. This allows real-time checks during the Astro 6 build. If the data is bogus, the build fails: &lt;strong&gt;failing fast in CI/CD is infinitely better than pushing garbage metadata to production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Test it today. Break your frontmatter on purpose and see how Zod catches the problem before it ruins your rankings. Implement strict validation if you don't want AI to have any room to invent data about your project.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This TIL was originally published on my blog. You can find more engineering, Astro, and Web Performance deep-dives at &lt;a href="https://campa.dev/en/til/zod-4-edge-validation-seo/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=zod-4-edge-validation-seo" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>performance</category>
      <category>seo</category>
    </item>
    <item>
      <title>Privacidad por Diseño: Checklist técnico para la Ley 7593/2025</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:50:00 +0000</pubDate>
      <link>https://forem.com/campadev/privacidad-por-diseno-checklist-tecnico-para-la-ley-75932025-2l5f</link>
      <guid>https://forem.com/campadev/privacidad-por-diseno-checklist-tecnico-para-la-ley-75932025-2l5f</guid>
      <description>&lt;p&gt;La privacidad por diseño deja de ser una sugerencia ética para transformarse en el estándar legal obligatorio bajo la &lt;strong&gt;Ley 7593/2025 de Paraguay&lt;/strong&gt;. Este cambio legislativo marca un hito en la madurez digital del país, obligando a empresas y desarrolladores a repensar cómo capturan, almacenan y protegen la información ciudadana.&lt;/p&gt;

&lt;p&gt;El MITIC ha sido claro: no bastará con términos y condiciones genéricos; el software debe integrar protecciones técnicas &lt;strong&gt;desde la arquitectura inicial&lt;/strong&gt;, no como un parche posterior.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Qué exige la Ley 7593/2025 a tu software?
&lt;/h2&gt;

&lt;p&gt;La ley establece que el responsable del tratamiento de datos debe implementar medidas técnicas y organizativas &lt;strong&gt;desde el diseño&lt;/strong&gt; del sistema. No es un checklist que se añade antes del deploy. Es arquitectura.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sanciones:&lt;/strong&gt; La Ley 7593/2025 prevé multas de hasta 20.000 jornales mínimos para quienes no implementen medidas de protección adecuadas. Con el jornal de 2026, eso supera los &lt;strong&gt;₲ 2.230 millones&lt;/strong&gt; por incidente.&lt;/p&gt;

&lt;p&gt;La traducción técnica de la ley es directa:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artículo de Ley&lt;/th&gt;
&lt;th&gt;Qué dice&lt;/th&gt;
&lt;th&gt;Requerimiento Técnico&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Art. 4(d) — Minimización&lt;/td&gt;
&lt;td&gt;Datos "limitados a lo necesario"&lt;/td&gt;
&lt;td&gt;Validar que solo se recopilan campos &lt;strong&gt;necesarios&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Art. 6 — Consentimiento&lt;/td&gt;
&lt;td&gt;"Previo, libre, e inequívoco"&lt;/td&gt;
&lt;td&gt;Opt-in explícito + registro auditado de versión legal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Art. 9 — Responsable&lt;/td&gt;
&lt;td&gt;"Medidas técnicas apropiadas"&lt;/td&gt;
&lt;td&gt;Privacy-by-design en la arquitectura&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Art. 16 — Seguridad&lt;/td&gt;
&lt;td&gt;"Monitoreo y mejora continua"&lt;/td&gt;
&lt;td&gt;Cifrado en tránsito y en reposo (AES-256)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arts. 26-31 — Derechos ARCO&lt;/td&gt;
&lt;td&gt;Acceso y supresión en ≤30 días&lt;/td&gt;
&lt;td&gt;API de exportación + endpoint de eliminación&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ¿Cómo implementar privacidad por diseño en tu stack?
&lt;/h2&gt;

&lt;p&gt;La respuesta corta: &lt;strong&gt;no guardes lo que no necesitás, cifrá lo que sí guardás, y dejá que el usuario borre todo cuando quiera&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 1: Minimización de datos
&lt;/h3&gt;

&lt;p&gt;Cada campo que agregás a un formulario es una responsabilidad legal. Validá en el schema, no en el frontend:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Schema que rechaza datos innecesarios por diseño&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ContactFormSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// Sin teléfono, sin nombre completo, sin dirección&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ContactForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ContactFormSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;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;code&gt;z.strictObject()&lt;/code&gt; rechaza cualquier campo que no esté en el schema. Si alguien intenta inyectar data extra, Zod tira un error. La minimización se vuelve inquebrantable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 2: Consentimiento explícito
&lt;/h3&gt;

&lt;p&gt;Una casilla pre-marcada no es consentimiento. Necesitás un opt-in explícito y auditado:&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;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ConsentRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;legalTextVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// El hash del texto legal vigente&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El &lt;code&gt;legalTextVersion&lt;/code&gt; es clave: si actualizás los términos, necesitás saber qué versión aceptó cada usuario.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 3: Cifrado y retención
&lt;/h3&gt;

&lt;p&gt;Los datos que guardás tienen fecha de vencimiento. La retención indefinida es incumplimiento automático:&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;// Job diario que purga datos expirados&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;purgeExpiredData&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;RETENTION_DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;365&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;cutoff&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;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;RETENTION_DAYS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;lastActiveAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cutoff&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="na"&gt;purposeConsent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`anon-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;@anonimizado.local`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Usuario Anonimizado&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending_deletion&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="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;La privacidad por diseño no te hace menos ágil — te hace más profesional. Y en Paraguay, donde la transformación digital del agro y el comercio apenas arranca, ser el que cumple primero es un diferencial que tus clientes van a notar.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Para ver el código completo y los componentes de arquitectura, revisá el &lt;a href="https://campa.dev/es/blog/privacidad-por-diseno-ley-7593-paraguay/" rel="noopener noreferrer"&gt;artículo original en mi blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>compliance</category>
      <category>security</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Algebraic Data Types in TS: Indestructible Payment Flows</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Fri, 10 Apr 2026 18:23:27 +0000</pubDate>
      <link>https://forem.com/campadev/algebraic-data-types-in-ts-indestructible-payment-flows-jak</link>
      <guid>https://forem.com/campadev/algebraic-data-types-in-ts-indestructible-payment-flows-jak</guid>
      <description>&lt;p&gt;Your payment flow has a &lt;code&gt;status: string&lt;/code&gt; field? You're one typo away from a double charge. A silent &lt;code&gt;"pednign"&lt;/code&gt; in production won't throw an error; it loses money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Algebraic Data Types&lt;/strong&gt; (ADTs) in TypeScript let you model states where invalid data is compiler-illegal. Not just "unlikely." &lt;strong&gt;Forbidden.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: The bag of optionals
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Everything is possible... including double charging&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaymentState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;receiptUrl&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// When does transactionId exist?&lt;/span&gt;
&lt;span class="c1"&gt;// Is it safe to read receiptUrl?&lt;/span&gt;
&lt;span class="c1"&gt;// Nobody knows. Runtime will tell.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you use a simple string for status, you end up with an interface where everything is optional. The compiler cannot help you because it doesn't know the relationship between the status and the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: Discriminated Unions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ The compiler forbids illegal states&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PaymentState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;intentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;intentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bancard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe&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="o"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;receiptUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;Each union variant carries &lt;strong&gt;only&lt;/strong&gt; relevant data. No &lt;code&gt;transactionId&lt;/code&gt; in &lt;code&gt;"pending"&lt;/code&gt;; no &lt;code&gt;errorMessage&lt;/code&gt; in &lt;code&gt;"success"&lt;/code&gt;. The compiler guarantees integrity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guard: Exhaustive switch with &lt;code&gt;never&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaymentState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Waiting for user action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Intent created: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intentId&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Processing via &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gateway&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`✅ TX: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retryable&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Error &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: retrying`&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Fatal error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Added a new state and forgot to handle it?&lt;/span&gt;
      &lt;span class="c1"&gt;// TypeScript yells here ↓&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_exhaustive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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;_exhaustive&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that &lt;code&gt;never&lt;/code&gt;, any new state added to the union passes silently through the switch. In a payment flow, "silent" means "duplicate charges at 3AM without logs."&lt;/p&gt;

&lt;h2&gt;
  
  
  ADTs vs. Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Discriminated Unions (ADTs)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pros:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;State-specific data (no loose optionals)&lt;/li&gt;
&lt;li&gt;Automatic compiler narrowing in each case&lt;/li&gt;
&lt;li&gt;Native exhaustiveness checks with never&lt;/li&gt;
&lt;li&gt;Zero runtime overhead (erased during compilation)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  String Enums / Class Hierarchy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cons:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enums: don't link data to state; everything stays optional&lt;/li&gt;
&lt;li&gt;Classes: runtime overhead + fragile instanceof checks&lt;/li&gt;
&lt;li&gt;Enums + interfaces: you duplicate the source of truth&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it works (The Aha! Moment)
&lt;/h2&gt;

&lt;p&gt;An ADT combines finite states with specific data variants. The compiler is your first QA. If you forget a case, it yells before the code ever hits staging. In payments, "unlikely" isn't enough; you need it to be &lt;strong&gt;illegal to represent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A string is a promise. A union is a contract.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://campa.dev/en/til/algebraic-data-types-indestructible-payments/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=algebraic-data-types-indestructible-payments" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Astro 6's 0kb JS is the Ultimate Enterprise SEO Solution</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Thu, 09 Apr 2026 03:00:00 +0000</pubDate>
      <link>https://forem.com/campadev/why-astro-6s-0kb-js-is-the-ultimate-enterprise-seo-solution-1m2j</link>
      <guid>https://forem.com/campadev/why-astro-6s-0kb-js-is-the-ultimate-enterprise-seo-solution-1m2j</guid>
      <description>&lt;p&gt;Crawl Budget is the most scarce resource in enterprise SEO. Google defines it as "the number of URLs Googlebot can and wants to crawl" — and that number has an efficiency ceiling. &lt;/p&gt;

&lt;p&gt;Sites with 100k+ URLs that rely on JS-heavy frameworks (Next.js, Nuxt) often waste critical crawling capacity in the &lt;strong&gt;Web Rendering Service (WRS)&lt;/strong&gt;. This is Google's infrastructure that executes JavaScript, but it creates processing queues that can last days or even weeks.&lt;/p&gt;

&lt;p&gt;Astro 6 solves this by design: &lt;strong&gt;Pure static HTML, 0kb JS runtime by default, and an Island architecture&lt;/strong&gt; that protects rendering fidelity.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost of JavaScript Rendering
&lt;/h2&gt;

&lt;p&gt;Googlebot goes through two passes to index your content:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crawl:&lt;/strong&gt; It downloads the HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering:&lt;/strong&gt; It executes the JS in the WRS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second pass is an asynchronous queue. If your content is hidden behind JS, Googlebot won't "see" it until its turn comes up in the queue. This creates &lt;strong&gt;"Zombie Pages"&lt;/strong&gt;: URLs that are discovered but have incomplete content in the index.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 5 Infrastructure Gates
&lt;/h3&gt;

&lt;p&gt;Jason Barnard describes the pipeline as 5 sequential gates:&lt;br&gt;
&lt;strong&gt;Discovery&lt;/strong&gt; → &lt;strong&gt;Selection&lt;/strong&gt; → &lt;strong&gt;Crawling&lt;/strong&gt; → &lt;strong&gt;Rendering&lt;/strong&gt; → &lt;strong&gt;Indexing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With Astro 6, the rendering gate is practically bypassed. The content is already in the HTML that Googlebot downloads during the Crawling phase. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Legacy (SSR/CSR)&lt;/th&gt;
&lt;th&gt;Astro 6 (SSG/Islands)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JS Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;150-400kb (typical)&lt;/td&gt;
&lt;td&gt;0kb (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infra Gates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 Gates (Discovery to Index)&lt;/td&gt;
&lt;td&gt;4 Gates (Skips Rendering)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Render Fidelity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Variable (Depends on WRS)&lt;/td&gt;
&lt;td&gt;100% (HTML is the truth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Bot Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The AI Factor: Bots don't execute JS
&lt;/h2&gt;

&lt;p&gt;There is an angle most people ignore: &lt;strong&gt;AI bots don't execute JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most search engines (Google, Bing) have rendering capabilities. But Perplexity, smaller AI agents, and training crawlers mostly work with the initial HTML. If your content depends on client-side rendering, to these bots, &lt;strong&gt;your content doesn't exist.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases: Astro 6 vs Next.js
&lt;/h2&gt;

&lt;p&gt;It's not about which framework is "better," but which is right for the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Use Astro 6 for (Content is King):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;E-commerce with large catalogs (100k+ products).&lt;/li&gt;
&lt;li&gt;Directories and Marketplaces with static content.&lt;/li&gt;
&lt;li&gt;Technical documentation portals.&lt;/li&gt;
&lt;li&gt;Enterprise landing pages at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Use Next.js for (Logic is King):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time data dashboards.&lt;/li&gt;
&lt;li&gt;Apps with complex route-based authentication.&lt;/li&gt;
&lt;li&gt;Collaborative tools (editors, whiteboards).&lt;/li&gt;
&lt;li&gt;SaaS platforms with heavy client-side business logic.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Ockham's Razor for SEO
&lt;/h2&gt;

&lt;p&gt;The simplest solution is usually the right one. If Googlebot isn't indexing all your URLs, the answer isn't more servers or more complex SSR. &lt;strong&gt;The answer is less JavaScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In 2026, with AI bots becoming a major source of digital visibility, static HTML is no longer just a performance optimization — it's a multi-bot visibility strategy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://campa.dev/es/blog/0kb-js-astro-6-seo-enterprise/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=0kb-js-astro-6-seo-enterprise" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>seo</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Zod 4 - From Validation to Data Pipelines</title>
      <dc:creator>Hugo Campañoli</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:42:33 +0000</pubDate>
      <link>https://forem.com/campadev/zod-4-from-validation-to-data-pipelines-3ol9</link>
      <guid>https://forem.com/campadev/zod-4-from-validation-to-data-pipelines-3ol9</guid>
      <description>&lt;p&gt;Validating that a field exists is the floor, not the ceiling. Your CMS or API data is often "dirty": dates as strings, empty tags, non-normalized slugs. If you clean this up in your components, you're duplicating logic that &lt;strong&gt;Zod 4&lt;/strong&gt; can solve in a single schema.&lt;/p&gt;

&lt;p&gt;In this guide, we'll see how to transform your validation into a &lt;strong&gt;pure data pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Validator to Transformation Pipeline
&lt;/h3&gt;

&lt;p&gt;Look at the difference between a schema that only validates and one that transforms, normalizes, and performs cross-field validation:&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;// Advanced Schema — Validate + Transform + Normalize&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;\s&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="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;arr&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="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;superRefine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIssue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Date cannot be in the future&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publishedAt&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="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;
  
  
  The Three Patterns You Need to Know
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;.transform()&lt;/code&gt; — Modify data after validation&lt;/strong&gt;&lt;br&gt;
Change the output type directly in the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;.superRefine()&lt;/code&gt; — Cross-field validation&lt;/strong&gt;&lt;br&gt;
Perfect for complex rules like "Published articles require a date, but drafts don't".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;.prefault()&lt;/code&gt; — The new Zod 4 pre-parse default&lt;/strong&gt;&lt;br&gt;
In Zod 4, &lt;code&gt;.default()&lt;/code&gt; applies &lt;strong&gt;after&lt;/strong&gt; the transform. Use &lt;code&gt;.prefault()&lt;/code&gt; if you need the default value to pass through your transformation pipeline (Zod 3 behavior).&lt;/p&gt;




&lt;h3&gt;
  
  
  Where should the logic go?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In the Schema (Zod):&lt;/strong&gt; Single source of truth, automatic output typing, zero boilerplate in UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Components:&lt;/strong&gt; Dispersed logic, harder to test, and redundant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validating is easy. Transforming with elegance is the art of a Senior Engineer.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Originally published at:&lt;/strong&gt; &lt;a href="https://campa.dev/es/til/zod-4-esquemas-dinamicos/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=zod-4-esquemas-dinamicos" rel="noopener noreferrer"&gt;campa.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>zod</category>
      <category>typescript</category>
      <category>astro</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
