<?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: 137Foundry</title>
    <description>The latest articles on Forem by 137Foundry (@137foundry).</description>
    <link>https://forem.com/137foundry</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%2F3856342%2F39ac4be7-399f-4f6e-9a32-60abf8a8a324.png</url>
      <title>Forem: 137Foundry</title>
      <link>https://forem.com/137foundry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/137foundry"/>
    <language>en</language>
    <item>
      <title>Why Your Webhook Endpoint Keeps Getting Duplicate Events (And How to Fix It)</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Sun, 05 Apr 2026 05:22:53 +0000</pubDate>
      <link>https://forem.com/137foundry/why-your-webhook-endpoint-keeps-getting-duplicate-events-and-how-to-fix-it-3d9c</link>
      <guid>https://forem.com/137foundry/why-your-webhook-endpoint-keeps-getting-duplicate-events-and-how-to-fix-it-3d9c</guid>
      <description>&lt;p&gt;You checked the logs. The order was fulfilled. The confirmation email was sent. And yet the webhook provider is showing another retry in the delivery history, and now you have two fulfilled orders for one payment. The logic ran twice, and you have no idea when it started happening or how many times it has happened before.&lt;/p&gt;

&lt;p&gt;Duplicate webhook event processing is one of those problems that sits invisible in production for weeks or months, then surfaces suddenly as a customer complaint or a billing discrepancy. Understanding exactly why it happens makes it straightforward to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Duplicate Events Happen
&lt;/h2&gt;

&lt;p&gt;Webhook providers retry delivery when your endpoint does not return a successful response within their timeout window. The provider does not know whether the timeout happened because your server was down, because your processing was slow, or because your handler successfully ran but crashed before sending the response. From their perspective, the delivery failed. So they try again.&lt;/p&gt;

&lt;p&gt;The specific scenario that causes most duplicate processing is this: your endpoint starts processing a payment event, completes the order fulfillment logic, and then your process crashes, restarts, or hits an uncaught exception before executing &lt;code&gt;res.status(200).send()&lt;/code&gt;. The event processing succeeded. The response never went out. The provider retries. You process it again.&lt;/p&gt;

&lt;p&gt;This is not a provider bug. Retry-on-failure is a core design feature of reliable event delivery systems. The problem is on your side: your endpoint is processing events without any protection against running the same logic twice.&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%2Fl9iqsmpeq8e76konhp6j.jpeg" 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%2Fl9iqsmpeq8e76konhp6j.jpeg" alt="Developer reviewing webhook retry logs in a server monitoring dashboard" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Christina Morillo on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Two-Part Fix
&lt;/h2&gt;

&lt;p&gt;Solving duplicate events requires two things working together.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Return 202 Before You Process Anything
&lt;/h3&gt;

&lt;p&gt;The first change is structural: your endpoint should not process the event synchronously at all. It should validate the incoming request, store the raw payload, and return &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202" rel="noopener noreferrer"&gt;HTTP 202&lt;/a&gt; immediately. Processing happens in a background worker after the acknowledgment is sent.&lt;/p&gt;

&lt;p&gt;This eliminates the window where a processing success can be paired with a failed response. The endpoint stores the event and returns. The provider receives its 202 and stops retrying. A worker handles the actual logic independently.&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/orders&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&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;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&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="nx"&gt;id&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="nx"&gt;req&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="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&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="na"&gt;received_at&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;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&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;// Respond before processing&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with this change, the provider might still deliver the same event twice in some edge cases (concurrent retries, network partitions). The 202 pattern reduces the frequency significantly but does not eliminate duplicates entirely. That is where idempotency comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Implement Idempotency Using the Event ID
&lt;/h3&gt;

&lt;p&gt;Every webhook provider includes a unique event ID in the payload. Use it. Before your background worker processes any event, check whether that event ID already exists in a &lt;code&gt;processed_events&lt;/code&gt; table. If it does, skip it. If it does not, process the event and record the ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Skip if already processed
&lt;/span&gt;    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT 1 FROM processed_events WHERE event_id = %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# Silently skip duplicate
&lt;/span&gt;
    &lt;span class="c1"&gt;# Run business logic
&lt;/span&gt;    &lt;span class="nf"&gt;execute_business_logic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Record completion after success
&lt;/span&gt;    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO processed_events (event_id, processed_at) VALUES (%s, NOW())&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record the event ID after the business logic succeeds, not before. If you record it first and processing fails, the event is permanently skipped on future retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Common Implementation Mistake
&lt;/h2&gt;

&lt;p&gt;Teams that implement idempotency checks often still see occasional duplicates. The most common cause is a race condition in the check-then-insert sequence: two worker processes both query for the event ID, both find nothing, both proceed to process, and both succeed before either inserts the record.&lt;/p&gt;

&lt;p&gt;The fix is to make the check and the claim atomic. In PostgreSQL, &lt;code&gt;INSERT ... ON CONFLICT DO NOTHING RETURNING event_id&lt;/code&gt; does this in a single operation. If the row already exists, the insert is silently skipped and RETURNING returns nothing, which your code treats as a duplicate signal. Two processes executing this simultaneously will have exactly one succeed and one skip.&lt;/p&gt;

&lt;p&gt;This atomic approach is safer than any version of "check, then decide" because the database engine enforces the uniqueness constraint at the storage level, regardless of how many concurrent workers are running.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;"Every duplicate event problem I have seen traces back to the same thing: the endpoint was doing too much before it returned a response. Responding fast and processing asynchronously eliminates the window where duplicates happen." - Dennis Traina, &lt;a href="https://137foundry.com/services/ai-integration" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Problem Hides for So Long
&lt;/h2&gt;

&lt;p&gt;Duplicate event processing is easy to miss because the system usually looks correct from the outside. The order was fulfilled - that part worked. The second fulfillment might succeed silently, or it might fail gracefully because the record already exists, or it might produce a visible error that gets swallowed by error handling. None of these outcomes necessarily produce an obvious alert.&lt;/p&gt;

&lt;p&gt;The places to look for this problem proactively are your provider's delivery dashboard (do any events show multiple successful deliveries?) and any tables where your webhook logic writes data (are there duplicate records with identical payload IDs?). For payment integrations specifically, reconciling webhook-triggered transactions against payment provider records catches duplicates that application logs miss.&lt;/p&gt;

&lt;p&gt;For a complete walkthrough of webhook reliability patterns, including async processing, idempotency, dead letter queues, and monitoring lag, the guide on &lt;a href="https://137foundry.com/articles/how-to-build-webhook-integrations-that-handle-failures-gracefully" rel="noopener noreferrer"&gt;building webhook integrations that handle failures gracefully&lt;/a&gt; covers the full architecture. &lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; works with engineering teams on exactly these integration challenges, particularly for systems where duplicates have financial or operational consequences.&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%2Fo9pfemeq7i4s9yzd2fx1.jpeg" 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%2Fo9pfemeq7i4s9yzd2fx1.jpeg" alt="Code editor showing webhook idempotency implementation with database check" width="800" height="531"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by luis gomes on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your Idempotency Implementation
&lt;/h2&gt;

&lt;p&gt;Once you have idempotency in place, it is worth verifying it works correctly before you rely on it in production. The test is straightforward: trigger a real event from your provider's dashboard, let your handler process it, then use your provider's replay feature or a tool like Postman to resend the exact same event to your endpoint.&lt;/p&gt;

&lt;p&gt;Your handler should return 200 or 202 on the second delivery without running any business logic. Check that no duplicate records were created in your database and that the idempotency table contains exactly one entry for the event ID. If your duplicate check works correctly, the second delivery is a no-op at the business logic level.&lt;/p&gt;

&lt;p&gt;Also test the race condition path: send two deliveries of the same event in rapid succession before either has been processed. Both requests should resolve cleanly, with exactly one processed and one skipped. If you are using a read-then-write check rather than an atomic insert, this test will expose the race condition - you may see both requests proceed to processing. An atomic &lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt; eliminates this window entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Checklist Before Your Next Webhook Goes Live
&lt;/h2&gt;

&lt;p&gt;Before any webhook integration reaches production, verify these three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your endpoint returns 202 (or 200) immediately without waiting for processing to complete&lt;/li&gt;
&lt;li&gt;Your processing logic checks the event ID before running any business logic&lt;/li&gt;
&lt;li&gt;Your idempotency insert uses an atomic operation (ON CONFLICT or equivalent) rather than a separate read-then-write&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three changes transform a webhook handler from brittle to resilient. They take less than a day to implement correctly, and they prevent a category of production incidents that is genuinely difficult to debug after the fact.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>5 Patterns for Building Resilient Event-Driven Integrations</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Sun, 05 Apr 2026 05:21:42 +0000</pubDate>
      <link>https://forem.com/137foundry/5-patterns-for-building-resilient-event-driven-integrations-3i8h</link>
      <guid>https://forem.com/137foundry/5-patterns-for-building-resilient-event-driven-integrations-3i8h</guid>
      <description>&lt;p&gt;Point-to-point integrations are easy to build and easy to break. You wire up an API call from one system to another, it works in testing, and then a 30-second downstream outage in production causes a cascade of failures, lost state, and a manual cleanup effort that takes longer than the outage itself.&lt;/p&gt;

&lt;p&gt;Event-driven integration patterns address this directly. They decouple the systems involved so that no single failure propagates through the entire integration chain. The tradeoff is upfront design work, but the operational stability that results is not comparable to the alternative.&lt;/p&gt;

&lt;p&gt;Here are five patterns that appear in most well-built event-driven integrations, with examples of when and why each one matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Queue-Based Event Processing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Instead of processing webhook events or API callbacks synchronously in the request handler, your endpoint stores each incoming event in a &lt;a href="https://en.wikipedia.org/wiki/Message_queue" rel="noopener noreferrer"&gt;message queue&lt;/a&gt; or database table and returns an acknowledgment immediately. A separate worker process reads from the queue and handles the business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Webhook providers set short timeout windows - typically 5-30 seconds. If your handler does any significant processing before responding, you risk timing out even when nothing is wrong with your application. The provider marks the delivery failed and retries, creating duplicates.&lt;/p&gt;

&lt;p&gt;Separating acknowledgment from processing eliminates this window entirely. The endpoint does the minimum work (validate, store, acknowledge), and the worker handles everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&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="c1"&gt;// Endpoint: validates, stores, acknowledges&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validateSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;eventStore&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&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="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Worker: processes independently&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;processQueue&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;event&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;eventStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleBusinessLogic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;eventStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;
  
  
  2. Idempotent Consumers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Every event handler checks whether the event has already been processed before running any business logic. The event ID from the provider payload is used as the idempotency key, stored in a &lt;code&gt;processed_events&lt;/code&gt; table. Processing the same event twice produces the same outcome as processing it once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; No event delivery system guarantees exactly-once delivery. Retries, network partitions, and processing failures all create scenarios where the same event arrives multiple times. Without &lt;a href="https://en.wikipedia.org/wiki/Idempotence" rel="noopener noreferrer"&gt;idempotency&lt;/a&gt; at the consumer level, duplicates produce duplicate side effects - fulfilled orders, sent emails, deducted inventory.&lt;/p&gt;

&lt;p&gt;Idempotent consumers are the primary defense against duplicate processing at the application layer, and they are necessary regardless of what queue or broker infrastructure you use.&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%2Flxss48wc6hufwj35yu1n.jpeg" 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%2Flxss48wc6hufwj35yu1n.jpeg" alt="Server room infrastructure with data pipeline processing event streams" width="800" height="1202"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Brett Sayles on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to apply:&lt;/strong&gt; Every event consumer that writes state or triggers side effects. If the handler is purely read-only and produces no observable changes, idempotency is not necessary but still not harmful.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Dead Letter Queues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Events that fail to process after a defined number of retry attempts are moved to a separate "dead letter" storage location rather than dropped. A dead letter queue (DLQ) holds failed events for manual inspection and eventual reprocessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Some events fail not because of transient infrastructure issues but because of application-level problems: a referenced record does not exist, the payload is malformed, or an edge case in the business logic throws an unhandled exception. These events will fail on every retry until the underlying issue is fixed.&lt;/p&gt;

&lt;p&gt;Without a DLQ, these events silently disappear. You may not know what data was missed until a customer reports a problem. With a DLQ, failed events are available for inspection, and once the code issue is fixed, they can be reprocessed without requiring the provider to resend them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic implementation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_RETRIES&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="nf"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# Success
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;log_attempt_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dead_letter_queue&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="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Move to DLQ
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Circuit Breakers for Downstream Failures
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; A circuit breaker wraps calls to downstream services and tracks failure rates. When failures exceed a threshold, the circuit "opens" and subsequent calls fail immediately without attempting the downstream request. After a cooldown period, the circuit enters a "half-open" state and tests whether the downstream service has recovered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; When a downstream service (a payment gateway, a shipping API, a CRM) is experiencing an outage, your event handlers will fail on every attempt. Without a circuit breaker, your workers keep attempting calls to a known-bad service, consuming resources and creating a backlog of failed events.&lt;/p&gt;

&lt;p&gt;Martin Fowler's &lt;a href="https://martinfowler.com/bliki/CircuitBreaker.html" rel="noopener noreferrer"&gt;Circuit Breaker pattern&lt;/a&gt; is the widely referenced description of this design. In practice, most teams implement it with a library (hystrix, opossum for Node.js, resilience4j for Java) rather than from scratch.&lt;/p&gt;

&lt;p&gt;The circuit breaker is particularly valuable in event-driven integrations because it prevents a temporary downstream outage from turning into a permanent data backlog. When the downstream recovers, the circuit closes and events that were queued during the outage process normally.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;"The pattern we see most often in integration work is teams building point-to-point connections that are brittle by design. Event-driven patterns are more work upfront, but the operational stability over time is not even close." - Dennis Traina, &lt;a href="https://137foundry.com/about/ai-automation" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Event Sourcing for Audit Trails
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Rather than updating application state in-place, every state change is recorded as an immutable event in an event log. The current state of any entity is derived by replaying its event history. This is the core idea behind &lt;a href="https://en.wikipedia.org/wiki/Event-driven_architecture" rel="noopener noreferrer"&gt;event sourcing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; For integration systems that handle high-value business events (payments, order state changes, inventory updates), the ability to audit what happened and replay events to rebuild state is genuinely valuable. When something goes wrong - a processing bug, a deployment that corrupted state - you can replay events from the log to restore correct state.&lt;/p&gt;

&lt;p&gt;This is a heavier architectural commitment than the other four patterns. It is worth the investment for domains with complex state transitions, audit requirements, or frequent debugging needs. For simpler integrations, a combination of the first four patterns (queue, idempotency, DLQ, circuit breaker) provides most of the reliability benefits without the full event sourcing model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to apply:&lt;/strong&gt; Financial transaction systems, inventory management with external integrations, any domain where "what happened and when" needs to be auditable over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Combining the Patterns
&lt;/h2&gt;

&lt;p&gt;These five patterns compose naturally. A typical production integration setup looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Events arrive at an endpoint that stores them in a queue (Pattern 1)&lt;/li&gt;
&lt;li&gt;Workers dequeue events, run an idempotency check (Pattern 2), and attempt processing&lt;/li&gt;
&lt;li&gt;Failed attempts are retried up to a limit, then moved to a DLQ (Pattern 3)&lt;/li&gt;
&lt;li&gt;Calls to downstream services go through a circuit breaker (Pattern 4)&lt;/li&gt;
&lt;li&gt;All state changes are written as immutable event records (Pattern 5, for applicable domains)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these patterns require specific infrastructure choices. They can be implemented with a PostgreSQL table as a queue, a Redis set for idempotency keys, a separate database table as a DLQ, and a simple failure counter in memory as a circuit breaker.&lt;/p&gt;

&lt;p&gt;For teams building integrations that handle high-value business events and where reliability matters, API integration firm &lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; designs and implements these architectures as part of their data integration work. For a detailed look at the webhook-specific reliability patterns these designs are built on, the guide to &lt;a href="https://137foundry.com/articles/how-to-build-webhook-integrations-that-handle-failures-gracefully" rel="noopener noreferrer"&gt;building webhook integrations that handle failures gracefully&lt;/a&gt; covers the core decisions.&lt;/p&gt;

&lt;p&gt;The foundational reading for event-driven reliability is well-distributed across the industry: the &lt;a href="https://en.wikipedia.org/wiki/Message_queue" rel="noopener noreferrer"&gt;message queue pattern&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Idempotence" rel="noopener noreferrer"&gt;idempotence&lt;/a&gt; articles on Wikipedia provide solid conceptual grounding, and Fowler's circuit breaker article is the canonical implementation reference.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Responsive Images Are the Most Overlooked Mobile Performance Fix</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Sat, 04 Apr 2026 05:50:18 +0000</pubDate>
      <link>https://forem.com/137foundry/why-responsive-images-are-the-most-overlooked-mobile-performance-fix-2fhn</link>
      <guid>https://forem.com/137foundry/why-responsive-images-are-the-most-overlooked-mobile-performance-fix-2fhn</guid>
      <description>&lt;p&gt;A site can have clean HTML, minimal JavaScript, optimized fonts, and efficient CSS - and still load slowly on mobile because someone uploaded a 2400px-wide JPEG for the hero section and left it there. Images are consistently the single largest contributor to page weight, and mobile users pay for that weight in both load time and data usage. The fix is well-documented, the browser support is excellent, and most teams still are not implementing it fully.&lt;/p&gt;

&lt;p&gt;The issue is not ignorance of the problem - most developers know that oversized images hurt performance. The issue is that the correct implementation of responsive images involves three separate mechanisms that interact with each other, and partial implementations leave most of the performance benefit unrealized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Part Solution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Part 1: srcset for Resolution Switching
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;srcset&lt;/code&gt; attribute tells the browser what versions of an image are available and at what widths. The browser then chooses the most appropriate version based on the current viewport width and the device's pixel density.&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;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"product-800.jpg"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"product-400.jpg 400w,
          product-800.jpg 800w,
          product-1200.jpg 1200w,
          product-1600.jpg 1600w"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Product overview showing key features"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the &lt;code&gt;sizes&lt;/code&gt; attribute (covered below), the browser defaults to assuming the image will render at 100% of the viewport width. This is often wrong - a product image in a three-column grid on desktop renders at roughly 33% of the viewport width, not 100%. The browser will still pick a reasonable option from srcset without sizes, but it will not be the optimal one.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes are not optional - they tell the browser how much space to reserve for the image before it loads, preventing layout shift. This directly improves Cumulative Layout Shift (CLS), one of the &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt; metrics Google uses in ranking calculations.&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%2F64ugpapyijjl5324ztzx.png" 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%2F64ugpapyijjl5324ztzx.png" alt="Developer optimizing website images for mobile performance" width="800" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by &lt;em&gt;Karub&lt;/em&gt; ‎ on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Part 2: sizes for Layout Context
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;sizes&lt;/code&gt; attribute tells the browser how wide the image will actually render at different viewport widths. Without this information, the browser cannot calculate the optimal source to request because it does not know whether the image occupies the full viewport width, half of it, or a fixed pixel value.&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;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"product-800.jpg"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"product-400.jpg 400w,
          product-800.jpg 800w,
          product-1200.jpg 1200w,
          product-1600.jpg 1600w"&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 480px) 100vw,
         (max-width: 768px) calc(100vw - 2rem),
         (max-width: 1200px) 50vw,
         400px"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Product overview showing key features"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the browser: on screens narrower than 480px, the image takes the full viewport width; on screens up to 768px, it takes the full width minus 2rem of padding; on screens up to 1200px, it takes half the viewport; on larger screens, it renders at a fixed 400px. With this information, the browser can select the minimum file that will look sharp at each size.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" rel="noopener noreferrer"&gt;MDN documentation on responsive images&lt;/a&gt; covers the srcset and sizes interaction in depth, including the calculation the browser uses to select the appropriate source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3: Modern Formats with the picture Element
&lt;/h3&gt;

&lt;p&gt;JPEG and PNG have been the default image formats for web use for decades. WebP and AVIF offer significantly better compression at equivalent visual quality - WebP typically reduces file size by 25-35% compared to JPEG, and AVIF reduces it further still. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element lets you serve these modern formats to browsers that support them while falling back to JPEG for browsers that do not.&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;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"product-400.avif 400w, product-800.avif 800w, product-1200.avif 1200w"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 100vw, 50vw"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"product-400.webp 400w, product-800.webp 800w, product-1200.webp 1200w"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 100vw, 50vw"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"product-800.jpg"&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"product-400.jpg 400w, product-800.jpg 800w, product-1200.jpg 1200w"&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 100vw, 50vw"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Product overview showing key features"&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
    &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser reads the source elements from top to bottom and uses the first one it supports. AVIF gets priority, then WebP, then JPEG as the fallback. The &lt;a href="https://caniuse.com/webp" rel="noopener noreferrer"&gt;caniuse data for WebP&lt;/a&gt; shows support above 97% globally, and &lt;a href="https://caniuse.com/avif" rel="noopener noreferrer"&gt;AVIF support&lt;/a&gt; has crossed 90% in modern browsers.&lt;/p&gt;

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

&lt;p&gt;Manually generating four format variants at four size variants for every image on a site is not sustainable for content-heavy projects. The right approach is to automate image processing as part of the build pipeline or at the content management layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloudinary.com/documentation/responsive_images" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;, &lt;a href="https://docs.imgix.com/" rel="noopener noreferrer"&gt;Imgix&lt;/a&gt;, and &lt;a href="https://developers.cloudflare.com/images/" rel="noopener noreferrer"&gt;Cloudflare Images&lt;/a&gt; are CDN-based services that handle format conversion, resizing, and delivery automatically. You upload a source image and the service generates appropriate derivatives on-demand based on URL parameters or automatic optimization. The &lt;a href="https://developers.google.com/speed/docs/insights/OptimizeImages" rel="noopener noreferrer"&gt;Google developer documentation on image optimization&lt;/a&gt; covers the principles behind this approach.&lt;/p&gt;

&lt;p&gt;For teams managing image processing at the build layer, &lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;Sharp&lt;/a&gt; is a Node.js image processing library that generates optimized variants efficiently. &lt;a href="https://imagemagick.org/" rel="noopener noreferrer"&gt;ImageMagick&lt;/a&gt; provides equivalent functionality in server-side and CLI contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lazy Attribute: Deferring Off-Screen Images
&lt;/h2&gt;

&lt;p&gt;Beyond serving the right image size, there is a simpler win available for below-the-fold images: native lazy loading. Adding &lt;code&gt;loading="lazy"&lt;/code&gt; to an img element tells the browser to defer loading that image until it is near the viewport, rather than loading all images as soon as the HTML is parsed.&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;!-- Images below the fold do not need to load immediately --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"article-inline-800.jpg"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"article-inline-400.jpg 400w, article-inline-800.jpg 800w"&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 100vw, 50vw"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Diagram showing responsive image size selection logic"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"450"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;loading="lazy"&lt;/code&gt; attribute has &lt;a href="https://caniuse.com/loading-lazy-attr" rel="noopener noreferrer"&gt;broad browser support&lt;/a&gt; and requires no JavaScript or external libraries. For pages with multiple inline images - articles, product listings, portfolio pages - lazy loading significantly reduces the initial page payload and improves LCP for the content above the fold.&lt;/p&gt;

&lt;p&gt;One important exception: do not apply &lt;code&gt;loading="lazy"&lt;/code&gt; to the largest contentful element or any image above the fold. The browser needs to load those immediately for LCP. Lazy loading is for images the user has not yet scrolled to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Image weight is the single biggest performance win most sites leave on the table. We regularly cut 40-60 percent off total page weight just by implementing proper srcset with a CDN in front of it - without touching a single line of application code." - Dennis Traina, &lt;a href="https://137foundry.com/services/technical-seo" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fshvz249xzzgn4cgmav9e.jpeg" 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%2Fshvz249xzzgn4cgmav9e.jpeg" alt="Website performance analytics showing image optimization results" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by AS Photography on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Starting Point
&lt;/h2&gt;

&lt;p&gt;If you are working on a site that does not yet have responsive images, the fastest path to improvement is not a full pipeline implementation - it is auditing the current images with &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; or &lt;a href="https://www.webpagetest.org" rel="noopener noreferrer"&gt;WebPageTest&lt;/a&gt; to identify the largest offenders, then implementing srcset and sizes on those specific images first. The Pareto distribution applies strongly to image weight: typically 20% of images account for 80% of the wasted bytes.&lt;/p&gt;

&lt;p&gt;For a complete look at how mobile performance decisions fit into mobile-first design as a whole - including Core Web Vitals targets, fluid typography, and touch-first interaction design - the &lt;a href="https://137foundry.com/articles/mobile-first-web-design-principles-for-ux-and-seo" rel="noopener noreferrer"&gt;guide to mobile-first web design&lt;/a&gt; connects these technical details to the broader design approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; handles image optimization as a standard part of web development projects, integrating the three-part responsive image system alongside CDN configuration so performance does not degrade as content grows.&lt;/p&gt;

&lt;p&gt;The responsive image specification has been supported in all major browsers since 2016. There is no longer a compatibility reason to avoid it. The only remaining barrier is implementation effort - and for most sites, addressing the largest image offenders takes an afternoon and measurably improves both page speed and search rankings.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>5 Open Source CSS Frameworks for Mobile-First Web Development</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Sat, 04 Apr 2026 05:50:16 +0000</pubDate>
      <link>https://forem.com/137foundry/5-open-source-css-frameworks-for-mobile-first-web-development-2he9</link>
      <guid>https://forem.com/137foundry/5-open-source-css-frameworks-for-mobile-first-web-development-2he9</guid>
      <description>&lt;p&gt;CSS frameworks have evolved significantly from their early origins as grid systems bolted onto fixed-width layouts. The modern options are either built mobile-first from the ground up or have been substantially rearchitected in that direction. Here are the ones worth evaluating for projects where responsive design quality matters from day one.&lt;/p&gt;

&lt;p&gt;Here are the ones I keep coming back to, depending on the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Tailwind CSS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;tailwindcss.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tailwind takes a utility-first approach - instead of semantic component classes, you compose layouts directly in HTML using low-level utility classes. Mobile-first is baked into the responsive prefix system: &lt;code&gt;md:flex-row&lt;/code&gt; means "apply flex-row at the md breakpoint and above," which is a mobile-first media query by definition.&lt;/p&gt;

&lt;p&gt;The configuration file gives you full control over breakpoints, spacing scales, color palettes, and typography. Tailwind's JIT (just-in-time) compiler generates only the CSS you actually use, so production builds are typically a few kilobytes rather than the hundreds of kilobytes a legacy framework ships with.&lt;/p&gt;

&lt;p&gt;The tradeoff is verbose HTML and a learning curve for the naming conventions. But for teams that have adopted it, the speed advantage for building responsive layouts is substantial - you can see the layout respond across breakpoints as you add classes without switching to a stylesheet.&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%2F1giq7zvjubafg6s0snib.jpeg" 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%2F1giq7zvjubafg6s0snib.jpeg" alt="Developer writing CSS code on computer screen" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Bibek ghosh on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bootstrap 5
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;getbootstrap.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap 5 dropped the jQuery dependency and significantly improved its mobile-first implementation compared to earlier versions. The grid system uses Flexbox throughout and the breakpoint system goes from &lt;code&gt;xs&lt;/code&gt; (no prefix, mobile default) upward through &lt;code&gt;sm&lt;/code&gt;, &lt;code&gt;md&lt;/code&gt;, &lt;code&gt;lg&lt;/code&gt;, &lt;code&gt;xl&lt;/code&gt;, and &lt;code&gt;xxl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Bootstrap's strength is its component library. If you need a working mobile-friendly navigation, modal, accordion, carousel, or form in minimal time, Bootstrap has production-ready implementations of all of them. The documentation is comprehensive and the browser compatibility is broad.&lt;/p&gt;

&lt;p&gt;For projects where design differentiation matters, Bootstrap requires more design work to avoid the "Bootstrap look" - but the underlying grid and component infrastructure is solid. The &lt;a href="https://getbootstrap.com/docs/5.3/layout/grid/" rel="noopener noreferrer"&gt;Bootstrap 5 grid documentation&lt;/a&gt; is worth reading even if you use a different framework, as it explains mobile-first grid concepts clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Foundation by Zurb
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://get.foundation/" rel="noopener noreferrer"&gt;get.foundation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Foundation was mobile-first before most frameworks made it a default. The grid system uses a 12-column flexbox layout with named breakpoints (small, medium, large, xlarge, xxlarge) and a progressive-disclosure approach where small-screen styles are the base and larger viewports add or override them.&lt;/p&gt;

&lt;p&gt;Foundation's XY Grid is notably more flexible than Bootstrap's grid for complex layouts - it supports both fixed-count column grids and flexible grid patterns that adapt to content width. The framework also ships with motion UI for accessible animations, which is a useful inclusion for projects where entrance animations matter.&lt;/p&gt;

&lt;p&gt;Foundation is used less frequently than Tailwind or Bootstrap in new projects today, but it remains an excellent option for content-heavy sites and applications where its grid flexibility addresses layout problems that simpler grids struggle with.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Bulma
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://bulma.io/" rel="noopener noreferrer"&gt;bulma.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bulma is a pure-CSS framework (no JavaScript) built entirely on Flexbox. The component system is semantic rather than utility-based - you apply class names like &lt;code&gt;columns&lt;/code&gt;, &lt;code&gt;column&lt;/code&gt;, &lt;code&gt;card&lt;/code&gt;, and &lt;code&gt;navbar&lt;/code&gt; that describe the component type rather than the specific styles.&lt;/p&gt;

&lt;p&gt;Bulma's mobile-first approach means all components default to stacked single-column layouts and expand to multi-column at the &lt;code&gt;tablet&lt;/code&gt; and &lt;code&gt;desktop&lt;/code&gt; breakpoints. The framework is lightweight (the full build is under 200KB unminified) and has no JavaScript dependencies, making it a good option for projects where you want a CSS layer without JavaScript coupling.&lt;/p&gt;

&lt;p&gt;The documentation includes a detailed &lt;a href="https://bulma.io/documentation/columns/responsiveness/" rel="noopener noreferrer"&gt;responsive columns guide&lt;/a&gt; that shows how to use the breakpoint classes to control column behavior across device sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pico.css
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://picocss.com/" rel="noopener noreferrer"&gt;picocss.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pico is minimal by design. It applies sensible, accessible default styles to semantic HTML elements without requiring any class names for basic layouts. A form element automatically gets mobile-friendly input sizing and spacing. A nav element gets responsive behavior. Tables reflow correctly on small screens.&lt;/p&gt;

&lt;p&gt;Pico is not the right choice for complex component-heavy applications, but for content sites, landing pages, documentation, and simple web applications, it provides a clean mobile-first baseline in under 10KB. The tradeoff is that customization requires CSS overrides rather than a configuration system, so it scales less well to large design systems.&lt;/p&gt;

&lt;p&gt;For teams building sites where mobile performance is critical, Pico's minimal footprint makes it worth considering as a starting foundation over heavier alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose Between Them
&lt;/h2&gt;

&lt;p&gt;The frameworks above serve meaningfully different use cases, and the wrong choice adds friction rather than speed. A few questions that help clarify the decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How custom does the visual design need to be?&lt;/strong&gt; Tailwind gives you the most control with the least visual opinion. Bootstrap and Foundation have stronger default aesthetics that require more work to override. Pico and Bulma sit in the middle - sensible defaults that are relatively easy to theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much component coverage do you need out of the box?&lt;/strong&gt; Bootstrap has the broadest component library: navbars, carousels, modals, accordions, dropdowns. If your project needs these and you want them already built, Bootstrap is worth the overhead. Tailwind requires you to build or import components separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the team's CSS familiarity?&lt;/strong&gt; Tailwind has a steeper initial learning curve. Developers new to CSS often find Bootstrap or Bulma's semantic class names more approachable because they map to components they can reason about. Tailwind rewards teams that are comfortable thinking in terms of CSS properties rather than component names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the performance constraints?&lt;/strong&gt; For sites where page weight directly affects business outcomes - high mobile traffic, performance-sensitive markets - Pico and Tailwind (with PurgeCSS configured) produce the smallest production builds. Bootstrap and Foundation require more careful configuration to avoid shipping unused styles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; works across the full range of these frameworks depending on project requirements - the right choice depends on team familiarity, project scale, and the balance between speed-to-ship and design customization needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Most frameworks give you the grid, but they can't give you the layout decisions. We've seen teams spend more time fighting a framework's responsive system than they would have spent writing the CSS themselves. The framework should serve the design - not the other way around." - Dennis Traina, &lt;a href="https://137foundry.com/about/design-seo" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The choice of CSS framework matters less than the discipline of writing mobile-first CSS within whatever system you choose. The frameworks above all support mobile-first patterns natively - whether you use them depends on your project's specific requirements for component coverage, bundle size, and design flexibility.&lt;/p&gt;

&lt;p&gt;For a deeper look at how mobile-first principles apply beyond the framework layer - including design decisions, performance budgets, and SEO implications - the &lt;a href="https://137foundry.com/articles/mobile-first-web-design-principles-for-ux-and-seo" rel="noopener noreferrer"&gt;guide to mobile-first web design principles&lt;/a&gt; covers what changes when you build from mobile up versus desktop down.&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%2Fvhu3nxiqxws9jzm3s4to.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%2Fvhu3nxiqxws9jzm3s4to.jpg" alt="CSS framework responsive grid layout on laptop screen" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Negative Space on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design" rel="noopener noreferrer"&gt;MDN: Responsive Web Design Basics&lt;/a&gt; - Foundation concepts before you pick a framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://web.dev/responsive-web-design-basics/" rel="noopener noreferrer"&gt;web.dev: Mobile-First&lt;/a&gt; - Google's guidance on responsive design fundamentals&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing" rel="noopener noreferrer"&gt;Google Mobile-First Indexing&lt;/a&gt; - Why mobile-first design now directly affects search rankings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/" rel="noopener noreferrer"&gt;CSS-Tricks: A Complete Guide to Flexbox&lt;/a&gt; - The layout model all modern frameworks are built on&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Interaction to Next Paint Changed How Developers Think About Responsiveness</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Fri, 03 Apr 2026 03:10:49 +0000</pubDate>
      <link>https://forem.com/137foundry/why-interaction-to-next-paint-changed-how-developers-think-about-responsiveness-5b86</link>
      <guid>https://forem.com/137foundry/why-interaction-to-next-paint-changed-how-developers-think-about-responsiveness-5b86</guid>
      <description>&lt;p&gt;For years, First Input Delay was the responsiveness metric in Core Web Vitals. It measured one thing: the delay between a user's first interaction and the browser starting to process it. Most sites passed FID easily because it only captured input delay on the first click, ignoring everything after that. A page could freeze for two seconds when you opened a dropdown menu, and FID would not register it.&lt;/p&gt;

&lt;p&gt;In March 2024, Google replaced FID with Interaction to Next Paint. The shift was not cosmetic. INP fundamentally changed what "responsive" means for web applications by measuring every interaction across the entire page visit, not just the first one, and by tracking the complete lifecycle of each interaction through input delay, processing, and paint.&lt;/p&gt;

&lt;p&gt;This article explains why the change matters, what INP actually measures, and how to think about responsiveness now that the rules have changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What FID Got Wrong
&lt;/h2&gt;

&lt;p&gt;First Input Delay measured the gap between a user's first click (or tap or keypress) and the moment the browser began running the event handler. This was useful but deeply limited.&lt;/p&gt;

&lt;p&gt;The biggest problem was that FID only captured the first interaction. If your page loaded cleanly but became sluggish after JavaScript hydration completed, after a state update triggered a heavy re-render, or after a third-party script loaded, FID would still report a good score. Users experiencing a slow, janky interface would not be reflected in the metric.&lt;/p&gt;

&lt;p&gt;FID also only measured input delay, the time spent waiting before processing started. It did not count how long the event handler itself took to run, and it did not count how long the browser took to update the screen after the handler finished. A click that had zero input delay but took 800ms to process and paint would score perfectly under FID.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;Chrome's analysis of real-user data&lt;/a&gt;, over 90% of websites passed the FID threshold of 100ms. That number made FID essentially useless as a differentiator. Meanwhile, users continued to report feeling that sites were slow and unresponsive.&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%2Fwctwtaiwz6feglolzcwq.jpeg" 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%2Fwctwtaiwz6feglolzcwq.jpeg" alt="Person using laptop with website interface visible" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by cottonbro studio on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What INP Measures Differently
&lt;/h2&gt;

&lt;p&gt;Interaction to Next Paint captures the full lifecycle of an interaction in three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input delay&lt;/strong&gt; is the time between the user's action (click, tap, keypress) and the browser starting to run your event handler. This is what FID measured, but INP captures it for every interaction, not just the first one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processing time&lt;/strong&gt; is how long your event handler takes to execute. If clicking a button triggers a state update that causes React to re-render a large component tree, that entire rendering time counts as processing time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presentation delay&lt;/strong&gt; is the time between your handler finishing and the browser updating pixels on screen. This includes layout calculation, paint, and compositing. If your handler changes CSS properties that trigger layout (like width, height, or position), the presentation delay can be significant.&lt;/p&gt;

&lt;p&gt;The INP value for a page visit is typically the interaction with the worst latency (with some statistical adjustment for pages with many interactions). Google considers 200ms or less "good," 200-500ms "needs improvement," and over 500ms "poor."&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;web.dev INP documentation&lt;/a&gt; explains the statistical methodology. For most sites, the reported INP value is the 98th percentile interaction, meaning it reflects the worst experience a typical user encounters during their visit.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Changes How You Build
&lt;/h2&gt;

&lt;p&gt;Under FID, responsiveness was mostly about avoiding long tasks during initial page load. If your JavaScript did not block the main thread before the first click, you passed. Developers focused on code splitting, lazy loading, and deferring non-critical scripts, all optimizations for the initial load window.&lt;/p&gt;

&lt;p&gt;INP requires responsiveness throughout the entire session. This shifts the focus from "fast to load" to "fast to use." Specific patterns that passed FID but fail INP:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heavy state updates on interaction.&lt;/strong&gt; Clicking a filter that triggers a re-render of 500 list items blocks the main thread during processing. Under FID, this was invisible unless it happened to be the first interaction. Under INP, it counts every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Synchronous layout calculations in event handlers.&lt;/strong&gt; Reading layout properties (like &lt;code&gt;offsetHeight&lt;/code&gt; or &lt;code&gt;getBoundingClientRect()&lt;/code&gt;) inside a click handler forces the browser to calculate layout synchronously, which blocks the main thread. The &lt;a href="https://developer.chrome.com/docs/devtools/performance/#layout" rel="noopener noreferrer"&gt;Google developers documentation on forced layout&lt;/a&gt; covers this in detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party scripts that run on interaction.&lt;/strong&gt; Analytics scripts that fire on every click, chat widgets that initialize on first interaction, and consent management platforms that intercept touches all contribute to INP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"INP changed the conversation from 'how fast does the page load' to 'how fast does the page respond to every single thing the user does.' That is a much harder bar to clear, and it is exactly what users actually care about." - Dennis Traina, &lt;a href="https://137foundry.com/about/ai-automation" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Practical Fixes That Work
&lt;/h2&gt;

&lt;p&gt;The good news is that INP optimizations follow predictable patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break up long event handlers.&lt;/strong&gt; If a click handler triggers work that takes more than 50ms, split it. Use &lt;code&gt;scheduler.yield()&lt;/code&gt; (available in Chrome and behind a flag in other browsers) or &lt;code&gt;setTimeout(0)&lt;/code&gt; to yield back to the browser between chunks of work. This lets the browser process pending user input and paint updates between your work chunks.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleFilterClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedFilter&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 the UI immediately&lt;/span&gt;
  &lt;span class="nf"&gt;showLoadingIndicator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Yield to let the browser paint the loading state&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Now do the expensive filtering&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Yield again before rendering results&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;renderResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filtered&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;Debounce rapid interactions.&lt;/strong&gt; Scroll handlers, resize listeners, and search-as-you-type inputs can fire dozens of events per second. Debounce them so the expensive work only runs once after the user stops interacting, rather than on every individual event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move computation to Web Workers.&lt;/strong&gt; Data processing, sorting large arrays, and parsing JSON payloads do not need to happen on the main thread. A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Web Worker&lt;/a&gt; runs JavaScript in a background thread that cannot block user interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize React re-renders.&lt;/strong&gt; Use &lt;code&gt;React.memo&lt;/code&gt; to prevent unnecessary re-renders, &lt;code&gt;useMemo&lt;/code&gt; for expensive calculations, and &lt;code&gt;useTransition&lt;/code&gt; to mark non-urgent state updates as low priority so they do not block the main thread during user interactions. The &lt;a href="https://react.dev/reference/react/useTransition" rel="noopener noreferrer"&gt;React documentation on Transitions&lt;/a&gt; explains how &lt;code&gt;startTransition&lt;/code&gt; keeps the UI responsive during heavy updates.&lt;/p&gt;

&lt;p&gt;For a broader look at all three Core Web Vitals and the diagnostic workflow from field data to fixes, &lt;a href="https://137foundry.com/articles/how-to-diagnose-and-fix-core-web-vitals-issues-that-hurt-your-rankings" rel="noopener noreferrer"&gt;this complete guide to Core Web Vitals&lt;/a&gt; covers LCP and CLS alongside INP.&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%2F0gcv3of2wqte4ropqbnl.jpeg" 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%2F0gcv3of2wqte4ropqbnl.jpeg" alt="Computer screen showing code development environment" width="800" height="1199"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Daniil Komov on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring INP in Production
&lt;/h2&gt;

&lt;p&gt;Lab tools like Lighthouse simulate interactions, but they cannot replicate the variety of real user behavior. Some users click rapidly, some use keyboard navigation, some interact with elements that your test script never touches. Real-user monitoring is the only reliable way to track INP.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/GoogleChromeLabs/web-vitals" rel="noopener noreferrer"&gt;web-vitals library&lt;/a&gt; reports INP values from actual visitors. Integrate it with your analytics pipeline to see which pages and which interactions have the highest INP values. The attribution build identifies the exact element and event type responsible for the worst interaction.&lt;/p&gt;

&lt;p&gt;Teams at &lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; typically pair web-vitals monitoring with the Chrome DevTools Interactions track to diagnose specific slow interactions identified in field data. That combination of real-user detection and lab investigation is the fastest path to fixing INP problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Responsiveness Standard Is Higher Now
&lt;/h2&gt;

&lt;p&gt;INP raised the bar for what counts as a responsive web application. Passing FID was easy because it only measured one moment. Passing INP requires that every interaction, on every page, throughout the entire visit, responds within 200ms. That is a higher standard, but it is also a more honest reflection of user experience. Sites that invest in INP optimization are genuinely faster to use, which translates to better engagement, lower bounce rates, and a ranking advantage in competitive search results.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>5 Browser DevTools Tricks for Debugging Core Web Vitals</title>
      <dc:creator>137Foundry</dc:creator>
      <pubDate>Fri, 03 Apr 2026 03:10:46 +0000</pubDate>
      <link>https://forem.com/137foundry/5-browser-devtools-tricks-for-debugging-core-web-vitals-1e4f</link>
      <guid>https://forem.com/137foundry/5-browser-devtools-tricks-for-debugging-core-web-vitals-1e4f</guid>
      <description>&lt;p&gt;Most developers know Chrome DevTools for setting breakpoints and inspecting CSS. But the Performance and Rendering panels contain specialized features for debugging Core Web Vitals that even experienced developers overlook. These are not experimental flags or hidden menus. They are stable, production-ready features built specifically for diagnosing LCP, CLS, and INP problems.&lt;/p&gt;

&lt;p&gt;The difference between guessing at performance issues and knowing exactly what to fix comes down to using the right DevTools features at the right time. Here are five that consistently help me track down the root cause of Core Web Vitals failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Performance Panel's Web Vitals Track
&lt;/h2&gt;

&lt;p&gt;The Performance panel records everything that happens during page load and user interaction. But the default view can be overwhelming, with dozens of tracks showing network requests, rendering activity, GPU work, and JavaScript execution simultaneously.&lt;/p&gt;

&lt;p&gt;The trick is to focus on the &lt;strong&gt;Web Vitals&lt;/strong&gt; lane in the Experience section. After recording a performance trace (press Ctrl+Shift+E to start recording with a page reload), expand the Experience track. You will see markers for LCP, layout shifts (CLS events), and interactions (INP candidates).&lt;/p&gt;

&lt;p&gt;Click on the LCP marker to see exactly which element triggered it and when it rendered. Click on a layout shift marker to see which elements moved and by how much. Click on an interaction marker to see the full breakdown of input delay, processing time, and presentation delay.&lt;/p&gt;

&lt;p&gt;This targeted approach turns a wall of data into a focused investigation. Instead of scanning the entire waterfall looking for "slow things," you start from the metric that is failing and trace backward to the cause.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.chrome.com/docs/devtools/performance/" rel="noopener noreferrer"&gt;Chrome DevTools Performance panel reference&lt;/a&gt; documents every track and what it measures. Spend 20 minutes reading it once, and your debugging sessions will be significantly faster going forward.&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%2Fizj2fdot9vp0lfx5ys2o.jpeg" 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%2Fizj2fdot9vp0lfx5ys2o.jpeg" alt="Developer analyzing code and performance metrics on screen" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Daniil Komov on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Layout Shift Regions Overlay
&lt;/h2&gt;

&lt;p&gt;CLS problems are notoriously hard to debug because layout shifts happen fast. By the time you notice the page jumped, the shift is over and you cannot tell which element moved or why.&lt;/p&gt;

&lt;p&gt;DevTools has a rendering overlay that highlights layout shift regions in real time. Open the Command Menu (Ctrl+Shift+P), type "Show Rendering," and enable &lt;strong&gt;Layout Shift Regions&lt;/strong&gt;. Now, every time an element shifts position, DevTools draws a blue rectangle around the affected area.&lt;/p&gt;

&lt;p&gt;Reload the page and watch. You will see exactly which elements shift and when. Common findings include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images loading without reserved dimensions causing text below them to jump&lt;/li&gt;
&lt;li&gt;Web fonts swapping in with different metrics than the fallback font&lt;/li&gt;
&lt;li&gt;Ads or embedded widgets injecting content above the fold&lt;/li&gt;
&lt;li&gt;Cookie consent banners that push page content down instead of overlaying it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you see the shifting element, fixing it is usually straightforward. Add explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes to images, use &lt;code&gt;font-display: optional&lt;/code&gt; to prevent font swaps, or reserve space for dynamic content with CSS &lt;code&gt;min-height&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Most CLS fixes are trivially simple once you can see which element is shifting. The hard part is always identification, not the fix itself." - Dennis Traina, &lt;a href="https://137foundry.com/about/frontend" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Network Throttling With Request Blocking
&lt;/h2&gt;

&lt;p&gt;LCP failures on fast development machines often stem from resources that are perfectly fine on a gigabit connection but catastrophically slow on mobile networks. DevTools network throttling simulates slower connections, but the real trick is combining throttling with request blocking.&lt;/p&gt;

&lt;p&gt;Open the Network panel, set throttling to "Slow 3G" or create a custom profile (DevTools lets you define exact download speed, upload speed, and latency). Then right-click any request and select "Block request URL" or "Block request domain." This lets you test what happens when specific resources are slow or unavailable.&lt;/p&gt;

&lt;p&gt;Practical uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block your analytics and chat widget scripts to see how much they affect LCP&lt;/li&gt;
&lt;li&gt;Block third-party font CDNs to test your font fallback strategy&lt;/li&gt;
&lt;li&gt;Block specific JavaScript bundles to find which ones are render-blocking&lt;/li&gt;
&lt;li&gt;Throttle to 3G and reload to see what your mobile users actually experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developer.chrome.com/docs/devtools/network/reference/" rel="noopener noreferrer"&gt;Network reference in DevTools&lt;/a&gt; covers request blocking and custom throttling profiles. For a complete walkthrough of Core Web Vitals diagnostics, &lt;a href="https://137foundry.com/articles/how-to-diagnose-and-fix-core-web-vitals-issues-that-hurt-your-rankings" rel="noopener noreferrer"&gt;this guide covers the full process&lt;/a&gt; from identifying failures in Search Console to applying targeted fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Interactions Track for INP Debugging
&lt;/h2&gt;

&lt;p&gt;Interaction to Next Paint replaced First Input Delay in March 2024, and many developers still debug it using FID-era techniques that miss the point. FID only measured input delay (the gap between a click and the browser starting to process it). INP measures the entire interaction lifecycle: input delay plus processing time plus presentation delay.&lt;/p&gt;

&lt;p&gt;The Performance panel's &lt;strong&gt;Interactions&lt;/strong&gt; track shows every user interaction during a recording. Record a trace while clicking buttons, opening dropdowns, typing in form fields, and scrolling. Each interaction appears as a block in the Interactions track, colored by duration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Green: under 200ms (good)&lt;/li&gt;
&lt;li&gt;Yellow: 200-500ms (needs improvement)&lt;/li&gt;
&lt;li&gt;Red: over 500ms (poor)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click on any interaction to see the three phases broken down. If input delay is the problem, look for long tasks running when the user clicked. If processing time is the problem, your event handler is doing too much work. If presentation delay is the problem, the browser is spending too long on layout and paint after your handler finishes.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://web.dev/articles/inp" rel="noopener noreferrer"&gt;web.dev INP guide&lt;/a&gt; explains each phase and the common causes. The &lt;a href="https://developer.chrome.com/docs/web-platform/long-animation-frames" rel="noopener noreferrer"&gt;Long Animation Frames API&lt;/a&gt; (available in Chrome 123+) provides even more detail about what scripts are blocking during interactions.&lt;/p&gt;

&lt;p&gt;Teams at &lt;a href="https://137foundry.com" rel="noopener noreferrer"&gt;137Foundry&lt;/a&gt; regularly use this workflow to trace INP problems to specific event handlers, third-party scripts, or framework hydration bottlenecks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Performance Insights Panel
&lt;/h2&gt;

&lt;p&gt;The Performance Insights panel (separate from the standard Performance panel) provides a simplified, guided view of performance data. While the Performance panel gives you raw data and expects you to know what to look for, Performance Insights highlights the specific issues and links them to actionable recommendations.&lt;/p&gt;

&lt;p&gt;After recording a trace, Performance Insights shows a timeline with annotated insights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Render blocking request" with the specific CSS or JS file identified&lt;/li&gt;
&lt;li&gt;"Layout shift" with the culprit element and the shift score&lt;/li&gt;
&lt;li&gt;"Long task" with the script responsible and its duration&lt;/li&gt;
&lt;li&gt;"LCP" with the element and timing breakdown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each insight links to documentation explaining why it matters and how to fix it. This panel is especially useful for developers who are new to performance optimization and want guided analysis rather than raw data.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.chrome.com/docs/devtools/performance-insights/" rel="noopener noreferrer"&gt;Performance Insights documentation&lt;/a&gt; walks through the panel's features. It does not replace the full Performance panel for deep investigation, but it catches the most common issues faster.&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%2Fuksourx4hyi9vdqtf9ts.jpeg" 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%2Fuksourx4hyi9vdqtf9ts.jpeg" alt="Laptop screen showing data analysis and optimization" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Tiger Lily on &lt;a href="https://www.pexels.com" rel="noopener noreferrer"&gt;Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;These five features cover different aspects of Core Web Vitals debugging:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Primary Metric&lt;/th&gt;
&lt;th&gt;What It Reveals&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web Vitals Track&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;Which metrics fail and when&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout Shift Regions&lt;/td&gt;
&lt;td&gt;CLS&lt;/td&gt;
&lt;td&gt;Which elements shift and why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Throttling + Blocking&lt;/td&gt;
&lt;td&gt;LCP&lt;/td&gt;
&lt;td&gt;Which resources delay rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interactions Track&lt;/td&gt;
&lt;td&gt;INP&lt;/td&gt;
&lt;td&gt;Which interactions are slow and why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance Insights&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;Guided analysis with recommendations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with the Web Vitals Track to identify which metric is failing. Then use the specialized tool for that specific metric. The fastest path to fixing Core Web Vitals is always: identify the failing metric, isolate the root cause, fix the specific element, and verify in field data after deploy.&lt;/p&gt;

&lt;p&gt;Performance debugging is a skill that improves with practice. These tools make the process systematic rather than based on guesswork, and they are all available right now in your browser without installing anything.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
