<?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: Andrés Clúa</title>
    <description>The latest articles on Forem by Andrés Clúa (@andresclua).</description>
    <link>https://forem.com/andresclua</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%2F812332%2Feb83ad9a-bb84-4902-98a8-1f92591b4ae6.png</url>
      <title>Forem: Andrés Clúa</title>
      <link>https://forem.com/andresclua</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/andresclua"/>
    <language>en</language>
    <item>
      <title>Web Performance Is an Architecture Decision, Not a Last-Minute Fix</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:08:51 +0000</pubDate>
      <link>https://forem.com/andresclua/web-performance-is-an-architecture-decision-not-a-last-minute-fix-3n95</link>
      <guid>https://forem.com/andresclua/web-performance-is-an-architecture-decision-not-a-last-minute-fix-3n95</guid>
      <description>&lt;p&gt;I work at a digital agency and lead the tech side. One thing we try to get right from day one is performance. Not because we're obsessed with Lighthouse scores. Because we've learned that performance problems don't come from bad code. They come from bad decisions made early in the project.&lt;/p&gt;

&lt;p&gt;The CMS you choose. How you handle third-party scripts. Whether you serve videos as raw iframes or defer them. How your CSS is loaded. Where your assets live.&lt;/p&gt;

&lt;p&gt;These are architecture decisions. And once they're made, they're expensive to undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern We Try to Avoid
&lt;/h2&gt;

&lt;p&gt;Here's what happens on a lot of projects across the industry. A site launches. The design looks great. Then someone runs a Lighthouse audit and the score is 43. Panic. Everyone wants to "fix performance" like it's a bug you can patch on a Friday afternoon.&lt;/p&gt;

&lt;p&gt;It's not a bug. It's the consequence of choices nobody questioned months ago.&lt;/p&gt;

&lt;p&gt;We try not to end up there. That means asking the uncomfortable questions at the kickoff, not at the QA stage. It means pushing back sometimes. And it means being honest about what certain tools and platforms cost you in performance before you commit to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your CMS Choice Is a Performance Decision
&lt;/h2&gt;

&lt;p&gt;This is the one that doesn't get enough attention. People choose a CMS based on features, familiarity, or client preference. Performance rarely enters the conversation. But it should. Because some platforms give you a head start and others put you in a hole from the beginning.&lt;/p&gt;

&lt;p&gt;A headless CMS like Sanity, Contentful, or Storyblok gives you full control over the frontend. You decide what gets loaded, when, and how. There's no theme layer injecting scripts you didn't ask for. No plugin ecosystem silently adding database queries and stylesheets on every page load.&lt;/p&gt;

&lt;p&gt;WordPress can absolutely be fast. I've built fast WordPress sites. But it takes discipline. The default experience is a theme that loads everything everywhere, a page builder that inlines CSS for every block variation, and a plugin for every feature that each adds its own JavaScript and stylesheet. Out of the box, a typical WordPress site with a popular theme, a form plugin, an SEO plugin, and a slider is already shipping 2MB+ of assets before you add any actual content.&lt;/p&gt;

&lt;p&gt;That doesn't mean WordPress is bad. It means you have to fight harder for performance on WordPress than on a headless setup. And if you're not aware of that going in, you'll end up with a 43 on Lighthouse wondering what happened.&lt;/p&gt;

&lt;p&gt;The honest breakdown from what I've seen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headless CMS (Sanity, Contentful, Storyblok):&lt;/strong&gt; You own the frontend. Performance is in your hands. The CMS doesn't inject anything into your build. If it's slow, it's your fault. That's actually a good thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WordPress:&lt;/strong&gt; Can be fast, but requires intentional architecture. Block themes perform better than classic themes. Avoid heavy page builders if performance matters. Audit every plugin. Know what each one adds to the page weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monolithic platforms (Drupal, AEM, Sitecore):&lt;/strong&gt; Similar challenges to WordPress but at enterprise scale. More moving parts. More potential for bloat. Performance is achievable but requires dedicated effort and expertise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static site generators (Astro, Nuxt, Next.js in SSG mode):&lt;/strong&gt; The fastest option by default because you're serving pre-built HTML. But the moment you start adding client-side interactivity and third-party scripts, you can still ruin it.&lt;/p&gt;

&lt;p&gt;The point is not that one CMS is better than another. The point is that the CMS decision &lt;em&gt;is&lt;/em&gt; a performance decision, and it should be evaluated as one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The YouTube Embed Problem
&lt;/h2&gt;

&lt;p&gt;Here's a concrete example that comes up constantly. A client site scoring poorly on Core Web Vitals. We investigate. The biggest offender? YouTube embeds.&lt;/p&gt;

&lt;p&gt;A standard YouTube iframe loads around 800KB of JavaScript before the user even clicks play. That's one embed. Put four on a page and the browser is downloading over 3MB of scripts for videos most visitors will never watch.&lt;/p&gt;

&lt;p&gt;LCP tanks. INP suffers because the main thread is busy parsing scripts nobody asked for. The page feels slow because it is slow.&lt;/p&gt;

&lt;p&gt;The fix: don't load the iframe until the user wants to watch. Show a placeholder. Load on click. Reserve the space with &lt;code&gt;aspect-ratio: 16/9&lt;/code&gt; so there's no layout shift.&lt;/p&gt;

&lt;p&gt;This is one of the things I built &lt;a href="https://boostifyjs.com" rel="noopener noreferrer"&gt;Boostify.js&lt;/a&gt; to solve. Instead of dropping a raw YouTube iframe in your markup, you set up a container and let Boostify handle the deferred loading:&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;div&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"js--boostify-embed"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"aspect-ratio: 16/9;"&lt;/span&gt;
  &lt;span class="na"&gt;data-url-youtube=&lt;/span&gt;&lt;span class="s"&gt;"https://www.youtube.com/embed/dQw4w9WgXcQ"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&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 javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bstf&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;Boostify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js--boostify-embed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;videoEmbed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-url-youtube&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auto&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero bytes of YouTube JavaScript loaded until the user interacts. The space is reserved so no layout shift. The page loads as if the video isn't there. When the user clicks, the embed loads instantly.&lt;/p&gt;

&lt;p&gt;This needs to be a default in your component library, not a fix applied after someone complains about the Lighthouse score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third-Party Scripts: The Invisible Weight
&lt;/h2&gt;

&lt;p&gt;YouTube is obvious because you can see it. But the worst offenders are invisible.&lt;/p&gt;

&lt;p&gt;Google Analytics. Facebook Pixel. Hotjar. Microsoft Clarity. HubSpot tracking. LinkedIn Insight Tag. Cookie consent banners. Chat widgets. A/B testing tools.&lt;/p&gt;

&lt;p&gt;On a typical site, I've counted 8 to 12 third-party scripts loading on every page. Each one competes for the main thread. Each one makes network requests. Each one adds weight.&lt;/p&gt;

&lt;p&gt;None of these are critical for the user's first interaction. The user came to read content, check services, fill out a form. They didn't come so Hotjar could record their session before the page renders.&lt;/p&gt;

&lt;p&gt;But these scripts are usually dropped straight into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. Render-blocking. Loading before your actual content.&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="c"&gt;&amp;lt;!-- What I see on most sites when I open the source --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.googletagmanager.com/gtag/js?id=G-XXXXX"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://connect.facebook.net/en_US/fbevents.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://static.hotjar.com/c/hotjar-XXXXX.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- The page hasn't started rendering yet --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Boostify, you mark your tracking scripts with &lt;code&gt;type="text/boostify"&lt;/code&gt; instead of &lt;code&gt;type="text/javascript"&lt;/code&gt;. The browser ignores them on load. Boostify picks them up and loads them only after the user interacts with the page (mouse move, scroll, touch).&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="c"&gt;&amp;lt;!-- Deferred until user interaction --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/boostify"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.googletagmanager.com/gtag/js?id=G-XXXXX"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&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 javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bstf&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;Boostify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Third-party scripts loaded after user interaction&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your content renders immediately. Analytics loads quietly in the background when the user is already engaged. You might lose tracking on 1-2% of ultra-fast bounces, but your site is faster, which typically increases conversions overall.&lt;/p&gt;

&lt;p&gt;My recommendation: consolidate everything into a single Google Tag Manager container, mark that one script with &lt;code&gt;type="text/boostify"&lt;/code&gt;, and let GTM handle all your tags internally. One deferred script instead of twelve blocking ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading the Right Thing at the Right Time
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;onload&lt;/code&gt; trigger handles the global stuff like analytics and tracking. But a real page has more than that. It has a slider halfway down. A contact form with a map at the bottom. An interactive widget in a section most users never scroll to.&lt;/p&gt;

&lt;p&gt;This is where Boostify's &lt;code&gt;scroll&lt;/code&gt; and &lt;code&gt;observer&lt;/code&gt; triggers come in, and where things get interesting when you combine them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scroll trigger&lt;/strong&gt;: fire a callback when the user scrolls past a certain distance. Good for loading resources that you know the user will need soon, like a slider component just below the fold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;// Load slider CSS and JS only when user starts scrolling&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadStyle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&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;https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.4/tiny-slider.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;attributes&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="s1"&gt;media=all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;'&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;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&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;https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.2/min/tiny-slider.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;attributes&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="s1"&gt;type="text/javascript"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Initialize once loaded&lt;/span&gt;
    &lt;span class="nf"&gt;tns&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.my-slider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;slideBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;&lt;strong&gt;Observer trigger&lt;/strong&gt;: fire a callback when a specific element enters the viewport. This uses the Intersection Observer API under the hood. Perfect for content further down the page that may or may not be reached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;root&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="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.contact-section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;// Load the map embed only when the contact section is visible&lt;/span&gt;
    &lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;videoEmbed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&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;https://www.google.com/maps/embed?pb=YOUR_MAP_EMBED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.map-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;400px&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;&lt;strong&gt;The real power is using all three together.&lt;/strong&gt; Think about a typical landing page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bstf&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;Boostify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Analytics and tracking: defer until first user interaction&lt;/span&gt;
&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Below-the-fold slider: load when user starts scrolling&lt;/span&gt;
&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadStyle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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;/css/slider.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head&lt;/span&gt;&lt;span class="dl"&gt;'&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;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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;/js/slider.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&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="c1"&gt;// 3. YouTube video in the middle: load when its section is visible&lt;/span&gt;
&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.video-section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;videoEmbed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&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;https://www.youtube.com/embed/dQw4w9WgXcQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.video-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&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;span class="c1"&gt;// 4. Contact form with map: load only if user reaches the bottom&lt;/span&gt;
&lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.contact-section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="nx"&gt;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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;/js/form-validation.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&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;bstf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadStyle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&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;/css/form.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On initial page load, the browser downloads your HTML, your critical CSS, and your above-the-fold content. That's it. Everything else loads exactly when it's needed.&lt;/p&gt;

&lt;p&gt;This isn't micro-optimization. On a page with a slider, two video embeds, a map, and eight tracking scripts, the difference between loading everything upfront and loading it progressively can be 4-5MB of JavaScript. That's the difference between a 2-second load and an 8-second load on mobile.&lt;/p&gt;

&lt;p&gt;And it's an architecture decision. You set it up once in your component system. Every page benefits from it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLS: Where Your CMS Shows Its True Colors
&lt;/h2&gt;

&lt;p&gt;Cumulative Layout Shift exposes bad architecture more clearly than any other metric.&lt;/p&gt;

&lt;p&gt;You know the experience. You're about to click a button and the page jumps because something loaded above it. A font swaps in and the layout shifts. An image loads without dimensions and pushes everything down.&lt;/p&gt;

&lt;p&gt;This is where CMS choice really matters. A good CMS setup enforces constraints that prevent CLS. A bad one lets editors do whatever they want and hopes for the best.&lt;/p&gt;

&lt;p&gt;If your CMS lets editors drop iframes anywhere without aspect ratio containers, you'll have CLS problems. If your theme doesn't enforce image dimensions, you'll have CLS problems. If your font loading strategy is "link to Google Fonts in the head," you'll have CLS problems.&lt;/p&gt;

&lt;p&gt;The fix isn't a CSS hack after launch. It's building the system so these issues can't happen.&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;/* Defaults that should exist in every project */&lt;/span&gt;
&lt;span class="nt"&gt;img&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;video&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="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="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.embed-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;9&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="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;On a headless setup, you control the component layer. You can enforce these patterns at the component level. On WordPress, you need to be more careful with theme defaults and block output. On page builder-heavy setups, good luck.&lt;/p&gt;

&lt;h2&gt;
  
  
  INP: The Metric That Exposes JavaScript Bloat
&lt;/h2&gt;

&lt;p&gt;Interaction to Next Paint replaced First Input Delay in 2024. It's harder to pass because it measures the responsiveness of &lt;em&gt;all&lt;/em&gt; interactions, not just the first one.&lt;/p&gt;

&lt;p&gt;That hamburger menu that takes 400ms to respond? INP catches it. The accordion that freezes before opening? INP catches that too.&lt;/p&gt;

&lt;p&gt;In 2026, pages at position 1 on Google show a 10% higher Core Web Vitals pass rate than those at position 9. Only 47% of sites reach Google's "good" thresholds across all three metrics. More than half the web is failing.&lt;/p&gt;

&lt;p&gt;The most common INP killer is too much JavaScript on the main thread. Not just third-party scripts. First-party JavaScript too. Heavy page builders that inline JS for every component. Animations running on the main thread. Event listeners doing too much work synchronously.&lt;/p&gt;

&lt;p&gt;This is another place where CMS architecture matters. A site built with a lightweight frontend framework on top of a headless CMS will naturally ship less JavaScript than a WordPress site running a page builder with 40 registered block types, each loading its own script.&lt;/p&gt;

&lt;p&gt;And this is exactly why progressive loading matters. If you defer everything that's not immediately needed (using the &lt;code&gt;onload&lt;/code&gt;, &lt;code&gt;scroll&lt;/code&gt;, and &lt;code&gt;observer&lt;/code&gt; pattern described above), the main thread is free to handle user interactions without competing for resources. INP improves because the browser isn't busy parsing tracking scripts and slider libraries while the user is trying to click things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Budgets at the Kickoff
&lt;/h2&gt;

&lt;p&gt;I've never seen a creative brief that includes a performance budget. Not once across any project in any agency.&lt;/p&gt;

&lt;p&gt;Nobody says "this page should load in under 2.5 seconds on a mid-range Android phone on 4G." But that's how Google evaluates your site. Mobile performance is the primary ranking signal. Even for desktop results.&lt;/p&gt;

&lt;p&gt;53% of mobile visitors abandon a site that takes more than 3 seconds to load. A one-second delay reduces conversions by 7%. For a site generating $100K/month, that's $84K lost per year. For one second.&lt;/p&gt;

&lt;p&gt;These numbers belong in every project kickoff. We try to set targets early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LCP under 2.5s on mobile&lt;/li&gt;
&lt;li&gt;INP under 200ms&lt;/li&gt;
&lt;li&gt;CLS under 0.1&lt;/li&gt;
&lt;li&gt;Total page weight under 1.5MB&lt;/li&gt;
&lt;li&gt;No more than 3 third-party scripts loading before first paint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't aggressive. They're the thresholds Google considers "good." Most sites don't hit them because nobody set them as a goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Data vs Lab Data
&lt;/h2&gt;

&lt;p&gt;One more thing. Stop obsessing over Lighthouse scores in isolation.&lt;/p&gt;

&lt;p&gt;Lighthouse is lab data. It runs in a simulated environment. It's useful for diagnostics. But Google ranks you based on field data. Real users on real devices with real network conditions.&lt;/p&gt;

&lt;p&gt;Your Lighthouse score might be 90 because you tested on your MacBook with fiber internet. But your field data in Search Console might show real users on mid-range phones getting an LCP of 4.2 seconds.&lt;/p&gt;

&lt;p&gt;Field data is what matters. Check your Core Web Vitals report in Search Console. That's the real score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Performance is not a task at the end. It's not a sprint before launch. It's not a plugin.&lt;/p&gt;

&lt;p&gt;It starts with the CMS you choose and how you set it up. It continues with how you handle scripts, images, fonts, and video. It lives in your component architecture, your loading strategy, your defaults.&lt;/p&gt;

&lt;p&gt;Some platforms make this easier. Some make it harder. Neither is inherently wrong, but you need to know the tradeoffs before you commit.&lt;/p&gt;

&lt;p&gt;By the time someone opens DevTools and says "why is this so slow," the answer is usually "because we never decided it should be fast."&lt;/p&gt;

&lt;p&gt;Make that decision early. Make it part of the architecture. Hold every choice accountable to it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://boostifyjs.com" rel="noopener noreferrer"&gt;Boostify.js&lt;/a&gt; is free and open source. All the loading patterns described in this article (deferred third-party scripts, scroll triggers, observer-based loading, video embed placeholders) are available out of the box. &lt;a href="https://github.com/andresclua/boostify" rel="noopener noreferrer"&gt;Check it out on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>boostifyjs</category>
      <category>frontend</category>
      <category>marketing</category>
    </item>
    <item>
      <title>EmDash Is Not Your Next CMS. It Might Be the One After That</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Mon, 06 Apr 2026 17:20:55 +0000</pubDate>
      <link>https://forem.com/andresclua/emdash-is-not-your-next-cms-it-might-be-the-one-after-that-3358</link>
      <guid>https://forem.com/andresclua/emdash-is-not-your-next-cms-it-might-be-the-one-after-that-3358</guid>
      <description>&lt;p&gt;Everyone is talking about EmDash. Serverless, sandboxed plugins, AI-first. Cool. I get it.&lt;/p&gt;

&lt;p&gt;I work at a digital agency. We've been building on WordPress for years. Also Sanity, Contentful, Storyblok, Drupal, AEM. We've tried a lot. So when something new shows up, I don't get hyped that fast. I just want to know: can I use this for a real project?&lt;/p&gt;

&lt;h2&gt;
  
  
  We don't pick favorites
&lt;/h2&gt;

&lt;p&gt;There's no "best CMS." Every project is different.&lt;/p&gt;

&lt;p&gt;WordPress is a great tool, we build custom plugins, custom post types, REST API endpoints, tailored admin panels. It's not a blogging platform for us. It's a framework. So when people compare WordPress to newer tools by pointing at Gutenberg or starter themes... they're looking at one layer. That's not how we work with it.&lt;/p&gt;

&lt;p&gt;Sanity is great when content modeling gets complex. Storyblok has a really nice visual editor that clients actually enjoy using. Contentful works when the client needs a clean API-first layer and doesn't want to deal with hosting. Drupal taught us a lot about structured content before headless was even a thing.&lt;/p&gt;

&lt;p&gt;AEM is enterprise. Enterprise scale, enterprise money. It solves problems that other platforms don't even try to solve.&lt;/p&gt;

&lt;p&gt;We picked each one for a reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about EmDash
&lt;/h2&gt;

&lt;p&gt;There are some ideas in EmDash that are genuinely interesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless by default.&lt;/strong&gt; No more dealing with PHP memory limits or server configs. That's nice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sandboxed plugins.&lt;/strong&gt; Plugins run in isolation. No more "one plugin broke the whole site" at 2am. That alone is a big deal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI built into the core.&lt;/strong&gt; Not a plugin wrapping an API. AI is part of the architecture. That opens up real possibilities for content generation and automation.&lt;/p&gt;

&lt;p&gt;I like where this is going.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes me wait
&lt;/h2&gt;

&lt;p&gt;But when I think about using #EmDash for a client project right now, there are a few things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ecosystem is basically empty.&lt;/strong&gt; WordPress has thousands of plugins. Sanity and Contentful have mature integrations. EmDash is starting from zero. When you have a client deadline, that matters a lot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's developer-first.&lt;/strong&gt; TypeScript, Astro, edge functions. And look, I love Astro. I still smile every time I see a &lt;a href="https://featured.undp.org/digital-goals/" rel="noopener noreferrer"&gt;project we built for UNDP&lt;/a&gt; in the Astro showcase. My team can work with all of that. But we also need to hand things over to marketing teams. They need to update pages and publish content without calling us every time. WordPress and Contentful have been refining that for years. EmDash hasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's tied to Cloudflare.&lt;/strong&gt; WordPress runs on any hosting. Sanity and Contentful are SaaS. EmDash depends on the Cloudflare ecosystem. That's a dependency I'd think twice about for client work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It takes longer to ship.&lt;/strong&gt; We can go from kickoff to a working WordPress site with custom content types in days. With a new platform, there's always a learning curve. When a client has a launch date, that time gap is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not about good or bad
&lt;/h2&gt;

&lt;p&gt;I'm not saying EmDash is bad. I think it's one of the most interesting things I've seen in the CMS space in a while. The architecture makes sense. The vision is clear.&lt;/p&gt;

&lt;p&gt;But picking a CMS for client work is not just about architecture. It's about ecosystem, team readiness, how easy it is for the client, and whether this thing will still work in three years. Those things take time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'd try it
&lt;/h2&gt;

&lt;p&gt;Internal projects. A proof of concept. Something where we control the timeline and the scope. That's how we've always adopted new tools. We learn them in low-risk contexts first.&lt;/p&gt;

&lt;p&gt;If you care about developer experience, EmDash is worth exploring now. Not for your next client project. As a sandbox. Play with the serverless model, the plugin patterns, the AI workflows. The best time to learn a tool is before you need it on a deadline.&lt;/p&gt;

&lt;p&gt;If the ecosystem grows, I can see EmDash sitting next to WordPress, Sanity, and Contentful in our toolkit. The ideas are solid. It just needs time.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing
&lt;/h2&gt;

&lt;p&gt;If you haven't tried Sanity, Contentful, Storyblok, or Drupal, go try them. Seriously. Yeah, 80% of content modeling feels the same across platforms. But that other 20% is where you learn things you wouldn't learn otherwise. Each one gives you a different perspective on how content should work.&lt;/p&gt;

&lt;p&gt;AEM too, if you can afford the license. I'd say try it on something small, a personal project, just to see what's inside.&lt;/p&gt;

&lt;p&gt;And here's something that would have sounded crazy two years ago: building your own CMS is actually possible now. With AI and a small team of solid developers who know how to use it, you can build exactly what you need. Your integrations. Your workflows. Ship features when you want, not when a vendor decides.&lt;/p&gt;

&lt;p&gt;We're doing it. And once you have that freedom, it's hard to go back.&lt;/p&gt;

&lt;p&gt;Maybe EmDash gets there. Maybe WordPress keeps evolving. Or maybe you just build your own thing. The point is we have more options than ever. And that's a good place to be.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>WebGPU for Frontend Devs: A Practical First Project</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Wed, 18 Mar 2026 21:16:48 +0000</pubDate>
      <link>https://forem.com/andresclua/webgpu-for-frontend-devs-a-practical-first-project-5768</link>
      <guid>https://forem.com/andresclua/webgpu-for-frontend-devs-a-practical-first-project-5768</guid>
      <description>&lt;p&gt;Most WebGPU tutorials go deep into theory or jump straight into 3D engines. If you are a frontend developer like me, that feels like learning to drive by reading the car manual.&lt;/p&gt;

&lt;p&gt;This article is the opposite. We will build something visual, step by step, with zero GPU knowledge. By the end, you will have a &lt;strong&gt;real-time animated gradient running on your GPU&lt;/strong&gt; — and you will understand every line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is WebGPU (in 30 seconds)
&lt;/h2&gt;

&lt;p&gt;WebGPU lets your JavaScript talk directly to the GPU. Think about it like this:&lt;/p&gt;

&lt;p&gt;Imagine you need to paint a wall. Your &lt;strong&gt;CPU&lt;/strong&gt; is one very talented painter — fast, smart, and precise. But it paints one brushstroke at a time.&lt;/p&gt;

&lt;p&gt;Your &lt;strong&gt;GPU&lt;/strong&gt; is a team of 5,000 painters. Each one can only do simple strokes, but they all paint &lt;strong&gt;at the same time&lt;/strong&gt;. Give them a wall with 2 million pixels? They split the work and finish in one pass.&lt;/p&gt;

&lt;p&gt;WebGPU is the phone call that lets your browser hire that team of painters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser support (March 2026)
&lt;/h2&gt;

&lt;p&gt;Before we start — can your browser do this?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome/Edge&lt;/td&gt;
&lt;td&gt;✅ Supported (since v113)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;✅ Supported (since v141)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;✅ Supported (since 18.2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Good news: all modern browsers support it now. For production, always check first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebGPU is not supported in your browser&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;A full-screen animated gradient where the GPU decides the color of every pixel, 60 times per second. It looks like a lava lamp made of math.&lt;/p&gt;

&lt;p&gt;No libraries. No frameworks. Just HTML, JavaScript, and a few lines of GPU code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: The HTML (simple but necessary)
&lt;/h2&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&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;title&amp;gt;&lt;/span&gt;My First WebGPU Project&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&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="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;canvas&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="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;100vw&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="m"&gt;100vh&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&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&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;p&gt;Nothing new here — just a full-screen canvas. This is our "empty wall" that the GPU painters will fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Connect to the GPU
&lt;/h2&gt;

&lt;p&gt;Before the painters can work, you need to call the agency, confirm they are available, and give them the address. That is what this step does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.js&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;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Check support&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebGPU not supported&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="c1"&gt;// 2. Get an adapter (your physical GPU)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAdapter&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;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No GPU adapter found&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="c1"&gt;// 3. Get a device (your logical connection to it)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestDevice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Connect the canvas&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webgpu&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;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPreferredCanvasFormat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&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;What just happened — using our painting analogy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;adapter&lt;/code&gt; = finding the painting agency (your physical GPU)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;device&lt;/code&gt; = signing the contract with them (your app's private connection)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context&lt;/code&gt; = giving them the key to the room where the wall is (connecting canvas to GPU)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup code is the same for every WebGPU project. Write it once, copy it forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Write a shader (the fun part)
&lt;/h2&gt;

&lt;p&gt;Now we need to give instructions to our painters. In GPU world, these instructions are called &lt;strong&gt;shaders&lt;/strong&gt; — small programs written in WGSL (WebGPU Shading Language). The syntax looks like Rust, but simpler.&lt;/p&gt;

&lt;p&gt;Think of a shader like a recipe card you give to each painter. Every painter gets the &lt;strong&gt;same recipe&lt;/strong&gt;, but they each know their own position on the wall. So painter #4500 reads: "I am at position (200, 300). Based on this recipe, my color should be blue." All 5,000 painters follow the recipe at the same time, and the wall is painted in one pass.&lt;/p&gt;

&lt;p&gt;For our gradient, we need a &lt;strong&gt;fragment shader&lt;/strong&gt; — a recipe that takes a pixel position and returns a color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shaderCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* wgsl */&lt;/span&gt;&lt;span class="s2"&gt;`

  // A uniform is data we send from JavaScript to the GPU
  @group(0) @binding(0) var&amp;lt;uniform&amp;gt; time: f32;
  @group(0) @binding(1) var&amp;lt;uniform&amp;gt; resolution: vec2f;

  // Vertex shader: positions a full-screen triangle
  @vertex
  fn vertexMain(@builtin(vertex_index) i: u32) -&amp;gt; @builtin(position) vec4f {
    // A clever trick: 3 vertices that cover the entire screen
    let pos = array&amp;lt;vec2f, 3&amp;gt;(
      vec2f(-1.0, -1.0),
      vec2f( 3.0, -1.0),
      vec2f(-1.0,  3.0)
    );
    return vec4f(pos[i], 0.0, 1.0);
  }

  // Fragment shader: runs once PER PIXEL
  @fragment
  fn fragmentMain(@builtin(position) pos: vec4f) -&amp;gt; @location(0) vec4f {
    // Normalize coordinates to 0.0 - 1.0
    let uv = pos.xy / resolution;

    // Animated color channels using sine waves
    let r = sin(uv.x * 3.0 + time) * 0.5 + 0.5;
    let g = sin(uv.y * 3.0 + time * 0.7) * 0.5 + 0.5;
    let b = sin((uv.x + uv.y) * 2.0 + time * 1.3) * 0.5 + 0.5;

    return vec4f(r, g, b, 1.0);
  }
`&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;Let me explain each part:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@vertex fn vertexMain&lt;/code&gt; — This draws a giant triangle that covers the entire screen. Think of it as stretching a canvas over the wall. It is a standard trick you will reuse in every full-screen effect.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@fragment fn fragmentMain&lt;/code&gt; — This is the recipe card. It runs &lt;strong&gt;once for every pixel&lt;/strong&gt;. Each pixel receives its position and returns a color.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sin()&lt;/code&gt; with &lt;code&gt;time&lt;/code&gt; — The &lt;code&gt;sin&lt;/code&gt; function creates smooth waves. Adding &lt;code&gt;time&lt;/code&gt; makes them move. Different speeds per color channel (red, green, blue) create that organic, shifting look.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv&lt;/code&gt; — The pixel position, but normalized to a range of 0 to 1. Like a percentage: top-left is (0, 0), bottom-right is (1, 1). This way, the shader works on any screen size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have used CSS gradients before, the idea is similar — but this one updates 60 times per second and you control everything with math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Set up the pipeline
&lt;/h2&gt;

&lt;p&gt;We have the painters (GPU), the wall (canvas), and the recipe (shader). Now we need to put everything together — like a manager organizing the work before it starts. That is what the &lt;strong&gt;pipeline&lt;/strong&gt; does.&lt;/p&gt;

&lt;p&gt;It tells the GPU: "Here is the recipe, and here is the data you will need."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shaderCode&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;shaderModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createShaderModule&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="nx"&gt;shaderCode&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Create buffers for our uniform data (time + resolution)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBuffer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// f32 = 4 bytes&lt;/span&gt;
    &lt;span class="na"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GPUBufferUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNIFORM&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;GPUBufferUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COPY_DST&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;resolutionBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBuffer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// vec2f = 2 × 4 bytes&lt;/span&gt;
    &lt;span class="na"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GPUBufferUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UNIFORM&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;GPUBufferUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COPY_DST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Bind group layout: tells the GPU what data to expect&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bindGroupLayout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBindGroupLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;entries&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;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GPUShaderStage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FRAGMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uniform&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="na"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GPUShaderStage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FRAGMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uniform&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;span class="c1"&gt;// Bind group: connects our buffers to the shader's @binding slots&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBindGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bindGroupLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;entries&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;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&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;binding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolutionBuffer&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;span class="c1"&gt;// The render pipeline&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRenderPipeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPipelineLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;bindGroupLayouts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;bindGroupLayout&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shaderModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vertexMain&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;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shaderModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entryPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fragmentMain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;format&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolutionBuffer&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;Yes, this is a lot of code.&lt;/strong&gt; That is the most common complaint about WebGPU. But if you look closely, it is just connecting things — like plugging cables into the right ports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compile the recipe → &lt;code&gt;shaderModule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create two small envelopes (&lt;code&gt;buffers&lt;/code&gt;) to pass data to the painters: current time and screen size&lt;/li&gt;
&lt;li&gt;Label the envelopes so the painters know which is which (&lt;code&gt;bindGroup&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Put it all together in one package (&lt;code&gt;pipeline&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The good news? This wiring is almost the same for every project. The creative part is always the shader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: The render loop
&lt;/h2&gt;

&lt;p&gt;Time to paint. But we do not paint once — we paint 60 times per second. Each frame, we update the time (so the colors move), tell the GPU to run the recipe, and repeat. It is like a flipbook: each page is slightly different, and together they create animation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startRenderLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolutionBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send resolution to GPU (only needs to happen once, unless resized)&lt;/span&gt;
  &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;resolutionBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;Float32Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update time (convert ms to seconds)&lt;/span&gt;
    &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;Float32Array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create the render command&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCommandEncoder&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;pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginRenderPass&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;colorAttachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentTexture&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;createView&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;loadOp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;storeOp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;store&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="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setBindGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3 vertices = our full-screen triangle&lt;/span&gt;
    &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Submit to GPU&lt;/span&gt;
    &lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&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;If you have used &lt;code&gt;requestAnimationFrame&lt;/code&gt; before for Canvas 2D animations, this is the same idea. The only difference: instead of drawing with &lt;code&gt;ctx.fillRect()&lt;/code&gt;, you write a command list and send it to the GPU. Think of &lt;code&gt;commandEncoder&lt;/code&gt; as writing a to-do list, and &lt;code&gt;device.queue.submit()&lt;/code&gt; as handing that list to the painters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Wire it all together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&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;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;init&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;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolutionBuffer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;createPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shaderCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;startRenderLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bindGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolutionBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;index.html&lt;/code&gt; in your browser. You should see a smooth, colorful gradient filling your screen — every single pixel computed by the GPU in real time. All 5,000 painters working together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it your own
&lt;/h2&gt;

&lt;p&gt;The shader is your playground. You can change the recipe and the painters will follow. Try replacing the color math inside &lt;code&gt;fragmentMain&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circular waves:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let dist = distance(uv, vec2f(0.5, 0.5));
let wave = sin(dist * 20.0 - time * 3.0) * 0.5 + 0.5;
return vec4f(wave, wave * 0.5, 1.0 - wave, 1.0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Checkerboard morph:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let scale = 10.0;
let checker = step(0.5, fract(uv.x * scale + sin(time))) *
              step(0.5, fract(uv.y * scale + cos(time)));
return vec4f(checker, 1.0 - checker, 0.5, 1.0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Noise-like pattern:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let n = fract(sin(dot(uv + time * 0.1, vec2f(12.9898, 78.233))) * 43758.5453);
return vec4f(n * 0.8, n * 0.4, n, 1.0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change one number, save, refresh. You will see the result immediately. This is the best way to learn — experiment and break things.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you just learned
&lt;/h2&gt;

&lt;p&gt;Here is a cheat sheet. Every WebGPU concept mapped to something you already know as a frontend developer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WebGPU concept&lt;/th&gt;
&lt;th&gt;Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;adapter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Finding which painting agency is available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signing a contract with them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shaderModule&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The recipe card you give to every painter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;buffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An envelope with data (time, screen size) for the painters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bindGroup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Labels on the envelopes so painters know which is which — like props in a component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pipeline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The full work plan: recipe + materials + instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commandEncoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A to-do list you write before handing it to the painters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device.queue.submit()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Handing the to-do list and saying "go"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same as always — the flipbook loop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When should you use WebGPU?
&lt;/h2&gt;

&lt;p&gt;After building this, you might want to use the GPU for everything. Do not do that.&lt;/p&gt;

&lt;p&gt;The GPU is like a factory: amazing for mass production, terrible for custom one-off work. Use the right tool for the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use WebGPU when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing thousands of pixels, particles, or data points at once&lt;/li&gt;
&lt;li&gt;Running ML models in the browser&lt;/li&gt;
&lt;li&gt;Building visualizations with 100k+ data points&lt;/li&gt;
&lt;li&gt;Real-time image or video filters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stick with JavaScript when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM manipulation&lt;/li&gt;
&lt;li&gt;Business logic and API calls&lt;/li&gt;
&lt;li&gt;Anything that is not the same operation repeated thousands of times&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;You now have the foundation. The setup code stays the same — from here, the fun is changing the recipe (shader). Some ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add mouse interaction&lt;/strong&gt; — send mouse coordinates as another uniform, so the gradient reacts to your cursor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute shaders&lt;/strong&gt; — use the GPU to process data, not just pixels (great for simulations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle systems&lt;/strong&gt; — combine vertex and fragment shaders to move thousands of objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://webgpu.github.io/webgpu-samples/" rel="noopener noreferrer"&gt;WebGPU Samples&lt;/a&gt;&lt;/strong&gt; — official examples with more advanced patterns&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Questions? Leave a comment — I will answer every one. If you built something cool by changing the shader, I would love to see it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webgpu</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>frontend</category>
    </item>
    <item>
      <title>AI Writes the Code. AI Reviews the Code. So Why Do They Still Need You?</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Tue, 10 Mar 2026 23:56:53 +0000</pubDate>
      <link>https://forem.com/andresclua/ai-writes-the-code-ai-reviews-the-code-so-why-do-they-still-need-you-3gg6</link>
      <guid>https://forem.com/andresclua/ai-writes-the-code-ai-reviews-the-code-so-why-do-they-still-need-you-3gg6</guid>
      <description>&lt;p&gt;An AI writes your code. A different AI reviews it, flags the bugs, suggests the fix, and posts a clean summary to your PR. Cost: about $25. Time: minutes.&lt;/p&gt;

&lt;p&gt;So what exactly are &lt;em&gt;you&lt;/em&gt; doing here?&lt;/p&gt;

&lt;p&gt;That's not a rhetorical question. It's the question every engineering team is quietly asking right now. And the companies that get it wrong are already paying for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wall and the Tap
&lt;/h2&gt;

&lt;p&gt;Amazon recently held a &lt;a href="https://x.com/lukOlejnik/status/2031257644724342957" rel="noopener noreferrer"&gt;mandatory meeting&lt;/a&gt; to address a wave of production incidents tied to AI-assisted code. Their internal briefing described outages with "high blast radius" caused by generative AI changes, and admitted that best practices for this workflow simply don't exist yet.&lt;/p&gt;

&lt;p&gt;One incident stands out: an AI coding tool was asked to make a routine change to an AWS environment. Instead, it decided to delete and recreate the entire thing. Thirteen hours of recovery. Amazon called it an "extremely limited event." The affected tool served customers in mainland China.&lt;/p&gt;

&lt;p&gt;They asked the AI to fix a leaky tap, and it knocked down the wall.&lt;/p&gt;

&lt;p&gt;Amazon's fix wasn't more automation. It was more human oversight. Junior and mid-level engineers can no longer ship AI-assisted code without a senior signing off. One of the most technically advanced companies in the world looked at the problem and concluded: we need &lt;em&gt;more&lt;/em&gt; experienced humans in the loop, not fewer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Agree
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.coderabbit.ai/blog/state-of-ai-vs-human-code-generation-report" rel="noopener noreferrer"&gt;CodeRabbit analysis&lt;/a&gt; of over 470 pull requests found that AI-generated code produces roughly 1.7x more issues per PR than human-written code. The biggest gaps weren't cosmetic. Logic errors, missing null checks, broken control flow, security misconfigurations. The kind of things that pass CI but blow up in production at 3 AM.&lt;/p&gt;

&lt;p&gt;AI-generated code tends to look clean. It follows conventions, names things reasonably, formats well. But underneath that surface, it skips the guardrails that experienced engineers build instinctively. The code &lt;em&gt;looks&lt;/em&gt; right. That's what makes it dangerous.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Didn't Ship Garbage Before AI Either
&lt;/h2&gt;

&lt;p&gt;Before AI we had code reviews. QA cycles. Staging environments. Integration tests. Senior engineers who would block your PR because they'd seen that exact failure before in production. We built entire cultures around shipping reliable software because humans were in the loop.&lt;/p&gt;

&lt;p&gt;AI doesn't eliminate that need. It amplifies it. When you produce code 10x faster, you produce bugs 10x faster too. The volume goes up. The surface area for failure goes up. Remove the human filter to "move fast" and you get what Amazon got: a trend of high-impact outages with no safeguards.&lt;/p&gt;

&lt;p&gt;The smart move was never to take humans out of the loop. It's to take the tedious work out of their day so they can focus on what prevents disasters: judgment, context, and ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  Great Demo. Terrible Production App.
&lt;/h2&gt;

&lt;p&gt;Tools like Lovable, Bolt, and others let you go from idea to working app in minutes. The UIs look polished, the features work, the demo is convincing. As a proof of concept, they're unbeatable.&lt;/p&gt;

&lt;p&gt;But if you're reading this, there's a decent chance you've already hit the wall. The wall where the app works beautifully in a demo but falls apart the moment real users touch it. No rate limiting. No input validation. No security headers. Endpoints wide open. The kind of stuff that doesn't show up in a screenshot but absolutely shows up in a penetration test.&lt;/p&gt;

&lt;p&gt;I wrote about this after &lt;a href="https://dev.to/andresclua/superpowers-with-no-aim-what-i-found-after-stress-testing-an-ai-built-app-324p"&gt;stress-testing an AI-built app&lt;/a&gt;. XSS payloads accepted with a smile. SQL injection that went through without a flinch. An SSRF vulnerability where the server tried to fetch internal cloud metadata from a URL someone typed into a text field. The app &lt;em&gt;worked&lt;/em&gt;. It just wasn't &lt;em&gt;safe&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;want&lt;/em&gt; people who use these tools on my team. Someone who spins up a working prototype in an afternoon to validate an idea before we spend two sprints building it? That person is incredibly valuable.&lt;/p&gt;

&lt;p&gt;But here's the thing. Designers, marketers, and even developers who have never dealt with infrastructure, security, or scale should not look at these tools and think "I can ship this in production by tomorrow." Not because they're not smart. Because they haven't lived through the failures that teach you what production actually demands. Experience isn't gatekeeping. It's knowing what breaks when 10,000 real users hit your app at once instead of just you clicking around in a browser.&lt;/p&gt;

&lt;p&gt;The AI builds the proof of concept. The engineer turns it into a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Value Was Never the Syntax
&lt;/h2&gt;

&lt;p&gt;AI doesn't know your system. It doesn't know that your team decided last sprint to deprecate that service. It doesn't know the CTO's opinion on over-abstraction. It doesn't understand that the "elegant" solution will break the deploy pipeline because of a legacy dependency nobody wants to touch.&lt;/p&gt;

&lt;p&gt;Code is decisions. Every function, every architectural choice, every trade-off reflects something the model doesn't have access to: the context of your team, your users, your business. The model gives you signal. You give it meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Responsibility Didn't Change
&lt;/h2&gt;

&lt;p&gt;AI writing code doesn't eliminate engineers. It eliminates the mechanical fraction of the job. What remains is judgment, context, ownership, and taste. The things no model update will automate away because they require understanding &lt;em&gt;why&lt;/em&gt; you're building something, not just &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We're still human. The tools changed. The responsibility didn't.&lt;/p&gt;

&lt;p&gt;The question isn't whether AI can write and review your code. It can. The question is whether you can do everything around the code that the AI can't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the answer is yes, you've never been more valuable.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>startup</category>
      <category>nocode</category>
    </item>
    <item>
      <title>Superpowers With No Aim: What I Found After Stress-Testing an AI-Built App</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Wed, 18 Feb 2026 14:28:24 +0000</pubDate>
      <link>https://forem.com/andresclua/superpowers-with-no-aim-what-i-found-after-stress-testing-an-ai-built-app-324p</link>
      <guid>https://forem.com/andresclua/superpowers-with-no-aim-what-i-found-after-stress-testing-an-ai-built-app-324p</guid>
      <description>&lt;p&gt;Look, I love AI. I use it every day. It makes me faster, it helps me think, it writes boilerplate so I don't have to. But we need to have a serious conversation about what happens when you let AI build your app without knowing what you're actually asking for.&lt;/p&gt;

&lt;p&gt;A friend of mine shipped a side project recently. Nice UI, clean design, worked well. He built most of it with AI assistance — prompting his way through features, shipping fast, iterating quick. The vibe coding dream, right?&lt;/p&gt;

&lt;p&gt;I decided to throw some load at it. Just to see. Artillery, some custom scripts, a couple hundred random payloads. Nothing crazy, nothing illegal — just the kind of stuff any curious person with a terminal could do on a Tuesday afternoon.&lt;/p&gt;

&lt;p&gt;What I found was genuinely scary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;First test: 690 requests at 2 requests per second. Every single one went through. Zero rate limiting. The server didn't even flinch, but not in a good way — it just accepted everything blindly.&lt;/p&gt;

&lt;p&gt;Second test: 3,050 requests with spikes up to 30 per second. Out of those, only 33 got a 429 (rate limited). That's 1%. The other 99% went through like butter.&lt;/p&gt;

&lt;p&gt;Then I ran the fuzzer. 780 payloads — XSS, SQL injection, command injection, SSRF, path traversal, oversized strings, weird edge cases. The results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;614 malicious payloads accepted&lt;/strong&gt; with a happy 202 status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;86 payloads crashed the backend&lt;/strong&gt; with 500 errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 requests timed out&lt;/strong&gt; because the server stopped responding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let that sink in. I sent &lt;code&gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/code&gt; as an input value and the server said "yeah cool, let me process that for you."&lt;/p&gt;

&lt;h2&gt;
  
  
  The really bad stuff
&lt;/h2&gt;

&lt;p&gt;The scariest finding was SSRF. I put a cloud metadata URL as a user input — the kind of internal endpoint where AWS and GCP store instance credentials. The server actually tried to fetch it. If that request resolves, someone can pull service account keys, environment variables, the whole thing. No exploit kit needed. Just a URL in a text field.&lt;/p&gt;

&lt;p&gt;There were also API parameters sent directly from the client with zero server-side validation. Path traversal payloads went through without any checks. That means potentially reading or writing data you were never supposed to access.&lt;/p&gt;

&lt;p&gt;No authentication on any endpoint. No input sanitization. No content security policy. The server was even broadcasting what framework it was running through response headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a skill issue, it's a prompting issue
&lt;/h2&gt;

&lt;p&gt;Here's the thing — I'm not writing this to roast anyone. The app works. The UI is clean. The features do what they're supposed to do. The problem isn't the developer. The problem is how we're using AI to build stuff.&lt;/p&gt;

&lt;p&gt;When you tell an AI "build me an app that does X", you get exactly that. A thing that does X. It works. It demos well. It looks great in a tweet.&lt;/p&gt;

&lt;p&gt;But the AI didn't add rate limiting because you didn't ask for it. It didn't validate inputs because your prompt was about features, not security. It didn't think about SSRF because that wasn't in the conversation. It gave you exactly what you asked for — nothing more, nothing less.&lt;/p&gt;

&lt;p&gt;We've become &lt;strong&gt;monkeys with shotguns&lt;/strong&gt;. We have this incredibly powerful tool that can generate entire applications in minutes, and we're pointing it at production without understanding what it built or what it missed. We're not thinking. We're just firing.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Make it work" vs "Make it right"
&lt;/h2&gt;

&lt;p&gt;There's a massive difference between these two prompts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt A:&lt;/strong&gt; &lt;em&gt;"Build me an API that receives user input, processes it, and returns results"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt B:&lt;/strong&gt; &lt;em&gt;"Build me an API that receives user input. Validate the input format using a strict whitelist. Add rate limiting at 10 requests per minute per IP. Add security headers. Sanitize all user input before storing. Block private IP ranges and internal hostnames to prevent SSRF. Use parameterized queries. Add proper error handling that doesn't leak stack traces or internal details. Return 405 for unsupported HTTP methods."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Prompt A gives you a working app. Prompt B gives you a safe working app. The AI is perfectly capable of doing both — but it only does what you tell it.&lt;/p&gt;

&lt;p&gt;And that's the core problem. AI doesn't have opinions about your architecture. It doesn't push back and say "hey, are you sure you want to accept arbitrary input from anonymous users and process it server-side without any checks?" It just does it. It's the most agreeable coworker you've ever had, and that's exactly what makes it dangerous.&lt;/p&gt;

&lt;h2&gt;
  
  
  The debugging trap
&lt;/h2&gt;

&lt;p&gt;Here's something that doesn't get talked about enough: AI debugging without context is often worse than the original bug.&lt;/p&gt;

&lt;p&gt;You hit an error, you paste it into the chat, the AI fixes the symptom. The &lt;code&gt;500&lt;/code&gt; goes away. But it didn't fix the root cause — it just wrapped it in a try-catch that silently swallows the error, or it loosened a validation that was actually protecting you, or it added a workaround that introduces a new vulnerability.&lt;/p&gt;

&lt;p&gt;I've seen this pattern over and over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dev builds feature with AI&lt;/li&gt;
&lt;li&gt;Something breaks&lt;/li&gt;
&lt;li&gt;Dev pastes error into AI&lt;/li&gt;
&lt;li&gt;AI patches the symptom&lt;/li&gt;
&lt;li&gt;Underlying issue is now hidden AND the patch introduced something new&lt;/li&gt;
&lt;li&gt;Repeat until the codebase is a house of cards&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the AI doesn't have the full picture — your architecture, your security requirements, your threat model — its fixes are just educated guesses. Sometimes they're great. Sometimes they remove the one validation that was actually keeping your database safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually think we should do
&lt;/h2&gt;

&lt;p&gt;I'm not saying stop using AI. That would be stupid. AI is genuinely incredible and I'd be a hypocrite — I used AI tools to run this entire security assessment.&lt;/p&gt;

&lt;p&gt;What I'm saying is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Know what you're building before you prompt.&lt;/strong&gt; Have an architecture in your head. Know what patterns you want. Know what your security boundaries are. Then tell the AI to build within those constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Iterate with intention, not with vibes.&lt;/strong&gt; "Make it work" is step one, not the finish line. After the feature works, go back and ask specifically about security, validation, error handling, edge cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Treat AI output like a junior dev's PR.&lt;/strong&gt; You wouldn't merge a junior's code without reviewing it. Don't deploy AI-generated code without understanding what it does and what it doesn't do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Security is not a feature, it's a requirement.&lt;/strong&gt; Bake it into your prompts from the start. "Add rate limiting" should be as natural as "add a button."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. When debugging, give context.&lt;/strong&gt; Don't just paste the error. Explain what the code is supposed to do, what your constraints are, what you've already tried. The better the context, the better the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;The app I tested took probably a few hours to build with AI. It would take maybe 30 minutes more to make it secure — if security was part of the conversation from the beginning.&lt;/p&gt;

&lt;p&gt;That's the gap. Not days. Not weeks. 30 minutes of intentional prompting. The difference between "build me this feature" and "build me this feature, and here's how I want it protected."&lt;/p&gt;

&lt;p&gt;AI gave us superpowers. But superpowers without direction are just destruction with extra steps.&lt;/p&gt;

&lt;p&gt;Build with intention. Prompt with context. Review what you ship.&lt;/p&gt;

&lt;p&gt;Or keep being monkeys with shotguns. Your call.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you want to run similar tests on your own projects (with permission, obviously), the tooling is straightforward: Artillery for load testing, custom JS payloads for fuzzing, and curl for manual recon. Took about 20 minutes to set up and the findings speak for themselves.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Full transparency:&lt;/strong&gt; This article was written with AI. I don't usually write like this — English isn't even my first language and my usual tone is way more chaotic. But that's kind of the whole point. I ran the security assessment step by step using AI tools, organized my findings as I went, and at the end I asked for a clean markdown copy for dev.to. The irony isn't lost on me — an article about being intentional with AI, made with AI. But that's exactly the difference I'm talking about. I didn't say "write me a blog post about security." I guided every section, every point, every finding, based on real tests I actually ran. The AI shaped the words. The thinking was mine.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>security</category>
    </item>
    <item>
      <title>Speculation Rules API: Make Your Pages Load Before the User Clicks</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Tue, 10 Feb 2026 12:31:04 +0000</pubDate>
      <link>https://forem.com/andresclua/speculation-rules-api-make-your-pages-load-before-the-user-clicks-3498</link>
      <guid>https://forem.com/andresclua/speculation-rules-api-make-your-pages-load-before-the-user-clicks-3498</guid>
      <description>&lt;p&gt;Imagine your website could predict where the user is going and have that page ready before they click. That's exactly what the &lt;strong&gt;Speculation Rules API&lt;/strong&gt; does.&lt;/p&gt;

&lt;p&gt;It's not magic. It's not a framework. It's a native browser API. And it's stupidly easy to implement.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When a user clicks a link, the browser has to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request the HTML from the server&lt;/li&gt;
&lt;li&gt;Download CSS, JS, images&lt;/li&gt;
&lt;li&gt;Parse everything&lt;/li&gt;
&lt;li&gt;Render the page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That takes time. Sometimes a little, sometimes a lot. But it always feels slower than it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Cheat (the good kind)
&lt;/h2&gt;

&lt;p&gt;The Speculation Rules API tells the browser: "Hey, the user will probably go to this page. Get it ready now."&lt;/p&gt;

&lt;p&gt;And the browser does it. In the background. Without the user knowing.&lt;/p&gt;

&lt;p&gt;When they finally click, the page shows up &lt;strong&gt;instantly&lt;/strong&gt;. Literally. 0ms of perceived wait time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prefetch vs Prerender
&lt;/h2&gt;

&lt;p&gt;There are two levels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefetch&lt;/strong&gt;: Downloads only the HTML of the page. Like downloading the blueprints of a house but not building it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerender&lt;/strong&gt;: Downloads EVERYTHING and renders the full page in the background. Like building the entire house and having it ready when you arrive.&lt;/p&gt;

&lt;p&gt;Prerender is more aggressive and uses more resources, but the experience is instant.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;

&lt;p&gt;It's a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag with &lt;code&gt;type="speculationrules"&lt;/code&gt; and JSON inside. No NPM, no imports, no config files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Specific URLs
&lt;/h3&gt;

&lt;p&gt;If you know exactly which pages you want to pre-load:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"speculationrules"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;prerender&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urls&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&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;/work&lt;/span&gt;&lt;span class="dl"&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;/contact&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;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the browser: "Prerender &lt;code&gt;/about&lt;/code&gt;, &lt;code&gt;/work&lt;/code&gt; and &lt;code&gt;/contact&lt;/code&gt; immediately."&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Automatic Rules (document rules)
&lt;/h3&gt;

&lt;p&gt;This is where it gets interesting. Instead of listing URLs by hand, you tell the browser to decide based on the links it finds on the page:&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"speculationrules"&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;prefetch&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&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;document&lt;/span&gt;&lt;span class="dl"&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;where&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;and&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href_matches&lt;/span&gt;&lt;span class="dl"&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;/*&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href_matches&lt;/span&gt;&lt;span class="dl"&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;*.pdf&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;selector_matches&lt;/span&gt;&lt;span class="dl"&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;.no-prefetch&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;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eagerness&lt;/span&gt;&lt;span class="dl"&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;conservative&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translation: "Prefetch all internal links on the page, except PDFs and links with the &lt;code&gt;.no-prefetch&lt;/code&gt; class, but only when the user starts clicking."&lt;/p&gt;




&lt;h2&gt;
  
  
  Eagerness: How Anxious Do You Want It to Be
&lt;/h2&gt;

&lt;p&gt;Controls &lt;strong&gt;when&lt;/strong&gt; the browser starts pre-loading:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;immediate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does it now. Doesn't ask.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same as immediate (for now)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;moderate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits for 200ms of hover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conservative&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits for the click to start (mousedown/touchstart)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;conservative&lt;/code&gt;&lt;/strong&gt; is the safest to start with. Only pre-loads when the user is already clicking, so you don't waste resources. My recommendation if you're unsure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;moderate&lt;/code&gt;&lt;/strong&gt; is the sweet spot. 200ms of hover is enough to have the page ready by the time the click lands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;immediate&lt;/code&gt;&lt;/strong&gt; is for when you're certain the user will go there. Use it with specific URLs, not document rules (or you'll prerender everything).&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful Filters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  By URL pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"href_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/work/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only links starting with &lt;code&gt;/work/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  By CSS selector
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".prerender-this"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only links with that class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exclude pages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"not"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"href_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/logout"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Always exclude routes with side effects. If you prerender &lt;code&gt;/logout&lt;/code&gt;, the user gets logged out without clicking. Not kidding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combine conditions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"href_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"not"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"href_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"not"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector_matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a[rel~='nofollow']"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Add It Dynamically with JS
&lt;/h2&gt;

&lt;p&gt;If you need to add rules after the page loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;prerender&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;urls&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;/next-page&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speculationrules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful if you want to prerender the "next page" based on some user data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check Browser Support
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;HTMLScriptElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;supports&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;HTMLScriptElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;speculationrules&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Speculation Rules supported&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Detect If a Page Was Prerendered
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prerendering&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This page is being prerendered&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prerenderingchange&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;=&amp;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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User just navigated to this prerendered page&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful if you want to defer analytics or other actions until the user actually sees the page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Browser Limits
&lt;/h3&gt;

&lt;p&gt;Chrome caps how many pages you can pre-load at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;immediate&lt;/code&gt;/&lt;code&gt;eager&lt;/code&gt;: up to 50 prefetches, 10 prerenders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;moderate&lt;/code&gt;/&lt;code&gt;conservative&lt;/code&gt;: up to 2 of each&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't go crazy prerendering 100 pages. The browser will just ignore them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Consumption
&lt;/h3&gt;

&lt;p&gt;Prerender uses bandwidth, CPU and battery. Chrome automatically disables it if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The device is in power saver mode&lt;/li&gt;
&lt;li&gt;Battery is low&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Content Can Go Stale
&lt;/h3&gt;

&lt;p&gt;If you prerender a page and the user takes 5 minutes to click, the content might have changed. For pages with real-time data, use &lt;code&gt;prefetch&lt;/code&gt; instead of &lt;code&gt;prerender&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extensions
&lt;/h3&gt;

&lt;p&gt;uBlock Origin disables preloading by default. Keep that in mind when measuring impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deferred APIs
&lt;/h3&gt;

&lt;p&gt;Some APIs (Geolocation, Notifications, Storage) are delayed until the page is actually activated. They won't fire during prerender.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debug in DevTools
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open Chrome DevTools&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Application &amp;gt; Background Services &amp;gt; Speculative Loads&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Reload the page&lt;/li&gt;
&lt;li&gt;You'll see which pages are being prerendered/prefetched and any errors&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Browser Support
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chrome: Yes (since 2024)&lt;/li&gt;
&lt;li&gt;Edge: Yes&lt;/li&gt;
&lt;li&gt;Firefox: No&lt;/li&gt;
&lt;li&gt;Safari: No&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Firefox and Safari, the &lt;code&gt;&amp;lt;script type="speculationrules"&amp;gt;&lt;/code&gt; tag is simply ignored. It doesn't break anything. Pure progressive enhancement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Now Here's the Plot Twist
&lt;/h2&gt;

&lt;p&gt;Everything above is cool. But if you hardcode your speculation rules and forget about them, you're leaving performance on the table.&lt;/p&gt;

&lt;p&gt;The real power of this API is that &lt;strong&gt;it's just JSON&lt;/strong&gt;. And JSON can be generated. Dynamically. From data.&lt;/p&gt;

&lt;p&gt;What data? &lt;strong&gt;Your analytics.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about it. Your analytics already know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which pages users visit most&lt;/li&gt;
&lt;li&gt;What the most common navigation paths are&lt;/li&gt;
&lt;li&gt;Which links get the most clicks on each page&lt;/li&gt;
&lt;li&gt;How those patterns change over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of guessing which pages to prerender, you can &lt;strong&gt;know&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Loop
&lt;/h3&gt;

&lt;p&gt;Here's the workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1&lt;/strong&gt;: You deploy speculation rules based on gut feeling. Prerender &lt;code&gt;/about&lt;/code&gt; and &lt;code&gt;/work&lt;/code&gt; because they seem important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2&lt;/strong&gt;: You check analytics. Turns out 73% of homepage visitors go to &lt;code&gt;/work&lt;/code&gt; first, then &lt;code&gt;/work/project-x&lt;/code&gt;. Nobody clicks &lt;code&gt;/about&lt;/code&gt; from the homepage. Now you know what to actually prerender.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3&lt;/strong&gt;: Traffic patterns shifted. A blog post went viral and now &lt;code&gt;/play&lt;/code&gt; is getting 5x the traffic. Your speculation rules should reflect that.&lt;/p&gt;

&lt;p&gt;This isn't a "set it and forget it" feature. It's a &lt;strong&gt;feedback loop&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Build It
&lt;/h3&gt;

&lt;p&gt;The simplest version: a script that runs weekly (cron job, CI pipeline, whatever) that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pulls your top navigation paths from Google Analytics, Plausible, or whatever you use&lt;/li&gt;
&lt;li&gt;Generates a JSON with the speculation rules&lt;/li&gt;
&lt;li&gt;Deploys it as a static file or injects it at build time
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build-speculation-rules.js&lt;/span&gt;
&lt;span class="c1"&gt;// Run this weekly via CI/cron&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;generateRules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Fetch top navigation paths from your analytics&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;topPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTopPathsFromAnalytics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g., [{ from: "/", to: "/work", percentage: 73 }, ...]&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Build rules per page&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rulesPerPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;for &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;path&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;topPaths&lt;/span&gt;&lt;span class="p"&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;rulesPerPage&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="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;rulesPerPage&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="k"&gt;from&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;rulesPerPage&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="k"&gt;from&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="na"&gt;url&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;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;eagerness&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;percentage&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moderate&lt;/span&gt;&lt;span class="dl"&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;conservative&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="c1"&gt;// 3. Write the output&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rulesPerPage&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;Then in your template/layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get the precomputed rules for the current page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentPageRules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;speculationData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentPath&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;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;prerender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentPageRules&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;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eagerness&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moderate&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&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="na"&gt;eagerness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;moderate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;prefetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentPageRules&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;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eagerness&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conservative&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&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="na"&gt;eagerness&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;conservative&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Eagerness Trick
&lt;/h3&gt;

&lt;p&gt;Here's where it gets smart. Use analytics to decide eagerness too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More than 60% of users navigate there?&lt;/strong&gt; Use &lt;code&gt;moderate&lt;/code&gt; (prerender on hover)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Between 20-60%?&lt;/strong&gt; Use &lt;code&gt;conservative&lt;/code&gt; (prerender on mousedown)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less than 20%?&lt;/strong&gt; Don't bother&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're not wasting resources prerendering pages nobody visits. And you're aggressively prerendering the ones everybody does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure the Impact
&lt;/h3&gt;

&lt;p&gt;Once you have this loop running, track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LCP (Largest Contentful Paint)&lt;/strong&gt; for pages that were prerendered vs not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation timing&lt;/strong&gt; using the Performance API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hit rate&lt;/strong&gt;: how often a prerendered page was actually visited
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntriesByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&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;navEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activationStart&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This page was prerendered!&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Prerender saved:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;navEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activationStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ms&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed that data back into the loop. Drop pages with low hit rates. Promote pages with high navigation probability. Every week your speculation rules get smarter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;script type="speculationrules"&amp;gt;&lt;/code&gt; to your HTML&lt;/li&gt;
&lt;li&gt;Define which pages to pre-load and with what eagerness&lt;/li&gt;
&lt;li&gt;Your pages load instantly&lt;/li&gt;
&lt;li&gt;No libraries, no frameworks, no weird stuff&lt;/li&gt;
&lt;li&gt;Browsers that don't support it just ignore it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect it to your analytics and update weekly&lt;/strong&gt; -- that's where the real value is&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Static speculation rules are a quick win. Analytics-driven speculation rules are a compounding advantage. Every week your site gets faster because it gets smarter about what to pre-load.&lt;/p&gt;

&lt;p&gt;It's free, it's easy, and it makes a real difference. There's no reason not to use it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this helped, drop a like and follow for more web performance content.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>frontend</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>That Side Project You Abandoned? I Finally Finished Mine.</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Mon, 26 Jan 2026 15:05:21 +0000</pubDate>
      <link>https://forem.com/andresclua/that-side-project-you-abandoned-i-finally-finished-mine-87i</link>
      <guid>https://forem.com/andresclua/that-side-project-you-abandoned-i-finally-finished-mine-87i</guid>
      <description>&lt;p&gt;We all have one. That side project you started super excited, thinking it would be &lt;em&gt;the one&lt;/em&gt;. You set up the repo, picked the stack, maybe even bought a domain. And then... life happened. Work got crazy. A new idea came up. The project just sat there for months.&lt;/p&gt;

&lt;p&gt;This is the story of how I finally finished mine.&lt;/p&gt;

&lt;p&gt;Last year, I stopped by Hotel Domine in Bilbao to grab a bite. The lobby has this crazy clock on the wall. I stood there like an idiot for five minutes, watching the hands move together to form numbers. I later found out it was inspired by &lt;strong&gt;"A Million Times"&lt;/strong&gt; by Humans Since 1982 — an art piece where hundreds of analog clocks work together to display time. Each small clock does its own thing, but together they show something bigger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmopiov5bxbq8reb50ny.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frmopiov5bxbq8reb50ny.jpg" alt=" " width="550" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was hooked. I &lt;em&gt;had&lt;/em&gt; to build a web version.&lt;/p&gt;

&lt;p&gt;I started the project that same week. I went with React and Vite — not my usual stack at all. I started with &lt;strong&gt;Angular 1.6 **(yes, I'm that old), quickly moved to **Vue&lt;/strong&gt; and never left that ecosystem. These days I mostly work with &lt;strong&gt;Astro&lt;/strong&gt;. Playing with time and dates is one of the first things you learn as a developer, right? So building a clock in React felt like a good way to get familiar with it without getting too deep. I had a basic grid of clocks working in a few hours. This was going to be easy!&lt;/p&gt;

&lt;p&gt;Then I didn't touch it for almost two years. Sound familiar?&lt;/p&gt;

&lt;p&gt;The problem wasn't motivation. It was that weird gap between "it works" and "I'm actually proud of this." That phase where you're moving pixels around, questioning every decision, and wondering if anyone will even care.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Finally Got Me to Finish
&lt;/h2&gt;

&lt;p&gt;I'm not going to lie, there was no magic moment. What worked for me was simple: &lt;strong&gt;I made it smaller.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of building the ultimate clock with every feature I could think of, I focused on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The main thing&lt;/strong&gt; — Mini clocks with two hands that rotate to form digits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One animation&lt;/strong&gt; — A nice entrance when the page loads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One extra detail&lt;/strong&gt; — A WebGL background that reacts to mouse movement&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No settings. No themes. No "export as GIF." Just a clock that looks cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Stuff
&lt;/h2&gt;

&lt;p&gt;For those who want to know how it works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Clock Grid:&lt;/strong&gt;&lt;br&gt;
Each digit is a 3×6 grid of mini analog clocks. Every mini clock has two hands that can point anywhere. By setting the right angles, you can "draw" numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Each position is [hourHand, minuteHand] angles&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digitPatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mi"&gt;0&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;TL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Top: corners + horizontal line&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;    &lt;span class="c1"&gt;// Vertical lines with fill&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;V&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BR&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Bottom row&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ... more digits&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;The Entrance Animation:&lt;/strong&gt;&lt;br&gt;
GSAP handles the staggered reveal. Each mini clock scales and rotates in from a random position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;gsap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;miniClocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stagger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;each&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;random&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;back.out(1.7)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Background:&lt;/strong&gt;&lt;br&gt;
Three.js with particles. They float slowly, but when you move your mouse (or tilt your phone), they react. It adds some depth without distracting from the clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Done is better than perfect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I could have spent another month adding features. Instead, I shipped something that works and looks good. The "just one more thing" mindset kills projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Limits make things easier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having only three things to build made every decision simpler. Should I add dark mode? No, it's not on the list. Should I add 12-hour format? No. Move on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Side projects don't need to be useful&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This clock has no practical purpose. Your phone already tells time. But building something just because it's &lt;em&gt;cool&lt;/em&gt; is a good enough reason. Not everything needs to be a startup idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The clock is live at &lt;a href="https://clock.andresclua.com" rel="noopener noreferrer"&gt;clock.andresclua.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Move your mouse around (or tilt your phone) to see the background react. Watch the seconds go by. Maybe stand there like an idiot for five minutes, like I did in that hotel lobby.&lt;/p&gt;

&lt;p&gt;And if you have your own abandoned project sitting in a repo somewhere — maybe it's time to make it smaller and ship it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your abandoned side project? Drop it in the comments — maybe we can push each other to finally finish them.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>ui</category>
      <category>webgl</category>
    </item>
    <item>
      <title>The AI Coding Shortcut: Balance Speed with Learning</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Mon, 14 Jul 2025 12:59:31 +0000</pubDate>
      <link>https://forem.com/andresclua/the-ai-coding-shortcut-balance-speed-with-learning-3mfc</link>
      <guid>https://forem.com/andresclua/the-ai-coding-shortcut-balance-speed-with-learning-3mfc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI code assistants can write functions, fix simple bugs, and save us time. But there’s a habit—sometimes called “instinct coding”—where you ask AI for all the code and then ship it without proper checks. That shortcut feels good, but it has real costs for both your project and your growth as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI Helps Us Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fast setup: In seconds, AI creates project templates or folder structures.&lt;/li&gt;
&lt;li&gt;Instant examples: You get code samples for new libraries without searching online.&lt;/li&gt;
&lt;li&gt;Consistent style: AI follows naming and formatting rules so your code stays tidy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These perks make AI a popular helper, especially under tight deadlines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is “Instinct Coding”?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For me “Instinct coding” means:&lt;/li&gt;
&lt;li&gt;You give a brief prompt to AI.&lt;/li&gt;
&lt;li&gt;It generates code.&lt;/li&gt;
&lt;li&gt;You paste that code directly into your project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skipping careful review leads to unpredictable results and hidden problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning by Doing vs. Copy-Paste Speed
&lt;/h2&gt;

&lt;p&gt;Writing code yourself, debugging, and understanding each error teach you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve performance by spotting slow parts.&lt;/li&gt;
&lt;li&gt;Handle unusual edge cases before real users see them.&lt;/li&gt;
&lt;li&gt;Debug complex issues with confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copying AI output feels fast but cuts short the trial-and-error that builds true skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact on Juniors and Seniors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Juniors:&lt;/strong&gt; Beginners may lean too heavily on AI, missing out on core learning. They risk never understanding basic concepts like data structures or algorithms, because they copy code without exploring why it works. Over time, this creates gaps that make it hard to solve novel problems alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seniors:&lt;/strong&gt; Experienced developers might use AI to speed up routine tasks, but overuse can dull their edge. Seniors need deep system knowledge to design architecture, review pull requests, and mentor others. If they rely solely on AI, they may lose familiarity with new frameworks, struggle to catch subtle bugs, and find it harder to guide junior teammates.&lt;/p&gt;

&lt;p&gt;Balancing AI help with hands-on work ensures both groups keep growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Holes and Hidden Bugs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Credentials exposure: AI can insert real or dummy API keys and passwords by mistake.&lt;/li&gt;
&lt;li&gt;Vulnerable dependencies: It may include outdated libraries or unsafe code patterns.&lt;/li&gt;
&lt;li&gt;Technical debt: Generated code often lacks tests, comments, and clear structure, making future maintenance harder and riskier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a careful review, these issues can lead to serious production failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart Ways to Use AI Coding
&lt;/h2&gt;

&lt;p&gt;Use AI where it shines, but always review its work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boilerplate tasks: Generate basic file structures, config settings, or helper functions.&lt;/li&gt;
&lt;li&gt;Prototyping: Quickly spin up demos to share ideas with clients or teammates.&lt;/li&gt;
&lt;li&gt;Requirement clarification: Let product managers draft rough versions that engineers can refine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After generation, take time to read, test, and clean up the code before deploying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI is a powerful ally—but not a replacement for hands-on coding and critical thinking. Relying on it without understanding your code can lead to hidden bugs, security holes, and technical debt. The best approach is to balance: use AI to speed up routine work, but keep your hands on the wheel. Write, test, and learn from your own code. &lt;/p&gt;

&lt;p&gt;That way, you build solid software and strengthen your skills—whether you’re just starting out or guiding a team.&lt;/p&gt;

&lt;p&gt;Probably most people don’t care, but every AI request runs on powerful servers and uses a lot of electricity. If every developer leans on AI for every line of code, the total energy use—and carbon footprint—of software development will go up a lot. This tech is super helpful, but let’s use it carefully in our daily work and when consuming AI-generated content—whether that’s code, images, videos, or anything else.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Let It Flow – Effortless Noise Animation for the DOM</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Tue, 24 Jun 2025 20:15:47 +0000</pubDate>
      <link>https://forem.com/andresclua/let-it-flow-effortless-noise-animation-for-the-dom-4aca</link>
      <guid>https://forem.com/andresclua/let-it-flow-effortless-noise-animation-for-the-dom-4aca</guid>
      <description>&lt;h2&gt;
  
  
  Why I Love Noise (and You Probably Do Too)
&lt;/h2&gt;

&lt;p&gt;As someone who works across creative code, product design, and interface motion, I often find myself looking for ways to make digital experiences feel &lt;em&gt;less rigid&lt;/em&gt;. Whether it's subtle breathing in a background or a micro-interaction that wobbles just right, nothing beats the magic of &lt;strong&gt;Perlin noise&lt;/strong&gt; or its more modern sibling, &lt;strong&gt;Simplex noise&lt;/strong&gt;, for bringing that organic, almost-natural motion to life.&lt;/p&gt;

&lt;p&gt;But here’s the thing—most of the tools we use to create that kind of movement aren’t really made for the web. Especially when working on B2B client projects, where simplicity and performance are everything, pulling in a full noise generator or a 3D engine just to move a few elements feels… excessive.&lt;/p&gt;

&lt;p&gt;So I built something to bridge that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet &lt;a href="https://www.npmjs.com/package/@andresclua/perlindom" rel="noopener noreferrer"&gt;PerlinDOM&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PerlinDOM&lt;/strong&gt; is a super lightweight (less than 1 kB gzipped), zero-dependency JavaScript library that gives any HTML element natural, Perlin-style motion — no canvas, no WebGL, no dependencies, no drama.&lt;/p&gt;

&lt;p&gt;It’s perfect when you want to make a boring interface feel a little more alive without writing boilerplate or compromising performance.&lt;/p&gt;

&lt;p&gt;I’ve been a long-time fan of noise-driven animation in &lt;strong&gt;Three.js&lt;/strong&gt;, but when you just want to animate a DOM element, spinning up a 3D renderer is like bringing a rocket launcher to a pillow fight.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Animating the DOM with Noise Gets Messy
&lt;/h3&gt;

&lt;p&gt;Normally, if you want to apply noise motion to DOM elements, you end up doing something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load a noise-generation library (often quite large).&lt;/li&gt;
&lt;li&gt;Manually set up a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop.&lt;/li&gt;
&lt;li&gt;Map the noise values to CSS transforms.&lt;/li&gt;
&lt;li&gt;Handle play/pause logic, cleanups, and performance tweaks…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What should be a creative experiment becomes a maintenance headache.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PerlinDOM Does Instead
&lt;/h2&gt;

&lt;p&gt;PerlinDOM wraps all of that into a single, clean, flexible API. Here’s what it gives you out of the box:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Organic motion&lt;/strong&gt; — never robotic, always smooth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero dependencies&lt;/strong&gt; — literally just plug and play.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny footprint&lt;/strong&gt; — less than 1kB gzipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean API&lt;/strong&gt; — with &lt;code&gt;play()&lt;/code&gt;, &lt;code&gt;pause()&lt;/code&gt;, &lt;code&gt;destroy()&lt;/code&gt;, and optional &lt;code&gt;seed&lt;/code&gt; support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable&lt;/strong&gt; — control the motion range, speed, and lerping with config options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient&lt;/strong&gt; — runs on &lt;code&gt;requestAnimationFrame&lt;/code&gt; for buttery-smooth animation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Use it when you want:
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Great for&lt;/th&gt;
&lt;th&gt;Not ideal for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subtle hover effects&lt;/td&gt;
&lt;td&gt;Complex, timeline-driven scenes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floating UI blobs or "breathing"&lt;/td&gt;
&lt;td&gt;Real-world physics or collisions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backgrounds with organic motion&lt;/td&gt;
&lt;td&gt;3D environments with heavy rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Light dashboards &amp;amp; playful UIs&lt;/td&gt;
&lt;td&gt;Frame-perfect synchronization needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/andresclua/embed/preview/YPXdeWy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;Need motion without a full motion engine?&lt;br&gt;&lt;br&gt;
Try &lt;a href="https://www.npmjs.com/package/@andresclua/perlindom" rel="noopener noreferrer"&gt;PerlinDOM&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
It’s designed for when your site needs to feel more &lt;em&gt;alive&lt;/em&gt; — without overthinking it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>animation</category>
      <category>perlin</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Navigating Asynchronous JavaScript: From Promises to Async/Await and Beyond</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Fri, 15 Mar 2024 09:25:12 +0000</pubDate>
      <link>https://forem.com/andresclua/navigating-asynchronous-javascript-from-promises-to-asyncawait-and-beyond-13li</link>
      <guid>https://forem.com/andresclua/navigating-asynchronous-javascript-from-promises-to-asyncawait-and-beyond-13li</guid>
      <description>&lt;p&gt;In the dynamic world of JavaScript development, understanding asynchronous operations &lt;strong&gt;is essential&lt;/strong&gt;. This blog post delves into the core concepts that make managing asynchronous code not just manageable, but elegant and effective. &lt;/p&gt;

&lt;p&gt;From the foundational &lt;strong&gt;promises&lt;/strong&gt;, through the evolution of promise chaining with &lt;strong&gt;.then()&lt;/strong&gt;, to the streamlined &lt;strong&gt;async/await syntax&lt;/strong&gt;, and the careful navigation around &lt;strong&gt;callback hell&lt;/strong&gt;, we’ll cover the tools and techniques to elevate your JavaScript code.&lt;/p&gt;

&lt;p&gt;My objective is to offer you a comprehensive understanding that balances fun and functionality, ensuring &lt;strong&gt;you can tackle asynchronous challenges with confidence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Promises:&lt;/strong&gt; The bedrock of asynchronous JavaScript, encapsulating potential future values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.then() Chaining:&lt;/strong&gt; An evolution in handling sequential async operations elegantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoiding Callback Hell:&lt;/strong&gt; Strategies to evade the notorious complexity of nested callbacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async/Await:&lt;/strong&gt; A syntactical sugar that simplifies writing async code akin to synchronous patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling:&lt;/strong&gt; The art of using try...catch...finally to manage exceptions in async flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this post, you'll have a solid grasp of these concepts, ready to apply them to enhance your JavaScript projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Promises: The Foundation
&lt;/h3&gt;

&lt;p&gt;At the heart of asynchronous JavaScript lie Promises, objects that represent values which may be available now, in the future, or never. These values mirror real-world operations—such as fetching data from an API—encapsulating their eventual success or failure. Promises exist in one of three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pending: The initial state, where the outcome is still unknown.&lt;/li&gt;
&lt;li&gt;Fulfilled: Indicates successful operation completion.&lt;/li&gt;
&lt;li&gt;Rejected: Signifies a failure in the operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;learningOutcome&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;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isSuccessful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;isSuccessful&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Successfully learned JavaScript!&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Learning journey continues...&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Streamlining with .then() Chaining
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;.then()&lt;/code&gt; method provides a structured way to specify actions following a Promise's resolution or rejection, thus avoiding the pitfalls of nested callbacks and improving code readability.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;learnJavaScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Learned JavaScript!'&lt;/span&gt;
  &lt;span class="c1"&gt;// Perform a simple follow-up action&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Preparing to apply knowledge...&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="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="o"&gt;=&amp;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;Failed to learn JavaScript:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern improves upon nested callbacks, offering a more manageable approach to handling multiple asynchronous tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Callback Hell: A Tangled Web
&lt;/h3&gt;

&lt;p&gt;Callback Hell, sometimes known as "The Pyramid of Doom," refers to the difficulty in managing and maintaining code that relies heavily on nested callbacks for asynchronous operations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;return&lt;/span&gt; &lt;span class="nf"&gt;getMoreData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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;return&lt;/span&gt; &lt;span class="nf"&gt;getEvenMoreData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finally got all data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&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 Evolution: Async/Await
&lt;/h3&gt;

&lt;p&gt;This nesting leads to code that &lt;strong&gt;is hard to read&lt;/strong&gt;, understand, and debug, motivating the search for cleaner solutions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;masterJavaScript&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;learnJavaScript&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'Learned JavaScript!'&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;performAnotherAsyncOperation&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextResult&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;This approach streamlines asynchronous code, making it easier to read and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mastering Error Handling with try...catch...finally
&lt;/h3&gt;

&lt;p&gt;Effective error handling in asynchronous code is crucial. The &lt;code&gt;try...catch...finally&lt;/code&gt; syntax in async/await operations ensures that errors are caught and handled gracefully, maintaining the robustness of applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchDataSafely&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Data fetched successfully:&lt;/span&gt;&lt;span class="dl"&gt;'&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="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;Failed to fetch data:&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;finally&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Attempt to fetch data completed.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Async/await syntax, introduced to simplify working with promises, makes asynchronous code look and behave more like synchronous code. It's the equivalent of telling JavaScript, "Wait here until this task finishes, then move on."&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;codeExecution&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;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;codingPromise&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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:&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By embracing these techniques, you’re not just writing code; you’re crafting an efficient, maintainable, and elegant codebase. &lt;/p&gt;

&lt;p&gt;Asynchronous JavaScript no longer needs to be daunting. With promises, .then() chaining, and the async/await syntax, you have the tools at your disposal to handle any asynchronous operation gracefully. &lt;/p&gt;

&lt;p&gt;Remember, the key to mastering JavaScript's asynchronous nature lies in understanding and effectively applying these concepts. &lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Crafting My NPM Package: Beyond jQuery</title>
      <dc:creator>Andrés Clúa</dc:creator>
      <pubDate>Mon, 11 Mar 2024 21:07:53 +0000</pubDate>
      <link>https://forem.com/andresclua/my-journey-developing-my-own-npm-package-56ff</link>
      <guid>https://forem.com/andresclua/my-journey-developing-my-own-npm-package-56ff</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A few years ago, when I began to lead development teams, I often faced a problem. I would say, "use the function we used in another project," and we would always end up copying and pasting code from one project to another. &lt;/p&gt;

&lt;p&gt;Yes, we were using &lt;code&gt;npm&lt;/code&gt; and similar tools, but sometimes the tasks we needed were very specific, like checking the browser, adding or removing styles, and many developers still preferred using jQuery for those tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft
&lt;/h2&gt;

&lt;p&gt;So I thought, "Why not create my own package?" I can address these small items, all in one library. My team will have a single source of truth, and together, we can solve our problems.&lt;/p&gt;

&lt;p&gt;You might think vanilla JS makes jQuery unnecessary, and you're right. However, I chose to start with something easier to manage, aiming to use it for our internal projects. This also helps old developers move to the vanilla ecosystem more smoothly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;p&gt;1 - Develop an NPM Module&lt;br&gt;
2 - Avoid Repetitive Work&lt;br&gt;
3 - Connect with other devs&lt;br&gt;
4 - Make the library Grow in terms of functionalities.&lt;/p&gt;
&lt;h2&gt;
  
  
  Library Features
&lt;/h2&gt;

&lt;p&gt;The library offers functionalities for manipulating HTML elements and attributes with ease. Features include hiding, showing, styling elements, adding or removing classes, toggling classes, and checking element matches. &lt;/p&gt;

&lt;p&gt;It also enables attribute manipulation, converts strings to boolean or number, identifies browser types and systems, and introduces delays in code execution. These tools streamline web development tasks, enhancing efficiency and code readability. &lt;/p&gt;

&lt;p&gt;Some Examples&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hide Elements&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;u_addClass&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;selector&lt;/span&gt;&lt;span class="dl"&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;add-new-class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// examples&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add-class&lt;/span&gt;&lt;span class="dl"&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;add-new-class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add-class&lt;/span&gt;&lt;span class="dl"&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;add-new-class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add-class&lt;/span&gt;&lt;span class="dl"&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;add-new-class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-element-class&lt;/span&gt;&lt;span class="dl"&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;add-new-class&lt;/span&gt;&lt;span class="dl"&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;Add style to elements&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;u_style&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Apply multiple styles to elements with class 'styled-elements'&lt;/span&gt;
&lt;span class="nf"&gt;u_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.styled-elements&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;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&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;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt; &lt;span class="c1"&gt;// if no unit is define will use pixels&lt;/span&gt;
&lt;span class="nf"&gt;u_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.styled-elements&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;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&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;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&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;Add Single class or multiple Classes to element&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;u_addClass&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Add 'class1' and 'class2' to elements with class 'add-classes-to-me'&lt;/span&gt;
&lt;span class="nf"&gt;u_addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add-classes-to-me&lt;/span&gt;&lt;span class="dl"&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;class1 class2&lt;/span&gt;&lt;span class="dl"&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;Check if element has matches certain criteria&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;u_matches&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Check if there's an element with the class 'active'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;u_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.menu-item&lt;/span&gt;&lt;span class="dl"&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;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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 javascript"&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;u_matches&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Check if there's an element containing both 'active' and 'highlighted' classes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasActiveAndHighlighted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;u_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.menu-item&lt;/span&gt;&lt;span class="dl"&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;active highlighted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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 javascript"&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;u_matches&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;@andresclua/jsutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Check if there's an element with a data-attribute 'data-role' equal to 'admin'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasAdminRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;u_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&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;admin&lt;/span&gt;&lt;span class="dl"&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;data-role&lt;/span&gt;&lt;span class="dl"&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;Browser Type&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;u_browser&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;@andresclua/jsutil&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;isChrome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;u_browser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find more documentation on how to use it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jsutildocs.netlify.app/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zhet7p4r32d0hfd7sfj.jpg" alt="Alt Text" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Creating &lt;strong&gt;my npm package was very rewarding&lt;/strong&gt;. I started with webpack but switched to Vite recently, to use its &lt;a href="https://vitejs.dev/guide/build" rel="noopener noreferrer"&gt;library mode&lt;/a&gt;. This change made my package work better for UMD and ES modules, improving how developers can use it.&lt;/p&gt;

&lt;p&gt;For the latest version, I've improved the documentation using &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, which I like for its ease of use in web projects.&lt;/p&gt;

&lt;p&gt;Using these tools made development smoother and helped me make a useful and easy-to-understand package. Making my npm package taught me about being adaptable, the importance of good documentation, and keeping up with new technology to help developers.&lt;/p&gt;

&lt;p&gt;I enjoyed sharing about making my npm package. It's been a great journey, and I'm excited to share more. Thanks for reading my first post. I'd love to hear your thoughts or feedback!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>npm</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
