<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ben Halpern</title>
    <description>The latest articles on DEV Community by Ben Halpern (@ben).</description>
    <link>https://dev.clauneck.workers.dev/ben</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png</url>
      <title>DEV Community: Ben Halpern</title>
      <link>https://dev.clauneck.workers.dev/ben</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.clauneck.workers.dev/feed/ben"/>
    <language>en</language>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:47:34 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-1b8h</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-1b8h</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-14pc"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>How We Saved Big and Simplified Our Image Pipeline: Adopting bunny.net on DEV</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Tue, 16 Jun 2026 16:41:51 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/devteam/how-we-saved-big-and-simplified-our-image-pipeline-adopting-bunnynet-on-dev-3d53</link>
      <guid>https://dev.clauneck.workers.dev/devteam/how-we-saved-big-and-simplified-our-image-pipeline-adopting-bunnynet-on-dev-3d53</guid>
      <description>&lt;p&gt;Hey everyone, Ben here.&lt;/p&gt;

&lt;p&gt;If you’ve been following the journey of DEV and our open source project &lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;Forem&lt;/a&gt;, you know we’ve always been obsessed with web performance. Way back in the day, I spoke at Codeland about how to make your website so fast it goes viral in Japan, diving into the mechanics of edge caching and how we kept our page loads nearly instant.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/lGQtUDMStnI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Our core philosophy has always been simple: keep the architecture as lean as possible, cache aggressively at the edge, and let the Rails monolith (Forem) focus on what it does best. For years, Fastly has handled our HTML edge caching brilliantly—most of your page requests never even have to touch our Puma servers, which keeps our RAM usage low and our response times in the milliseconds. Fastly continues to be how all the document content on DEV is served.&lt;/p&gt;

&lt;p&gt;But while edge-caching static HTML is a well-understood problem, user-uploaded media is a completely different beast. &lt;/p&gt;

&lt;p&gt;As DEV grew, we found ourselves drowning in images. Every post cover, user avatar, comment screenshot, and challenge banner is a high-res asset uploaded by our community. Serving billions of these images globally, while keeping page sizes lightweight, eventually led us into a silent scaling trap: a tangled, multi-CDN media pipeline, massive cloud egress fees, and eye-watering monthly bills.&lt;/p&gt;

&lt;p&gt;Here is the story of how we ended the multi-CDN chaos, simplified our media architecture, saved a small fortune, and used edge scripting to build a smarter, faster image-serving pipeline with &lt;a href="https://bunny.net" rel="noopener noreferrer"&gt;bunny.net&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chaos of Multi-CDN and the Billing Reality Check
&lt;/h2&gt;

&lt;p&gt;To understand why we made the switch, you have to look at what our image pipeline used to look like. &lt;/p&gt;

&lt;p&gt;For a long time, our media stack was a bit of a patchwork. We had different CDNs handling different parts of the platform, an image proxying service for dynamic resizing, and raw assets sitting in cloud storage (like AWS S3). &lt;/p&gt;

&lt;p&gt;When a user uploads a 10MB JPEG as an article cover, our Rails app doesn't pre-process it into dozens of different dimensions. Instead, we rely on on-the-fly image transformation. In theory, this is great: the browser requests &lt;code&gt;image.jpg?width=800&lt;/code&gt;, and a dynamic image optimizer resizes it, converts it to WebP or AVIF, and serves it.&lt;/p&gt;

&lt;p&gt;In practice, the economics and mechanics of this setup at scale are brutal, especially when you factor in the realities of modern web traffic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Scraper &amp;amp; Traffic Tax:&lt;/strong&gt; On the open web, you aren't just serving human users. You are constantly being hit by RSS readers, search crawlers, and high-frequency scrapers. Traditional setups simply weren't smart enough to handle this gracefully. A wave of aggressive scrapers requesting un-cached variations or bypassing standard query parameters would force our pipeline to repeatedly re-fetch and re-process assets, sending computing costs into the stratosphere. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress Fees:&lt;/strong&gt; Every single time an image optimizer had to fetch a raw image from our cloud storage origin because of a cache miss (often induced by the traffic patterns mentioned above), we paid steep cloud egress fees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transformation Pricing:&lt;/strong&gt; Many premium image CDNs charge "per thousand images processed" or utilize complex, punitive usage tiers. When you have millions of active posts and users scrolling feeds with hundreds of custom-sized avatars, those transformation counts skyrocket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-CDN Friction:&lt;/strong&gt; Running separate providers for HTML caching and image delivery/optimization created massive operational overhead. We had complex header configurations, CORS issues, and routing rules scattered across different YAML files and dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our media bills were ballooning, it was incredibly expensive, and we were spending way too much time debugging why our pipeline wasn't smart enough to handle volatile traffic spikes smoothly. We needed a solution that was fast, reliable, highly configurable, and above all, economically sustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Hopped Over to bunny.net
&lt;/h2&gt;

&lt;p&gt;I’ve actually been using bunny.net for years across many of my personal projects. Whether spinning up a quick side application or testing out a new concept, I always found myself returning to it because it is an incredibly well-designed platform with sensible, intuitive products. It lacks the dense, enterprise bloat of traditional cloud vendors; instead, it provides clean developer ergonomics that just work. Because of that first-hand experience, I knew it was a platform we could trust to scale seamlessly with DEV.&lt;/p&gt;

&lt;p&gt;What really sold us for Forem wasn't just the raw bandwidth savings (though slashing our bandwidth bills to a fraction of what premium enterprise CDNs charge was a massive win). It was how beautifully their product ecosystem handled our specific architectural pain points through the combination of &lt;strong&gt;&lt;a href="https://docs.bunny.net/optimizer&amp;amp;sa=D&amp;amp;source=docs&amp;amp;ust=1781024983671285&amp;amp;usg=AOvVaw2Y7n05XU3d31p19gJMiySy" rel="noopener noreferrer"&gt;Bunny Optimizer&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://docs.bunny.net/scripting&amp;amp;sa=D&amp;amp;source=docs&amp;amp;ust=1781024983671402&amp;amp;usg=AOvVaw3kuoHo97Y2pAuGGaSS0qnR" rel="noopener noreferrer"&gt;Edge Scripting&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Bunny Optimizer and Perma-Cache
&lt;/h3&gt;

&lt;p&gt;Bunny Optimizer acts as a fully managed, dynamic image transformation API. We could easily plug it in, append simple URL query parameters (like &lt;code&gt;?width=600&amp;amp;height=300&amp;amp;crop=1:1&lt;/code&gt;), and let Optimizer handle the resizing, cropping, and automatic compression on-the-fly. It automatically negotiates next-gen formats like WebP or AVIF based on the browser's &lt;code&gt;Accept&lt;/code&gt; headers, reducing file sizes by up to 80% without any visible quality loss.&lt;/p&gt;

&lt;p&gt;But the real magic ingredient—and our ultimate weapon against scraper traffic—is &lt;strong&gt;Perma-Cache&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Normally, when a CDN edge server evicts an infrequently accessed image variant, the next request has to go all the way back to the origin (our cloud storage) to fetch and re-optimize it, triggering more egress fees. Perma-Cache solves this by permanently replicating the optimized image variants to Bunny Storage. &lt;/p&gt;

&lt;p&gt;Once an image is processed once, it is stored at the edge forever. It never has to hit our AWS origin again, shielding our backend from erratic traffic and virtually eliminated our cloud storage egress fees overnight.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Edge Scripting: TypeScript-Native Control
&lt;/h3&gt;

&lt;p&gt;While Bunny Optimizer gave us the raw power to resize images, we needed fine-grained control over how we served them. We didn't want to pollute our Rails views with complicated URL-building logic, and we wanted to prevent users (or bots) from downloading massive raw images.&lt;/p&gt;

&lt;p&gt;This is where Edge Scripting came in. &lt;/p&gt;

&lt;p&gt;Built on Deno and V8, Edge Scripting runs JavaScript and TypeScript directly at the edge, allowing us to write lightweight, type-safe middleware that executes in milliseconds. It completely replaced the need for custom image proxies or complex Rails controller routing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Under the Hood: Forem’s Pluggable Images::Optimizer Service
&lt;/h2&gt;

&lt;p&gt;If you look into Forem's codebase, you’ll see that we’ve always designed our image pipeline to be pluggable. We didn't want to hardcode our views to use a specific CDN's query parameters. If a template wants to render a post cover image, it calls a unified helper that delegates to our &lt;code&gt;Images::Optimizer&lt;/code&gt; service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;# app/views/layouts/application.html.erb
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;Images&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;General&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;favicon_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;app/services/images/optimizer.rb&lt;/code&gt;, we use a simple strategy pattern. The &lt;code&gt;Optimizer&lt;/code&gt; class acts as a router that maps standardized parameters (like &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;fit&lt;/code&gt;, &lt;code&gt;gravity&lt;/code&gt;) to the specific URL format required by the active CDN provider. &lt;/p&gt;

&lt;p&gt;Historically, Forem has supported providers like Fastly, Cloudflare, and Cloudinary and adding bunny.net was incredibly straightforward. Here is a look at how our Rails service handles this multi-CDN routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/images/optimizer.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Images&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Optimizer&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;

      &lt;span class="c1"&gt;# Select the provider strategy based on our environment configuration&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:bunny&lt;/span&gt;
        &lt;span class="no"&gt;BunnyProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:cloudflare&lt;/span&gt;
        &lt;span class="no"&gt;CloudflareProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:fastly&lt;/span&gt;
        &lt;span class="no"&gt;FastlyProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt;
      &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"IMAGE_OPTIMIZATION_PROVIDER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bunny"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each provider implements its own URL rewriting strategy. For example, our bunny.net provider simply builds standard query strings that Bunny Optimizer parses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/images/bunny_provider.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Images&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BunnyProvider&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"width=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:width&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:width&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"height=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:height&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:height&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"crop=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:crop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:crop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"auto=format"&lt;/span&gt;

      &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uri&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="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This decoupled architecture is fantastic for an open-source project like Forem. Different self-hosted communities can configure their own CDN of choice by simply changing the &lt;code&gt;IMAGE_OPTIMIZATION_PROVIDER&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;However, while the Rails app generates these optimized URLs, we ran into an interesting operational challenge: what happens when a template forgets to pass a width parameter, or a legacy post contains raw external URLs? &lt;/p&gt;

&lt;p&gt;If we relied only on Rails-side URL building, any fallback or unparsed URL would still trigger a heavy, unoptimized image load. This is where our Edge Scripting strategy stepped in—to act as a global safety net right at the network boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Highlight: Smart Downsizing at the Edge
&lt;/h2&gt;

&lt;p&gt;With Edge Scripting, we can intercept image requests right at the CDN layer and apply custom business logic before the request even reaches the optimizer or storage. &lt;/p&gt;

&lt;p&gt;For example, we wanted to ensure that user profile avatars and feed thumbnails are never served larger than they actually need to be, regardless of what the original upload was or what query parameters were requested. If a client requests a raw, un-optimized avatar URL, our edge script automatically intercepts it, checks the context, and rewrites the request to enforce a strict maximum width and apply WebP compression.&lt;/p&gt;

&lt;p&gt;Here's a simplified example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A lightweight middleware script running on bunny.net's Edge&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;handleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Intercept requests to our user-uploaded uploads path&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/uploads/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAvatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/avatars/&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;isThumbnail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/thumbnails/&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 the request already has optimization parameters&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAvatar&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Dedicatedly downsize all avatar requests to a max of 150px&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;width&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;150&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;height&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;150&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crop&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;1:1&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isThumbnail&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Enforce a strict mobile-friendly limit on thumbnails&lt;/span&gt;
      &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;width&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;400&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;// Ensure automatic next-gen format negotiation (WebP/AVIF) is active&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch the optimized asset from bunny.net's CDN pipeline&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is incredibly powerful because it offloads the "computational thinking" of image delivery entirely to the edge. Our Rails application doesn't have to keep track of responsive image breakpoints or generate heavy, complex markup. We just request the logical asset URL, and our edge script dynamically handles the rest based on client headers and context.&lt;/p&gt;

&lt;p&gt;Even better, we’ve integrated this into our standard development workflows. We use GitHub Actions combined with fine-grained personal access tokens to manage and deploy these edge scripts automatically. When we want to adjust our optimization rules or add support for a new layout—like optimizing billboard images or adjusting resolutions on challenge pages—we just push a commit, our CI runs, and the new edge logic is live globally in seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Graceful Failures: Smart Fallbacks Over Plain Broken Images
&lt;/h2&gt;

&lt;p&gt;Beyond routing and sizing, running code at the edge unlocked a massive UX victory: the ability to handle missing or broken assets gracefully. &lt;/p&gt;

&lt;p&gt;In a massive community ecosystem, edge cases happen. A user might delete an external image they linked to, an old upload path might break during a migration, or a malformed request could slide through. Traditionally, when an image fails to load or returns a 404/500, the browser drops a jarring, ugly "broken image" icon that disrupts the layout and makes the entire site look broken.&lt;/p&gt;

&lt;p&gt;With Edge Scripting, we can catch these failures mid-flight. If our origin or storage returns an error status code, the edge script intercepts the response and seamlessly rewrites it to serve a beautifully styled, custom placeholder image that says &lt;em&gt;"Image not available"&lt;/em&gt; or matches our UI theme. &lt;/p&gt;

&lt;p&gt;Instead of writing complex, heavy JavaScript event listeners (&lt;code&gt;onError&lt;/code&gt;) into every single &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag across the Rails application, we handle it natively at the network layer. The application layer never has to think about fallback logic, and our users get a consistent, unbroken visual experience no matter what happens behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future considerations
&lt;/h2&gt;

&lt;p&gt;Stabilizing and optimizing our image pipeline was just phase one. As we look ahead to how DEV and Forem will continue to evolve, video is the next logical horizon. &lt;/p&gt;

&lt;p&gt;Video delivery is notoriously complex—requiring adaptive bitrate streaming (HLS/DASH), multi-resolution transcoding, specialized storage, and optimized video players. In legacy architectures, this usually means spinning up another fragmented set of expensive third-party video processors and complex integrations.&lt;/p&gt;

&lt;p&gt;Given our success with their media infrastructure, bunny.net is our definitive first choice for how we are thinking about video moving forward. Their unified platform approach extends directly into video streaming with products that match the same sensible, developer-first philosophy as their image optimizer. Because we can trust their infrastructure to scale predictably without predatory data transfer costs, expanding our edge architecture to handle next-generation rich media feels like a natural progression rather than a daunting infrastructure overhaul.&lt;/p&gt;




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

&lt;p&gt;As developers, we often focus on optimizing our database queries, refactoring Ruby code, or fine-tuning our server configurations. But sometimes, the biggest wins are sitting right there in your network tab. &lt;/p&gt;

&lt;p&gt;Egress fees and bloated media delivery are a silent tax on growing platforms. By moving to an edge-native, developer-friendly platform like bunny.net, we were able to simplify our architecture, speed up our page loads, and save a lot of money in the process. &lt;/p&gt;

&lt;p&gt;If you’re running a media-heavy platform or building open-source community software like Forem, do yourself a favor: look closely at your CDN bills, check your cloud storage egress, and see if you can offload some of that weight to a platform built to grow with you. Your budget (and your users) will thank you.&lt;/p&gt;

&lt;p&gt;Happy coding ❤️&lt;/p&gt;

</description>
      <category>performance</category>
      <category>webdev</category>
      <category>design</category>
      <category>bunny</category>
    </item>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 15 Jun 2026 12:10:18 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-14pc</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-14pc</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-1m9f"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 08 Jun 2026 12:39:59 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-1m9f</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-1m9f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-25la"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Every tool seems to have a coding agent horned in these days..... I don't think that makes sense.</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Wed, 03 Jun 2026 13:46:51 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/every-tool-seems-to-have-a-coding-agent-horned-in-these-days-i-dont-think-that-makes-sense-3db</link>
      <guid>https://dev.clauneck.workers.dev/ben/every-tool-seems-to-have-a-coding-agent-horned-in-these-days-i-dont-think-that-makes-sense-3db</guid>
      <description></description>
      <category>agents</category>
      <category>ai</category>
      <category>discuss</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:28:02 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-25la</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-25la</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-194f"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>This is great, taking an oldie-but-goodie and rethinking it</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Wed, 27 May 2026 16:54:47 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/this-is-great-taking-an-oldie-but-goodie-and-rethinking-it-4cgl</link>
      <guid>https://dev.clauneck.workers.dev/ben/this-is-great-taking-an-oldie-but-goodie-and-rethinking-it-4cgl</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf" class="crayons-story__hidden-navigation-link"&gt;Reviving a 12K+ Star Abandoned Library: toastr-next v3 🍞&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;GitHub “Finish-Up-A-Thon” Challenge Submission&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/divyesh5981" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1348271%2F3ca8b508-9d30-4c3c-919f-084e4d0a26ec.jpeg" alt="divyesh5981 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/divyesh5981" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Divyesh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Divyesh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3761174" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/divyesh5981" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1348271%2F3ca8b508-9d30-4c3c-919f-084e4d0a26ec.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Divyesh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf" id="article-link-3761174"&gt;
          Reviving a 12K+ Star Abandoned Library: toastr-next v3 🍞
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/githubchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;githubchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;47&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.clauneck.workers.dev/divyesh5981/reviving-a-12k-star-abandoned-library-toastr-next-v3-25mf#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              33&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 25 May 2026 13:40:05 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-194f</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-194f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-47g6"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>jokes</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>How we're using Gemini Embeddings to build a smarter, community-driven feed on DEV</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Fri, 22 May 2026 16:27:19 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/devteam/how-were-using-gemini-embeddings-to-build-a-smarter-community-driven-feed-on-dev-1b9f</link>
      <guid>https://dev.clauneck.workers.dev/devteam/how-were-using-gemini-embeddings-to-build-a-smarter-community-driven-feed-on-dev-1b9f</guid>
      <description>&lt;p&gt;Big improvements incoming 👋&lt;/p&gt;

&lt;p&gt;Finding the right balance for a feed algorithm is historically really hard. If you optimize purely for clicks and comments, you end up with a clickbait echo chamber. But if you just sort by recency, it's a firehose where great discussions disappear in hours. &lt;/p&gt;

&lt;p&gt;We've wrestled with this tension at DEV for a long time. We want a feed that feels alive, but actually surfaces high-quality, intellectually stimulating stuff.&lt;/p&gt;

&lt;p&gt;So, we're trying something new. We are combining standard community signals—like who you follow and what you react to—with &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/embedding-2" rel="noopener noreferrer"&gt;Gemini Embeddings 2&lt;/a&gt; and &lt;code&gt;pgvector&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here is a look under the hood at how we are putting this together.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Keeping things flexible and auditable
&lt;/h2&gt;

&lt;p&gt;Instead of duct-taping API calls all over the codebase, we built a flexible foundation using wrapper classes, mostly centered around &lt;code&gt;Ai::Base&lt;/code&gt; and &lt;code&gt;Ai::Embedding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a service needs the API, it just passes &lt;code&gt;wrapper: self&lt;/code&gt; to the client. This lets &lt;code&gt;Ai::Base&lt;/code&gt; look at the calling object, grab its class name, and check its &lt;code&gt;VERSION&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Ai&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;wrapper: &lt;/span&gt;&lt;span class="nb"&gt;self&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 gives us a really clean audit trail via our &lt;code&gt;AiAudit&lt;/code&gt; model. Every single time we generate a vector or analyze a trend, we automatically log the model used, the caller's class, payloads, latency, and token counts. &lt;/p&gt;

&lt;p&gt;It makes debugging and tracking costs so much easier, without muddying up our core business logic.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. A more personalized feed
&lt;/h2&gt;

&lt;p&gt;Our main feed is powered by &lt;code&gt;FeedConfig&lt;/code&gt;. It compiles custom SQL to score and rank articles for you.&lt;/p&gt;

&lt;p&gt;Historically, this was all hardcoded math based on things like tags and whether you follow the author. Now, we've introduced a semantic feedback loop. &lt;/p&gt;

&lt;p&gt;As you interact with the platform, we compile a dynamic &lt;code&gt;interest_embedding&lt;/code&gt; that represents what you actually care about. We use the &lt;code&gt;pgvector&lt;/code&gt; extension in PostgreSQL to inject your interests directly into the SQL query:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt;
    &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semantic_embedding&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;published_since&lt;/span&gt;
    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semantic_embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;interest_embedding&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="n"&gt;semantic_similarity_weight&lt;/span&gt;
    &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By using &lt;code&gt;1 - (embedding &amp;lt;=&amp;gt; user_interest)&lt;/code&gt;, we get a cosine similarity score. We scale that up and mix it in with standard social signals (like who you follow), post quality, and time decay.&lt;/p&gt;

&lt;p&gt;This means a highly relevant post can rise to the top of your feed, but so can a globally trending post from a community member you love. It’s all about balance.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. What the heck is an embedding anyway? (And why v2 matters)
&lt;/h2&gt;

&lt;p&gt;If you're new to the concept, an embedding is basically taking a piece of content—like an article text—and turning it into a long string of numbers (a vector). These numbers map the content into a "semantic space." If two posts are talking about the exact same conceptual ideas, their numbers will look very similar mathematically, even if they use completely different wording.&lt;/p&gt;

&lt;p&gt;We've upgraded this pipeline to use Google's newly released &lt;strong&gt;Gemini Embeddings 2&lt;/strong&gt; model. &lt;/p&gt;

&lt;p&gt;A standard text embedding model only looks at words. But Gemini Embeddings 2 compiles into massive 3,072-dimensional vectors and maps everything into a single, unified semantic space. &lt;/p&gt;
&lt;h3&gt;
  
  
  Future-proofing for a multi-modal DEV
&lt;/h3&gt;

&lt;p&gt;The coolest part about moving to Embeddings 2 is that it isn't just restricted to text. It natively accepts multimodal inputs—meaning text, code, images, audio, and video. &lt;/p&gt;

&lt;p&gt;Right now, we're using it to analyze written DEV posts. But because the underlying math maps everything into the exact same vector space, we are completely future-proofing our infrastructure. As the DEV platform evolves, we can easily feed images, podcast audio, or video posts into the exact same database architecture[. &lt;/p&gt;

&lt;p&gt;A user's &lt;code&gt;interest_embedding&lt;/code&gt; will be able to effortlessly surface an open-source video tutorial or a technical podcast episode based entirely on conceptual relevance, without us needing to rewrite our feed logic from scratch.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Catching nuanced trends 📈
&lt;/h2&gt;

&lt;p&gt;Tags are great for high-level sorting, but they miss the highly specific, timely conversations. If Ruby 3.4 drops, a &lt;code&gt;#ruby&lt;/code&gt; tag search won't distinguish between a "Hello World" tutorial and a deep debate about the new parser.&lt;/p&gt;

&lt;p&gt;To fix this, we are in the process of building a clustering service powered by &lt;code&gt;TrendDetector&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every 6 hours, a background job runs a Leader Clustering algorithm in pure Ruby:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Quality first:&lt;/strong&gt; We only look at recent articles scoring at least 15 points above our homepage minimum.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clustering:&lt;/strong&gt; We measure the cosine distance between articles. If a post is close enough (&lt;code&gt;0.15&lt;/code&gt; or less) to an existing cluster, it joins it. If not, it starts a new one.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Labeling:&lt;/strong&gt; Once a cluster hits 10 or more articles, we ask the Gemini API to label the trend and summarize the core debate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We store all of this in &lt;code&gt;TrendMembership&lt;/code&gt;, which lets us sort articles in the UI based on how close they are to the core topic.&lt;/p&gt;

&lt;p&gt;All of this can be tracked via our open source codebase Forem:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/forem" rel="noopener noreferrer"&gt;
        forem
      &lt;/a&gt; / &lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;
        forem
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      For empowering community 🌱
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;br&gt;
  &lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Forem 🌱&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;For Empowering Community&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;

&lt;p&gt;
  &lt;a href="https://github.com/forem/forem/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;
    &lt;img src="https://github.com/forem/forem/actions/workflows/ci.yml/badge.svg" alt="Build Status"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/forem/forem/actions/workflows/cd.yml" rel="noopener noreferrer"&gt;
    &lt;img src="https://github.com/forem/forem/actions/workflows/cd.yml/badge.svg" alt="Build Status"&gt;
  &lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/8ff45d4371c6a841fba51a46b5839394d54073200ca437804873b8ad9dda0223/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f772f666f72656d2f666f72656d"&gt;&lt;img src="https://camo.githubusercontent.com/8ff45d4371c6a841fba51a46b5839394d54073200ca437804873b8ad9dda0223/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6d6d69742d61637469766974792f772f666f72656d2f666f72656d" alt="GitHub commit activity"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/forem/forem/issues?q=is%3Aissue+is%3Aopen+label%3A%22ready+for+dev%22" rel="noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/1aceedd769bf7ab14522fe26fda6c0f714b7742980e78da6546df416e3c560d9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f666f72656d2f666f72656d2f7265616479253230666f72253230646576" alt="GitHub issues ready for dev"&gt;
  &lt;/a&gt;
  &lt;a href="https://gitpod.io/#https://github.com/forem/forem" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/fa445e63ae38a91a2c8f8bc1cd6842f48216c4a57a114b07484225f3bdc7528c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73657475702d6175746f6d617465642d626c75653f6c6f676f3d676974706f64" alt="GitPod badge"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Welcome to the &lt;a href="https://forem.com" rel="nofollow noopener noreferrer"&gt;Forem&lt;/a&gt; codebase, the platform that powers
&lt;a href="https://dev.clauneck.workers.dev" rel="nofollow"&gt;dev.to&lt;/a&gt;. We are so excited to have you. With your help, we can
build out Forem’s usability, scalability, and stability to better serve our
communities.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is Forem?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Forem is open source software for building communities. Communities for your
peers, customers, fanbases, families, friends, and any other time and space
where people need to come together to be part of a collective
&lt;a href="https://dev.clauneck.workers.dev/devteam/for-empowering-community-2k6h" rel="nofollow"&gt;See our announcement post&lt;/a&gt;
for a high-level overview of what Forem is.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.clauneck.workers.dev" rel="nofollow"&gt;dev.to&lt;/a&gt; (or just DEV) is hosted by Forem. It is a community of
software developers who write articles, take part in discussions, and build
their professional profiles. We value supportive and constructive dialogue in
the pursuit of great code and career growth for all members. The ecosystem spans
from beginner to advanced developers, and all are welcome to find their place…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  5. Putting the community first ❤️
&lt;/h2&gt;

&lt;p&gt;Human curation, both from the broader community and our editorial perspective, is still the backbone of the system. &lt;/p&gt;

&lt;p&gt;We are using Gemini Embeddings to amplify what the community is already doing. It’s about mixing the raw utility of vector search with the human spirit of developer-voted scores and relationships. &lt;/p&gt;

&lt;p&gt;We want DEV to be the best place on the internet to share code and talk about software. We think this is a big step in that direction.&lt;/p&gt;

&lt;p&gt;What do you think? Let me know in the comments.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>gemini</category>
      <category>ai</category>
      <category>googlecloud</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Pretty normal</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Thu, 21 May 2026 14:30:16 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/pretty-normal-1893</link>
      <guid>https://dev.clauneck.workers.dev/ben/pretty-normal-1893</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18" class="crayons-story__hidden-navigation-link"&gt;I Used to Get Excited About New Tools Now I Feel Tired.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Staying updated as unpaid homework&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/harsh2644" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3735796%2Fc3352182-725d-4924-b6df-a727a41d542a.jpeg" alt="harsh2644 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/harsh2644" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Harsh 
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Harsh 
                
              
              &lt;div id="story-author-preview-content-3716518" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/harsh2644" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3735796%2Fc3352182-725d-4924-b6df-a727a41d542a.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Harsh &lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18" id="article-link-3716518"&gt;
          I Used to Get Excited About New Tools Now I Feel Tired.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mentalhealth"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mentalhealth&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;78&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.clauneck.workers.dev/harsh2644/i-used-to-get-excited-about-new-tools-now-i-feel-tired-1e18#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              70&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>career</category>
      <category>devjournal</category>
      <category>discuss</category>
      <category>mentalhealth</category>
    </item>
    <item>
      <title>Definitely looking forward to using Antigravity 2.0</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Tue, 19 May 2026 17:27:33 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/definitely-looking-forward-to-using-antigravity-20-6ob</link>
      <guid>https://dev.clauneck.workers.dev/ben/definitely-looking-forward-to-using-antigravity-20-6ob</guid>
      <description></description>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>tooling</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Meme Monday</title>
      <dc:creator>Ben Halpern</dc:creator>
      <pubDate>Mon, 18 May 2026 12:47:35 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/ben/meme-monday-47g6</link>
      <guid>https://dev.clauneck.workers.dev/ben/meme-monday-47g6</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meme Monday!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today's cover image comes from &lt;a href="https://dev.clauneck.workers.dev/ben/meme-monday-55e2"&gt;the last thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;DEV is an inclusive space! Humor in poor taste will be downvoted by mods.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
