<?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: Abiodun Paul Ogunnaike</title>
    <description>The latest articles on DEV Community by Abiodun Paul Ogunnaike (@abbeymaniak).</description>
    <link>https://dev.to/abbeymaniak</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%2F259401%2F4fdee10f-2e85-452b-acf9-ccf6ed7f8ec3.jpeg</url>
      <title>DEV Community: Abiodun Paul Ogunnaike</title>
      <link>https://dev.to/abbeymaniak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abbeymaniak"/>
    <language>en</language>
    <item>
      <title>WordPress Performance Part 2: Crushing LCP &amp; Mastering Image Optimization for Core Web Vitals</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Mon, 12 Oct 2026 10:24:47 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/wordpress-performance-part-2-crushing-lcp-mastering-image-optimization-for-core-web-vitals-2o6d</link>
      <guid>https://dev.to/abbeymaniak/wordpress-performance-part-2-crushing-lcp-mastering-image-optimization-for-core-web-vitals-2o6d</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/abbeymaniak/a-practical-walkthrough-of-real-world-wordpress-performance-optimizations-46fm"&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/a&gt;, we walked through trimming megabytes of script bloat from &lt;strong&gt;your-website&lt;/strong&gt;: stripping Gutenberg editor dependencies from public blocks, banishing &lt;code&gt;@import&lt;/code&gt; font waterfalls, and deferring jQuery. Those architectural fixes slashed Total Blocking Time (TBT) and eliminated render-blocking delays.&lt;/p&gt;

&lt;p&gt;However, resolving script bloat is only half the battle. When auditing mobile Core Web Vitals, the most visible metric remains &lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In our Lighthouse audit, mobile LCP clocked in at an agonizing &lt;strong&gt;6.8 seconds&lt;/strong&gt;, accompanied by a stark recommendation: &lt;strong&gt;"Improve image delivery: estimated savings of 1,718 KiB."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, we'll dive into the exact steps we implemented to accelerate hero image delivery, eliminate bandwidth competition, and bring LCP firmly into the green (&amp;lt;2.5s).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Images Destroy Mobile Core Web Vitals
&lt;/h2&gt;

&lt;p&gt;Images frequently account for &lt;strong&gt;60% to 80%&lt;/strong&gt; of a webpage’s total weight. On mobile devices with CPU and network throttling, poor image delivery triggers severe performance bottlenecks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Delayed LCP Discovery:&lt;/strong&gt; When a hero banner image is loaded via JavaScript or dynamic sliders, the browser doesn't discover the image URL until after CSS and scripts have executed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bandwidth Cannibalization:&lt;/strong&gt; Slider blocks that eagerly download all slides simultaneously force the browser to divide network bandwidth among hidden slides instead of prioritizing the visible hero image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Lazy Loading:&lt;/strong&gt; Below-the-fold cards (event feeds, blog teasers, partner logos) loading eagerly at page initialization clog the network queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oversized Assets:&lt;/strong&gt; Delivering a 2500×1667 desktop image to a 400px mobile viewport wastes hundreds of kilobytes per image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the engineering strategy we used to fix these bottlenecks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Prioritize the Hero Banner with &lt;code&gt;fetchpriority="high"&lt;/code&gt; &amp;amp; &lt;code&gt;loading="eager"&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;On modern websites, the hero banner is almost always the &lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt; element. By default, browsers treat all images with equal priority, queueing them behind other resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Carousel / Slider Cannibalization
&lt;/h3&gt;

&lt;p&gt;In many custom slider components or carousel blocks, images are generated in a loop without differentiating the active first slide from offscreen slides:&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;// 🛑 Bad: All slides loaded with default priority&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slidesHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;div class="home-banner-slide"&amp;gt;
    &amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" alt="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" class="home-banner-image" /&amp;gt;
  &amp;lt;/div&amp;gt;
`&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes the browser to download Slide 2, Slide 3, and Slide 4 concurrently with Slide 1, delaying the rendering of the only slide the visitor actually sees.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Smart Slide Attribute Splitting
&lt;/h3&gt;

&lt;p&gt;Configure your slider component to treat Slide 1 as a critical asset, while deferring all subsequent slides:&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;// ✅ Good: First slide prioritized, remaining slides deferred&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slidesHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;div class="home-banner-slide"&amp;gt;
    &amp;lt;img 
      src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
      alt="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
      class="home-banner-image" 
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetchpriority="high" loading="eager"&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;loading="lazy" decoding="async"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 
    /&amp;gt;
  &amp;lt;/div&amp;gt;
`&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fetchpriority="high"&lt;/code&gt;&lt;/strong&gt;: Informs the browser's preload scanner that this image has maximum network priority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;loading="eager"&lt;/code&gt;&lt;/strong&gt;: Instructs the browser not to delay fetching the image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;loading="lazy"&lt;/code&gt; &amp;amp; &lt;code&gt;decoding="async"&lt;/code&gt;&lt;/strong&gt;: Ensures offscreen slides don't consume a single byte of bandwidth until the user starts navigating the carousel.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Preload the LCP Hero Image in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Even with &lt;code&gt;fetchpriority="high"&lt;/code&gt;, if your hero banner is rendered inside a Web Component or dynamic block, the browser must parse the HTML body before discovering the image URL.&lt;/p&gt;

&lt;p&gt;To achieve lightning-fast LCP, the browser should start downloading the hero banner &lt;strong&gt;the instant the initial HTML document response arrives&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Server-Side Preload Hint
&lt;/h3&gt;

&lt;p&gt;In your theme's &lt;code&gt;functions.php&lt;/code&gt;, extract the hero image URL from the page's block attributes and emit a &lt;code&gt;&amp;lt;link rel="preload"&amp;gt;&lt;/code&gt; tag directly in &lt;code&gt;wp_head&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_head'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Only target the front page or home page&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;is_admin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_front_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;is_home&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$post&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="nv"&gt;$post&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="n"&gt;WP_Post&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;has_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-website/home-banner-block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_blocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$blocks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'blockName'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'your-website/home-banner-block'&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'attrs'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'slides'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'imageSrc'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nv"&gt;$hero_img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'attrs'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'slides'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'imageSrc'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
                    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;link rel="preload" as="image" href="'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$hero_img&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'" fetchpriority="high"&amp;gt;'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; This moves hero image discovery from step 15 in the browser waterfall directly to step 1. The image begins downloading before external stylesheets or JavaScript bundles are even evaluated.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: Enforce Native Lazy Loading Across All Below-the-Fold Blocks
&lt;/h2&gt;

&lt;p&gt;A typical homepage contains multiple dynamic sections below the fold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upcoming Events&lt;/li&gt;
&lt;li&gt;Community News&lt;/li&gt;
&lt;li&gt;Impact Stories&lt;/li&gt;
&lt;li&gt;Partner Logos &amp;amp; Grids&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these cards render standard &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags without explicit loading attributes, the browser queues up dozens of image requests during initial page load, choking the main thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Audit Dynamic Block Templates
&lt;/h3&gt;

&lt;p&gt;Audit your theme's PHP block render functions and add native &lt;code&gt;loading="lazy"&lt;/code&gt; and &lt;code&gt;decoding="async"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- In your custom card or event block template --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card__img-wrapper"&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;class=&lt;/span&gt;&lt;span class="s"&gt;"card__img"&lt;/span&gt; 
        &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; 
        &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;esc_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; 
        &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; 
        &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global WordPress Attachment Filter
&lt;/h3&gt;

&lt;p&gt;To ensure that all standard WordPress media library images rendered via core functions also benefit from non-blocking decoding, add this hook in &lt;code&gt;functions.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ensure non-blocking image decoding across all attachment images&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_get_attachment_image_attributes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'decoding'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$attr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'decoding'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'async'&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="nv"&gt;$attr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;decoding="async"&lt;/code&gt;&lt;/strong&gt;: Allows the browser to decode image raster data off the main thread, preventing UI jank and reducing Total Blocking Time during scrolling.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Responsive Image Sizing &amp;amp; Layout Stability (CLS)
&lt;/h2&gt;

&lt;p&gt;Lighthouse often flags: &lt;strong&gt;"Image file is larger than it needs to be for its displayed dimensions."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serving a 2000px wide image into a 300px mobile card wastes data and forces mobile CPUs to perform expensive client-side image downscaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Leverage WordPress Responsive &lt;code&gt;srcset&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of using raw image URLs with &lt;code&gt;'full'&lt;/code&gt;, always register and request appropriate WordPress image dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In functions.php&lt;/span&gt;
&lt;span class="nf"&gt;add_image_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'card-thumbnail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;add_image_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hero-banner-mobile'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In your template: use wp_get_attachment_image for automatic srcset &amp;amp; sizes&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;wp_get_attachment_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$thumbnail_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'card-thumbnail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'class'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'card__img'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'loading'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'lazy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Prevent Cumulative Layout Shift (CLS)
&lt;/h3&gt;

&lt;p&gt;When images lack explicit dimensions or aspect ratios, the browser cannot reserve space for them before they load, causing jarring layout shifts as images pop into view.&lt;/p&gt;

&lt;p&gt;Add modern CSS aspect ratio rules to your image containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.card__img-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f0f0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Low-contrast placeholder */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.card__img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By defining &lt;code&gt;aspect-ratio&lt;/code&gt;, the browser reserves exact bounding boxes before the image binary arrives, locking CLS at &lt;strong&gt;0.000&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Adopt Next-Gen Image Formats (WebP &amp;amp; AVIF)
&lt;/h2&gt;

&lt;p&gt;Traditional formats like PNG and JPEG are inefficient for web delivery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebP:&lt;/strong&gt; Offers &lt;strong&gt;25%–35%&lt;/strong&gt; smaller file sizes than JPEG with identical visual quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AVIF:&lt;/strong&gt; Provides up to &lt;strong&gt;50%&lt;/strong&gt; compression savings compared to JPEG and supports superior color depth and high dynamic range.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WordPress Native Modern Format Support
&lt;/h3&gt;

&lt;p&gt;Starting in WordPress 5.8 (WebP) and WordPress 6.5 (AVIF), WordPress natively supports next-gen image uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In functions.php: Enable AVIF and WebP generation on upload (WP 6.5+)&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'image_editor_output_format'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$formats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$formats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image/jpeg'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'image/webp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$formats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image/png'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'image/webp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$formats&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;For existing media libraries, automated image optimization or server-level rewrites can convert your legacy library to WebP/AVIF without changing media URLs in your database.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Combined Impact of Phase 1 + Phase 2
&lt;/h2&gt;

&lt;p&gt;Combining &lt;strong&gt;Phase 1 (Script &amp;amp; Cache Optimization)&lt;/strong&gt; and &lt;strong&gt;Phase 2 (Image &amp;amp; LCP Acceleration)&lt;/strong&gt; delivers a complete performance transformation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before Optimization&lt;/th&gt;
&lt;th&gt;After Phase 1 &amp;amp; Phase 2&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile Score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85–95+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟢 Massive Jump&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Blocking Time (TBT)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9,660 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt; 200 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Instant Main Thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.8 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt; 1.8 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🚀 73% Faster Rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Page Weight&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4.2 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1.2 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;📉 Over 65% Bandwidth Saved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cumulative Layout Shift (CLS)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.001&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🎯 Rock-Solid Layout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary Checklist for Your WordPress Project
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] Preload the LCP hero banner in &lt;code&gt;wp_head&lt;/code&gt; with &lt;code&gt;fetchpriority="high"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[x] Set &lt;code&gt;fetchpriority="high"&lt;/code&gt; and &lt;code&gt;loading="eager"&lt;/code&gt; on the first carousel slide; set &lt;code&gt;loading="lazy"&lt;/code&gt; on all remaining slides.&lt;/li&gt;
&lt;li&gt;[x] Add &lt;code&gt;loading="lazy"&lt;/code&gt; and &lt;code&gt;decoding="async"&lt;/code&gt; across all below-the-fold custom blocks.&lt;/li&gt;
&lt;li&gt;[x] Define &lt;code&gt;aspect-ratio&lt;/code&gt; on all image containers to permanently prevent layout shifts.&lt;/li&gt;
&lt;li&gt;[x] Serve responsive &lt;code&gt;srcset&lt;/code&gt; sizes and leverage modern WebP/AVIF formats.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebVitals #WebDev #FrontendDevelopment #PageSpeed&lt;/p&gt;

&lt;p&gt;PART 1 - &lt;a href="https://dev.to/abbeymaniak/a-practical-walkthrough-of-real-world-wordpress-performance-optimizations-46fm"&gt;https://dev.to/abbeymaniak/a-practical-walkthrough-of-real-world-wordpress-performance-optimizations-46fm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>corewebvitals</category>
      <category>wordpress</category>
      <category>webperf</category>
      <category>performance</category>
    </item>
    <item>
      <title>How I Built an Autonomous AI Refund Decision Engine with FastAPI, React, and LiteLLM</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Mon, 05 Oct 2026 15:55:38 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/how-i-built-an-autonomous-ai-refund-decision-engine-with-fastapi-react-and-litellm-2e0d</link>
      <guid>https://dev.to/abbeymaniak/how-i-built-an-autonomous-ai-refund-decision-engine-with-fastapi-react-and-litellm-2e0d</guid>
      <description>&lt;p&gt;Customer returns and refund requests are among the costliest operational friction points for modern e commerce platforms. Traditional solutions force businesses to choose between two undesirable extremes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rigid rule engines that deny valid edge cases, frustrating loyal shoppers.&lt;/li&gt;
&lt;li&gt;Naive generative chatbots that can be easily manipulated through prompt injection, promising free money and unauthorized chargebacks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve this, I designed and built an autonomous AI Customer Support Refund System. It balances instant automated decisions with deterministic policy checks, velocity safeguards, and a supervisor review loop.&lt;/p&gt;

&lt;p&gt;Here is a full breakdown of how the platform is architected and built.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. High Level Architecture and the Tracer Bullet Approach
&lt;/h2&gt;

&lt;p&gt;Instead of building individual layers in isolation, we followed the Tracer Bullet build strategy. Every development milestone implemented a thin, working slice through the database, backend services, AI decision layer, and user interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    +--------------------------------+
                    |   React 18 + TypeScript SPA    |
                    | (Tailwind, Lucide, TanStack)   |
                    +---------------+----------------+
                                    |
                    Session Cookies (HTTP Only, Lax)
                                    |
                                    v
                    +--------------------------------+
                    |       FastAPI REST Layer       |
                    |  (Clean Architecture Services) |
                    +---------------+----------------+
                                    |
            +-----------------------+-----------------------+
            |                                               |
            v                                               v
+-----------------------+                       +-----------------------+
|  PostgreSQL 16 Engine |                       |  LiteLLM Policy Core  |
|  (SQLAlchemy 2 Async) |                       | (Ollama, OpenAI, Gem) |
+-----------------------+                       +-----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Python 3.11 with FastAPI, Pydantic 2, SQLAlchemy 2 (asyncio with asyncpg), and LiteLLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL 16 with normalized relational models for customers, orders, order items, refund claims, LLM providers, and audit logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React 18, TypeScript, Vite, TanStack Query (React Query), and Tailwind CSS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt;: Fully containerized multi stage Docker Compose environment with health checks.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Deterministic Guardrails Meet Generative AI
&lt;/h2&gt;

&lt;p&gt;One of the biggest pitfalls when integrating large language models into fintech or support operations is unpredictability. An LLM should not make decisions in an unconstrained void.&lt;/p&gt;

&lt;p&gt;Our engine applies a layered decision pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Customer Claim Submission]
            |
            v
+-------------------------------+
|  1. Fast Path Validation      | -&amp;gt; Window checks (within 30 days)
|     (Pure Code &amp;amp; Database)    | -&amp;gt; Item condition and final sale tags
+---------------+---------------+ -&amp;gt; Velocity &amp;amp; sum limits (7 day caps)
                |
                v
+-------------------------------+
|  2. Risk Scoring &amp;amp; History    | -&amp;gt; Lifetime return rate calculations
|     (Customer Profile Metrics)| -&amp;gt; Fraud cluster anomaly detection
+---------------+---------------+
                |
                v
+-------------------------------+
|  3. Structured AI Reasoning   | -&amp;gt; Evaluates nuanced testimony
|     (LiteLLM Provider Engine) | -&amp;gt; Strict JSON output schema
+---------------+---------------+ -&amp;gt; Auto fallback (OpenAI to Ollama)
                |
                v
+-------------------------------+
|  4. Audit Trail &amp;amp; Disposition | -&amp;gt; Approved: Instant settlement
|     (Immutable Event History) | -&amp;gt; Escalated: Queued for supervisor
+-------------------------------+ -&amp;gt; Denied: Policy citation logged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Structured Policy Output
&lt;/h3&gt;

&lt;p&gt;The AI decision engine produces typed outputs enforcing structured Pydantic schemas:&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;class&lt;/span&gt; &lt;span class="nc"&gt;PolicyEvaluationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Denied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Escalated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;confidence_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;policy_citations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;detected_red_flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;matched_rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a prompt injection attack is attempted (such as ignoring return windows or commanding the agent to approve high value claims), the deterministic validation layer flags anomaly markers and immediately forces an Escalated state for human review.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Scoped Customer Portal and Real Time Claims History
&lt;/h2&gt;

&lt;p&gt;Customers interact with a dedicated three step refund wizard and history dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Order and Item Selection&lt;/strong&gt;: Displays verified purchases fetched strictly from session cookies. Items already claimed are disabled to prevent duplicate abuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reason and Testimony Input&lt;/strong&gt;: Captures the customer return justification with character boundary validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant Evaluation&lt;/strong&gt;: Invokes the engine and renders a transparent verdict card with confidence percentage and detailed policy reasoning.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------+
| AutoRefund Portal              Sarah Jenkins [Customer badge] |
+---------------------------------------------------------------+
|  [ File a Refund ]    [ My Claims (3) ]                       |
|---------------------------------------------------------------|
|  Total Claims: 3  |  Approved: 2  |  Under Review: 1          |
|---------------------------------------------------------------|
|  Search: [ Filter by item or claim number... ]  [All] [Appr]  |
|---------------------------------------------------------------|
|  Claim ID      Order      Date         Status      Action     |
|  REF-9021      ORD-104    Sep 26 2026  Approved   [Inspect]   |
|  REF-8842      ORD-089    Sep 20 2026  Escalated  [Inspect]   |
+---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Slide Over Inspection Drawer
&lt;/h3&gt;

&lt;p&gt;Clicking &lt;strong&gt;Inspect&lt;/strong&gt; on any claim row opens a slide over drawer containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete order and refund item breakdowns.&lt;/li&gt;
&lt;li&gt;The verbatim customer statement quote.&lt;/li&gt;
&lt;li&gt;The AI policy evaluation with natural language rationale and confidence meters.&lt;/li&gt;
&lt;li&gt;Supervisor override justification alerts when manual human intervention occurred.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Human in the Loop: The Supervisor Review Desk
&lt;/h2&gt;

&lt;p&gt;Autonomous systems must always provide an escape hatch for human judgment.&lt;/p&gt;

&lt;p&gt;When claims exceed velocity thresholds or involve high value items, the system places the request in an &lt;strong&gt;Escalated&lt;/strong&gt; queue on the Admin Dashboard. Human supervisors can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review customer historical risk scores and lifetime spend.&lt;/li&gt;
&lt;li&gt;Inspect the initial AI decision and model explanation.&lt;/li&gt;
&lt;li&gt;Apply a &lt;strong&gt;Supervisor Override&lt;/strong&gt; to approve or deny the claim, accompanied by mandatory written justification notes.&lt;/li&gt;
&lt;li&gt;Every override event is immutably recorded in the audit logs with the supervisor identifier and timestamp.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Testing and Verification Rigor
&lt;/h2&gt;

&lt;p&gt;A financial decision engine demands comprehensive automated testing. We built an automated test harness covering backend and frontend layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;137 Backend Pytest Tests&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Unauthenticated access rejection (HTTP 401).&lt;/li&gt;
&lt;li&gt;Session scoping and strict tenant isolation (confirming Customer A never sees Customer B data).&lt;/li&gt;
&lt;li&gt;Order ownership verification and duplicate claim blocks.&lt;/li&gt;
&lt;li&gt;LLM fallback mechanisms and structured JSON response parsing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;121 Frontend Vitest Tests&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Segmented tab switching and URL query parameter preservation.&lt;/li&gt;
&lt;li&gt;Client side search filtering and status pill filtering.&lt;/li&gt;
&lt;li&gt;Slide over inspection drawer focus trap, Escape key handling, and accessibility.&lt;/li&gt;
&lt;li&gt;Real time cache invalidation upon claim submission.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All tests execute inside Docker containers, ensuring complete parity between local development and production environments.&lt;/p&gt;




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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never let an LLM run without guardrails&lt;/strong&gt;: Generative models are fantastic at interpreting messy human explanations, but hard constraints (deadlines, return caps, duplicate detection) belong in deterministic code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi provider resilience is essential&lt;/strong&gt;: Using LiteLLM allowed us to seamlessly route requests between cloud providers like OpenAI and Gemini, falling back to local Ollama instances during outages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session scoped security beats client provided parameters&lt;/strong&gt;: Never trust the frontend to pass a customer identifier. Deriving user identity strictly from secure, HTTP only cookies eliminates impersonation attacks at the root.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check out the full repository and architecture specs on GitHub!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/abbeymaniak/ai-customer-support-refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
      <category>ai</category>
    </item>
    <item>
      <title>A practical walkthrough of real-world WordPress performance optimizations</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Thu, 24 Sep 2026 08:49:02 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/a-practical-walkthrough-of-real-world-wordpress-performance-optimizations-46fm</link>
      <guid>https://dev.to/abbeymaniak/a-practical-walkthrough-of-real-world-wordpress-performance-optimizations-46fm</guid>
      <description>&lt;p&gt;When running a Google PageSpeed Insights or Lighthouse audit on a custom WordPress website, seeing a low mobile / desktop score (such as 30–40) alongside warnings like &lt;strong&gt;"Eliminate render-blocking resources"&lt;/strong&gt;, &lt;strong&gt;"Reduce unused JavaScript"&lt;/strong&gt;, and &lt;strong&gt;"Serve static assets with an efficient cache policy"&lt;/strong&gt; is frustratingly common.&lt;/p&gt;

&lt;p&gt;Many developers assume the only remedy is installing another heavyweight caching plugin. However, true long-term performance gains come from fixing fundamental bottlenecks in the theme itself: eliminating script bloat, optimizing font delivery, utilizing modern script loading strategies, and conditionally loading assets.&lt;/p&gt;

&lt;p&gt;In this article, we'll walk through the exact steps we implemented on &lt;strong&gt;your-website&lt;/strong&gt; to trim megabytes of unnecessary scripts, eliminate render-blocking delays, and streamline WordPress asset delivery.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Initial Audit: What Went Wrong?
&lt;/h2&gt;

&lt;p&gt;Our initial Lighthouse speed test revealed several common anti-patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg Editor Dependencies Leaking to the Frontend:&lt;/strong&gt; Public-facing custom blocks were pulling in entire WordPress admin and React libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Busting via &lt;code&gt;time()&lt;/code&gt;:&lt;/strong&gt; Styles and scripts had dynamic timestamps, entirely defeating browser and CDN caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render-Blocking Web Fonts via CSS &lt;code&gt;@import&lt;/code&gt;:&lt;/strong&gt; Google Fonts were being pulled synchronously inside multiple stylesheets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous jQuery &amp;amp; &lt;code&gt;jquery-migrate&lt;/code&gt;:&lt;/strong&gt; Core jQuery was blocking the critical rendering path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redundant &amp;amp; Global Plugin Overhead:&lt;/strong&gt; Heavy plugin assets were loading globally across 100% of pages, even where completely unused.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is how we addressed each issue step by step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Strip Gutenberg Admin Dependencies from Frontend Block Scripts
&lt;/h2&gt;

&lt;p&gt;WordPress custom blocks created with the Block API often share registration logic between the editor and the frontend. A common oversight is passing editor packages into the frontend dependency array:&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad: Loading editor runtime on public pages&lt;/span&gt;
&lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'your-website-banner-block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;get_template_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/assets/scripts/banner.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'wp-blocks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-element'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-editor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wp-components'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// 🛑 Heavy bloat!&lt;/span&gt;
    &lt;span class="no"&gt;YOUR_THEME_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you specify &lt;code&gt;'wp-blocks'&lt;/code&gt;, &lt;code&gt;'wp-element'&lt;/code&gt;, or &lt;code&gt;'wp-editor'&lt;/code&gt; as dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress automatically enqueues React, &lt;code&gt;react-dom&lt;/code&gt;, &lt;code&gt;lodash&lt;/code&gt;, and the complete Gutenberg block parser runtime.&lt;/li&gt;
&lt;li&gt;It injects API fetch handlers and triggers blocking background requests (&lt;code&gt;/wp-json/wp/v2/users/me&lt;/code&gt; and &lt;code&gt;rest-nonce&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The payload sent to visitors increases by over &lt;strong&gt;1.5–2.0 MB&lt;/strong&gt; of unneeded JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Audit your frontend scripts. If a script only handles DOM manipulation, sliders, or UI toggles on the frontend, it does not need WordPress editor packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Good: Only declare what the frontend script actually uses&lt;/span&gt;
&lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'your-website-banner-block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;get_template_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/assets/scripts/banner.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// or [] if refactored to vanilla JavaScript&lt;/span&gt;
    &lt;span class="no"&gt;YOUR_THEME_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'strategy'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'defer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'in_footer'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; Reserve &lt;code&gt;'wp-blocks'&lt;/code&gt;, &lt;code&gt;'wp-element'&lt;/code&gt;, and &lt;code&gt;'wp-editor'&lt;/code&gt; exclusively for &lt;code&gt;enqueue_block_editor_assets&lt;/code&gt; (the admin editor screen). Keep frontend enqueues lean.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2: Stop Using &lt;code&gt;time()&lt;/code&gt; for Asset Versioning
&lt;/h2&gt;

&lt;p&gt;During development, it’s tempting to pass &lt;code&gt;time()&lt;/code&gt; as the version argument to avoid caching while making frequent changes. However, leaving it in production is detrimental to performance.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad: Generates a new query string on every single request&lt;/span&gt;
&lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-website-style'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_uri&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates URLs like &lt;code&gt;style.css?ver=1726312489&lt;/code&gt;. Because the version changes every second:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browsers never cache your CSS or JS files.&lt;/li&gt;
&lt;li&gt;Edge caches (Cloudflare, Fastly, etc.) treat every visit as a cache miss.&lt;/li&gt;
&lt;li&gt;Repeat visitors must re-download the entire stylesheet and script bundle on every page view.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Use a centralized, static theme version constant (or file modification time if strictly needed during staging):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In functions.php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'YOUR_THEME_VERSION'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'YOUR_THEME_VERSION'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0.4'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In your enqueue functions&lt;/span&gt;
&lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-website-style'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_uri&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;YOUR_THEME_VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever you deploy an update, increment &lt;code&gt;YOUR_THEME_VERSION&lt;/code&gt; once. Browsers will cache assets indefinitely and only re-fetch them when you release a new version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Eliminate CSS &lt;code&gt;@import&lt;/code&gt; &amp;amp; Modernize Font Loading
&lt;/h2&gt;

&lt;p&gt;Loading web fonts via CSS &lt;code&gt;@import&lt;/code&gt; declarations is one of the biggest contributors to slow &lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt; and &lt;strong&gt;First Contentful Paint (FCP)&lt;/strong&gt;.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In SCSS or CSS&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&amp;amp;display=swap")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a browser encounters &lt;code&gt;@import&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It pauses CSS parsing.&lt;/li&gt;
&lt;li&gt;It makes a blocking network roundtrip to fetch Google's CSS stylesheet.&lt;/li&gt;
&lt;li&gt;Only then does it discover the actual font files (&lt;code&gt;.woff2&lt;/code&gt;) and begin downloading them.&lt;/li&gt;
&lt;li&gt;If multiple stylesheets include &lt;code&gt;@import&lt;/code&gt;, this blocking waterfall repeats.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Remove all &lt;code&gt;@import url(...)&lt;/code&gt; rules&lt;/strong&gt; from your stylesheets and SCSS files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add preconnect hints&lt;/strong&gt; in your theme's &lt;code&gt;functions.php&lt;/code&gt; to resolve the DNS and TLS handshake early:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_resource_hints'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$hints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$relation_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'preconnect'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$relation_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$hints&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="s1"&gt;'href'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://fonts.googleapis.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'crossorigin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'anonymous'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;$hints&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="s1"&gt;'href'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https://fonts.gstatic.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'crossorigin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'anonymous'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$hints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enqueue the font centrally&lt;/strong&gt; in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; with &lt;code&gt;display=swap&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;wp_enqueue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'your-website-google-fonts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&amp;amp;display=swap'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;display=swap&lt;/code&gt; instructs the browser to immediately render text using a system fallback font, seamlessly swapping in the custom web font once loaded. This eliminates Flash of Invisible Text (FOIT).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Defer jQuery and Strip &lt;code&gt;jquery-migrate&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Historically, WordPress loaded jQuery synchronously in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; to support legacy plugins that inject inline jQuery calls in the body. In modern setups, this severely delays page rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Remove &lt;code&gt;jquery-migrate&lt;/code&gt; on Frontend
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;jquery-migrate&lt;/code&gt; is only required for legacy plugins using deprecated jQuery 1.x APIs. Removing it saves unnecessary HTTP requests and ~10 KB of blocking script execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_default_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scripts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_admin&lt;/span&gt;&lt;span class="p"&gt;()&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scripts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;registered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$scripts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;registered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$scripts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;registered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery-migrate'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Defer Core jQuery (WordPress 6.3+)
&lt;/h3&gt;

&lt;p&gt;WordPress 6.3 introduced native script loading strategies (&lt;code&gt;defer&lt;/code&gt; and &lt;code&gt;async&lt;/code&gt;). You can instruct WordPress to defer jQuery on non-admin pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_enqueue_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;is_admin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_script_add_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'strategy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'defer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;wp_script_add_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jquery-core'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'strategy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'defer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Defer your theme's custom scripts as well&lt;/span&gt;
    &lt;span class="nf"&gt;wp_enqueue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'your-website-global'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;get_template_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/assets/scripts/global.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'jquery'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="no"&gt;YOUR_THEME_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'strategy'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'defer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'in_footer'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Safeguard Inline Event Handlers
&lt;/h3&gt;

&lt;p&gt;If your theme renders inline scripts (such as reCAPTCHA or tracking handlers in &lt;code&gt;wp_footer&lt;/code&gt;), avoid calling &lt;code&gt;jQuery(document).ready(...)&lt;/code&gt; directly in raw &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags, because deferred jQuery will not have executed yet when the HTML parser hits that inline tag.&lt;/p&gt;

&lt;p&gt;Instead, use standard Vanilla JavaScript:&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;// Instead of jQuery(document).on('submit', 'form', handler):&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &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="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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FORM&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;// Execute form logic safely&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;
  
  
  Step 5: Dequeue Redundant Third-Party Plugin Assets
&lt;/h2&gt;

&lt;p&gt;As custom themes evolve, features originally handled by plugins (such as back-to-top buttons, social sharing buttons, or mobile drawers) are frequently rewritten as native, lightweight theme components.&lt;/p&gt;

&lt;p&gt;However, inactive or redundant plugins often remain installed and continue injecting styles and scripts into every page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_enqueue_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dequeue redundant general plugin assets handled natively by your-website&lt;/span&gt;
    &lt;span class="nf"&gt;wp_dequeue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redundant-plugin-style'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_dequeue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redundant-plugin-fonts'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;wp_dequeue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redundant-plugin-script'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By auditing active plugins and dequeuing obsolete stylesheets and scripts, you remove duplicate network payloads without affecting user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Conditionally Load Heavy Plugin Assets (Forms &amp;amp; Anti-Spam)
&lt;/h2&gt;

&lt;p&gt;Popular plugins like contact form managers and security/spam verifiers often enqueue their JavaScript and CSS across &lt;strong&gt;100% of your website's pages&lt;/strong&gt;, even on simple text articles and archive pages where no forms exist.&lt;/p&gt;

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

&lt;p&gt;A visitor reading a blog post or an about page is forced to download form validation scripts, styling bundles, and reCAPTCHA libraries that will never be used on that page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Smart Conditional Dequeuing
&lt;/h3&gt;

&lt;p&gt;Instead of loading form assets globally, only keep them active on pages where forms are actually present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_enqueue_scripts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the current page actually has a form&lt;/span&gt;
    &lt;span class="c1"&gt;// (Be sure to check for custom popup banners or modals that embed forms!)&lt;/span&gt;
    &lt;span class="nv"&gt;$has_form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;is_front_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;is_home&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="nb"&gt;is_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'WP_Post'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;has_shortcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'contact-form-7'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nf"&gt;has_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contact-form-7/form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nf"&gt;has_block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'your-website/notification-bar-block'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
            &lt;span class="nb"&gt;stripos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'contact-form-7'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If no form is present, dequeue the form assets&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="nv"&gt;$has_form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_dequeue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contact-form-7'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;wp_dequeue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contact-form-7'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;wp_dequeue_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'form-anti-spam'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;wp_dequeue_style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'form-anti-spam-css'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important Pro Tip:&lt;/strong&gt; If your homepage or header contains an interactive popup banner or modal with an embedded registration form, ensure your condition explicitly includes &lt;code&gt;is_front_page()&lt;/code&gt; or checks for your banner block. Otherwise, selective dequeuing might strip the AJAX handlers from your popup form!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Applying these optimizations produced immediate, measurable improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payload Reduction:&lt;/strong&gt; Over &lt;strong&gt;1.5 MB&lt;/strong&gt; of unnecessary JavaScript (React &amp;amp; Gutenberg editor dependencies) was eliminated from the public frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render-Blocking Clearance:&lt;/strong&gt; Removing &lt;code&gt;@import&lt;/code&gt; fonts, dequeuing &lt;code&gt;jquery-migrate&lt;/code&gt;, and deferring core jQuery reduced initial render blocking by hundreds of milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effective Caching:&lt;/strong&gt; Replacing &lt;code&gt;time()&lt;/code&gt; with static versioning enabled proper browser caching and CDN hit ratios for all repeat visitors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Lost Functionality:&lt;/strong&gt; Interactive blocks, form popups, and native animations continue to work seamlessly.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Optimizing WordPress performance doesn't require stripping away interactive features or relying solely on complex caching plugins. By auditing asset dependencies, modernizing script loading strategies, and delivering assets only where they are genuinely needed, you can deliver an ultra-fast experience to your visitors.&lt;/p&gt;

&lt;p&gt;Have you audited your custom Gutenberg blocks and plugin enqueues recently? Let us know in the comments below!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Why I Added Tailscale to My VPS: Secure Access Without Relying on a Static IP</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:29:20 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/why-i-added-tailscale-to-my-vps-secure-access-without-relying-on-a-static-ip-161p</link>
      <guid>https://dev.to/abbeymaniak/why-i-added-tailscale-to-my-vps-secure-access-without-relying-on-a-static-ip-161p</guid>
      <description>&lt;p&gt;Managing a VPS from a home or office network sounds straightforward until your internet provider starts changing your public IP address.&lt;/p&gt;

&lt;p&gt;That was one of the problems I ran into recently.&lt;/p&gt;

&lt;p&gt;I had a VPS with some administrative resources restricted to specific IP addresses. The idea was simple: only my trusted IP should be able to access certain services.&lt;/p&gt;

&lt;p&gt;The problem?&lt;/p&gt;

&lt;p&gt;My ISP uses &lt;strong&gt;dynamic public IP addresses&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every time my public IP changed, I potentially lost access to those IP-restricted resources.&lt;/p&gt;

&lt;p&gt;Instead of continuously updating firewall rules whenever my IP changed, I decided to introduce &lt;strong&gt;Tailscale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article explains what Tailscale is, why it can be useful for VPS administration, and how to set it up on a Linux VPS and a Mac.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Tailscale?
&lt;/h2&gt;

&lt;p&gt;Tailscale is a networking platform built around &lt;strong&gt;WireGuard&lt;/strong&gt; that allows your devices and servers to communicate over a private network.&lt;/p&gt;

&lt;p&gt;Tailscale calls this private network a &lt;strong&gt;tailnet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of relying on the public IP address of your VPS, devices connected to your tailnet can communicate using private Tailscale addresses, typically in the &lt;code&gt;100.x.x.x&lt;/code&gt; range.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mac
  |
  | Tailscale
  |
  v
Private Tailnet
  |
  v
VPS
100.x.x.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale describes itself as a zero-trust, identity-based connectivity platform, and it uses WireGuard for encrypted point-to-point connections.&lt;/p&gt;

&lt;p&gt;The important part for me wasn't just encryption.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;stable private connectivity between my devices and my VPS&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Would You Use Tailscale?
&lt;/h2&gt;

&lt;p&gt;There are several reasons Tailscale can be useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your ISP Uses Dynamic IP Addresses
&lt;/h2&gt;

&lt;p&gt;This was my main reason.&lt;/p&gt;

&lt;p&gt;Imagine you configure a firewall rule like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allow SSH
Source: 102.143.20.123/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means only that specific IP address can connect.&lt;/p&gt;

&lt;p&gt;This is useful from a security perspective.&lt;/p&gt;

&lt;p&gt;But if your ISP changes your public IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Yesterday:
102.143.20.123

Today:
105.131.95.49
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;your firewall rule is now outdated.&lt;/p&gt;

&lt;p&gt;You have to manually update it.&lt;/p&gt;

&lt;p&gt;With Tailscale, your Mac and VPS can communicate over your tailnet regardless of your ISP's current public IP.&lt;/p&gt;

&lt;p&gt;Instead of depending on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My ISP public IP
        ↓
      VPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My Mac
   ↓
Tailscale
   ↓
Private tailnet
   ↓
VPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means I can maintain stable private access without constantly updating IP allowlists because my ISP changed my public IP.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Secure Access to Your VPS
&lt;/h2&gt;

&lt;p&gt;SSH is already encrypted, but exposing SSH to the public internet means your server is continuously reachable by internet scanners and automated login attempts.&lt;/p&gt;

&lt;p&gt;Tailscale provides another way to access your server over your private tailnet.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@100.89.121.90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@your-public-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;100.82.128.80&lt;/code&gt; address is a Tailscale address in this example.&lt;/p&gt;

&lt;p&gt;Tailscale's documentation specifically supports using Tailscale to SSH into Linux machines over the tailnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. You Don't Need to Know Your Current Public IP
&lt;/h2&gt;

&lt;p&gt;Before using Tailscale, I might need to check my current public IP before accessing an IP-restricted service.&lt;/p&gt;

&lt;p&gt;With Tailscale, my devices have stable identities on the tailnet.&lt;/p&gt;

&lt;p&gt;That makes remote administration much more predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Access Services That Shouldn't Be Public
&lt;/h2&gt;

&lt;p&gt;Not every service running on a VPS needs to be accessible from the entire internet.&lt;/p&gt;

&lt;p&gt;For example, you might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public
├── HTTP :80
└── HTTPS :443

Private
├── Admin dashboard
├── Database administration
├── Internal monitoring
├── Development tools
└── SSH administration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale can be useful for the private portion.&lt;/p&gt;

&lt;p&gt;Instead of exposing every administrative service publicly, you can make certain services reachable only through your tailnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. You Can Connect More Than Just Your Laptop
&lt;/h2&gt;

&lt;p&gt;Tailscale isn't limited to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mac → VPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mac
Laptop
Phone
VPS
Home server
Cloud server
Development machine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and control which devices can communicate with which resources.&lt;/p&gt;

&lt;p&gt;Tailscale's access-control system is designed around identity and least-privilege access, allowing policies to define which users and devices can access specific resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up Tailscale on a Linux VPS
&lt;/h2&gt;

&lt;p&gt;Let's walk through a basic setup.&lt;/p&gt;

&lt;p&gt;I'm using an Ubuntu-based VPS for this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Tailscale
&lt;/h2&gt;

&lt;p&gt;On the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the installation method documented by Tailscale for mainstream Linux distributions.&lt;/p&gt;

&lt;p&gt;After installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the installed version.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.102.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Authenticate the VPS
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale will provide an authentication URL.&lt;/p&gt;

&lt;p&gt;Open the URL in your browser and authenticate with your Tailscale account.&lt;/p&gt;

&lt;p&gt;Once completed, the VPS becomes part of your tailnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Check the Tailscale Connection
&lt;/h2&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your VPS and other devices connected to your tailnet.&lt;/p&gt;

&lt;p&gt;You can also retrieve the VPS's Tailscale IPv4 address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale ip &lt;span class="nt"&gt;-4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100.89.121.90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That address is what you can use for private communication with the VPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Install Tailscale on Your Mac
&lt;/h2&gt;

&lt;p&gt;Install Tailscale on your Mac and sign in using the same Tailscale account.&lt;/p&gt;

&lt;p&gt;After both devices are connected, you should have something similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mac
100.x.x.x
   |
   | Tailscale
   |
   v
VPS
100.89.121.90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Test SSH Through Tailscale
&lt;/h2&gt;

&lt;p&gt;From your Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@100.89.121.90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also explicitly specify an SSH key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/your-key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;IdentitiesOnly&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    root@100.89.121.90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the connection is going through the VPS's Tailscale address rather than its public IP.&lt;/p&gt;

&lt;p&gt;Tailscale's documentation also supports using MagicDNS, allowing you to connect using a machine hostname instead of remembering the &lt;code&gt;100.x.x.x&lt;/code&gt; address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making SSH Easier with ~/.ssh/config
&lt;/h2&gt;

&lt;p&gt;Typing the entire SSH command every time isn't necessary.&lt;/p&gt;

&lt;p&gt;On your Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host my-vps
    HostName 100.89.121.90
    User root
    IdentityFile ~/.ssh/your-key
    IdentitiesOnly yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh my-vps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes particularly useful when you have multiple servers and multiple SSH keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tailscale vs a Traditional VPN
&lt;/h2&gt;

&lt;p&gt;Tailscale is often described as a VPN, but the experience is different from configuring a traditional VPN server yourself.&lt;/p&gt;

&lt;p&gt;With a traditional VPN setup, you might have to manage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPN server
Certificates
Routing
Firewall rules
NAT
Client configuration
Key management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale handles much of the networking complexity for you.&lt;/p&gt;

&lt;p&gt;It uses WireGuard underneath and provides identity and access-control features around the network.&lt;/p&gt;

&lt;p&gt;That's one reason it is attractive for developers and infrastructure administrators who don't want to manually build and maintain a VPN infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tailscale and Firewalls
&lt;/h2&gt;

&lt;p&gt;One important thing to understand is that &lt;strong&gt;Tailscale does not automatically replace your firewall&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can still use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Contabo Firewall
        ↓
UFW
        ↓
Tailscale
        ↓
Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale is designed to work alongside existing firewalls, and its networking can often work without additional firewall configuration because of NAT traversal.&lt;/p&gt;

&lt;p&gt;Your firewall should still be configured according to what your server actually needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Should You Close Port 22?
&lt;/h2&gt;

&lt;p&gt;This depends on your architecture.&lt;/p&gt;

&lt;p&gt;If you use Tailscale exclusively for SSH, you can potentially remove public SSH access and use Tailscale SSH or SSH over the Tailscale network.&lt;/p&gt;

&lt;p&gt;Tailscale's documentation even recommends closing the public SSH port after verifying Tailscale SSH access when appropriate.&lt;/p&gt;

&lt;p&gt;However, this isn't always practical.&lt;/p&gt;

&lt;p&gt;For example, if you have a CI/CD system using GitHub-hosted runners that connects to your VPS over public SSH, closing port 22 would break that deployment workflow unless you redesign the deployment architecture.&lt;/p&gt;

&lt;p&gt;In that situation, you can keep port 22 publicly reachable while still using Tailscale for your own administrative access.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tailscale SSH
&lt;/h2&gt;

&lt;p&gt;Tailscale also has a feature called &lt;strong&gt;Tailscale SSH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of relying on traditional SSH key management, Tailscale can manage SSH authentication and authorization using your tailnet identity and access policies.&lt;/p&gt;

&lt;p&gt;You can enable it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--ssh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I recommend understanding your existing SSH setup before switching to Tailscale SSH on a production server.&lt;/p&gt;

&lt;p&gt;You don't necessarily need Tailscale SSH just to benefit from Tailscale networking.&lt;/p&gt;

&lt;p&gt;You can simply use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tailscale networking
+
Traditional SSH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is the approach I used for my VPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical VPS Security Architecture
&lt;/h2&gt;

&lt;p&gt;A setup I like is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Internet
                       |
              +--------+--------+
              |                 |
           HTTPS             SSH :22
              |                 |
              v                 v
        Public Services      VPS Firewall
                                |
                                v
                              SSH
                                ^
                                |
                         Tailscale network
                                ^
                                |
                              My Mac
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can keep public services public while using Tailscale for private administration.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public
├── 80/tcp
└── 443/tcp

Restricted / Administrative
├── SSH
├── Admin interfaces
├── Internal tools
└── Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Tailscale Doesn't Solve
&lt;/h2&gt;

&lt;p&gt;Tailscale isn't a replacement for general server security.&lt;/p&gt;

&lt;p&gt;You should still consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH keys instead of passwords&lt;/li&gt;
&lt;li&gt;Disabling SSH password authentication&lt;/li&gt;
&lt;li&gt;Avoiding root for automated deployments&lt;/li&gt;
&lt;li&gt;Using dedicated deployment users&lt;/li&gt;
&lt;li&gt;Keeping the operating system updated&lt;/li&gt;
&lt;li&gt;Configuring a firewall&lt;/li&gt;
&lt;li&gt;Using least-privilege access&lt;/li&gt;
&lt;li&gt;Monitoring authentication attempts&lt;/li&gt;
&lt;li&gt;Using Fail2ban where appropriate&lt;/li&gt;
&lt;li&gt;Protecting application credentials&lt;/li&gt;
&lt;li&gt;Backups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tailscale gives you a better networking and access layer; it doesn't magically make an insecure server secure.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Main Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest reason I added Tailscale to my VPS wasn't simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I need a VPN."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I need reliable private access to my infrastructure without depending on my ISP's changing public IP address."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;If your ISP gives you a dynamic IP and you've built firewall rules around your current address, eventually you'll probably run into the same problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ISP changes IP
      ↓
Firewall rule becomes outdated
      ↓
Access breaks
      ↓
Update firewall
      ↓
ISP changes IP again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailscale changes the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mac
 ↓
Tailscale identity
 ↓
Private tailnet
 ↓
VPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my access doesn't depend on whatever public IP my ISP happens to assign me that day.&lt;/p&gt;

&lt;p&gt;For anyone managing a VPS from a dynamic residential or office connection, &lt;strong&gt;Tailscale is worth considering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is particularly useful when you need private access to infrastructure without wanting to build and maintain a traditional VPN yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/docs?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Tailscale Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/docs/install/linux?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Tailscale Linux Installation Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/docs/features/tailscale-ssh?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Tailscale SSH Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/docs/integrations/firewalls?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Tailscale Firewall Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>infrastructure</category>
      <category>linux</category>
      <category>networking</category>
      <category>security</category>
    </item>
    <item>
      <title>Mastering Zero-Downtime Deployments with Laravel Deployer</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Wed, 26 Aug 2026 11:06:16 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/mastering-zero-downtime-deployments-with-laravel-deployer-18f3</link>
      <guid>https://dev.to/abbeymaniak/mastering-zero-downtime-deployments-with-laravel-deployer-18f3</guid>
      <description>&lt;p&gt;&lt;em&gt;How I achieved zero-downtime deployments using PHP Deployer for my Laravel app, and the critical caching and Horizon worker issues I had to solve.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deploying a Laravel application shouldn't mean taking your site offline. For a long time, I used simple &lt;code&gt;git pull&lt;/code&gt; scripts or basic FTP, which often resulted in momentary downtime or broken states if a user visited exactly while &lt;code&gt;composer install&lt;/code&gt; was running. &lt;/p&gt;

&lt;p&gt;To solve this for my platform, &lt;strong&gt;CelebrateMe&lt;/strong&gt;, I migrated to &lt;a href="https://deployer.org/" rel="noopener noreferrer"&gt;Deployer&lt;/a&gt; a powerful, PHP-based deployment tool that makes zero-downtime deployments incredibly easy. &lt;/p&gt;

&lt;p&gt;However, the journey wasn't entirely smooth. I ran into a couple of frustrating issues regarding &lt;strong&gt;Deployer's Git caching&lt;/strong&gt; and &lt;strong&gt;Laravel Horizon worker memory&lt;/strong&gt; that had me pulling my hair out. &lt;/p&gt;

&lt;p&gt;Here is how I set it up alongside GitHub Actions to automate the process, the issues I faced, and exactly how I fixed them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Triggering Deployments with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;To fully automate the CI/CD pipeline, I connected Deployer to &lt;strong&gt;GitHub Actions&lt;/strong&gt;. Instead of manually running &lt;code&gt;dep deploy&lt;/code&gt; from my local machine, I configured a GitHub Actions workflow that runs whenever I push to the &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;beta&lt;/code&gt; branch. &lt;/p&gt;

&lt;p&gt;The workflow handles building my frontend assets and then securely triggers Deployer (via an SSH key and secret tokens) to pull the latest code and deploy it to the server. This means every push automatically triggers a zero-downtime deployment without any manual intervention!&lt;/p&gt;

&lt;h2&gt;
  
  
  How Zero-Downtime Deployment Works
&lt;/h2&gt;

&lt;p&gt;Deployer achieves zero-downtime by maintaining a specific directory structure on your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/www/wwwroot/my-app/
├── current -&amp;gt; /www/wwwroot/my-app/releases/21  (Symlink to the latest release)
├── releases/
│   ├── 19/
│   ├── 20/
│   └── 21/  (The active release)
└── shared/
    ├── .env
    └── storage/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run &lt;code&gt;dep deploy&lt;/code&gt;, Deployer does not overwrite your live code. Instead, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a brand new folder in &lt;code&gt;releases/&lt;/code&gt; (e.g., &lt;code&gt;releases/22&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Clones your GitHub repository into that new folder.&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;composer install&lt;/code&gt;, &lt;code&gt;npm run build&lt;/code&gt;, and &lt;code&gt;php artisan migrate&lt;/code&gt; entirely in the background inside &lt;code&gt;releases/22&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally, it updates the &lt;code&gt;current&lt;/code&gt; symlink to point to &lt;code&gt;releases/22&lt;/code&gt; in a fraction of a second.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the web server (Nginx/Apache) points to the &lt;code&gt;current&lt;/code&gt; symlink, the transition is instantaneous. Zero downtime!&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Deployer's Git Cache (&lt;code&gt;.dep/repo&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;By default, to make deployments faster, Deployer tries to cache your Git repository on the server inside a hidden &lt;code&gt;.dep/repo&lt;/code&gt; directory. Instead of doing a full git clone every time, it fetches the changes into this cached repo and then copies them to the new release folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Issue I Faced
&lt;/h3&gt;

&lt;p&gt;After a few successful deployments, my deployments suddenly started failing mysteriously. Deployer would get stuck during the &lt;code&gt;deploy:update_code&lt;/code&gt; step, complaining about uncommitted changes, corrupted git indices, or simply failing to pull the latest commits from the main branch. &lt;/p&gt;

&lt;p&gt;Because the cached repository in &lt;code&gt;.dep/repo&lt;/code&gt; had gotten out of sync or corrupted by a previous interrupted deployment, Deployer was completely locked up. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: The Clone Strategy
&lt;/h3&gt;

&lt;p&gt;I realized that for my scale, a fresh clone takes only a few seconds anyway, and the reliability of a fresh clone far outweighs the slight speed boost of the Git cache.&lt;/p&gt;

&lt;p&gt;I bypassed the cache completely by explicitly telling Deployer to use the &lt;code&gt;clone&lt;/code&gt; strategy instead of the default cache strategy. &lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;deploy.php&lt;/code&gt; file, I added this single line of configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Force Deployer to freshly clone the repository every time&lt;/span&gt;
&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'update_code_strategy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'clone'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I added this, Deployer stopped relying on the &lt;code&gt;.dep/repo&lt;/code&gt; cache. It simply ran a fresh &lt;code&gt;git clone&lt;/code&gt; into the new release directory every time. The deployments became 100% reliable and I never saw a Git cache corruption error again.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Second Gotcha: Laravel Horizon and "Ghost" Code
&lt;/h2&gt;

&lt;p&gt;Even after my deployments were succeeding perfectly and the &lt;code&gt;current&lt;/code&gt; symlink was pointing to my new code, I noticed something terrifying in my error logs. &lt;/p&gt;

&lt;p&gt;My queue workers (Laravel Horizon) were crashing with errors referencing line numbers and logic from &lt;strong&gt;old code&lt;/strong&gt; that I had completely removed in my latest commit!&lt;/p&gt;

&lt;h3&gt;
  
  
  Why did this happen?
&lt;/h3&gt;

&lt;p&gt;When Deployer updates the &lt;code&gt;current&lt;/code&gt; symlink, your web server (Nginx) immediately starts serving the new PHP files for incoming HTTP requests. &lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;Laravel Horizon (and queue workers) are long-running PHP daemon processes&lt;/strong&gt;. They are started once and kept alive in memory. When you deploy new code, those long-running processes do &lt;em&gt;not&lt;/em&gt; automatically know that the files on the hard drive have changed. They continue executing the old classes that were loaded into memory when the worker first started (potentially days ago!).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Graceful Termination
&lt;/h3&gt;

&lt;p&gt;To fix this, you must tell Horizon to gracefully terminate itself after a deployment finishes. If you are using a process monitor like Supervisor, it will instantly restart the Horizon process, and when it boots back up, it will load the fresh code from the new &lt;code&gt;current&lt;/code&gt; symlink.&lt;/p&gt;

&lt;p&gt;To automate this, hook into Deployer's lifecycle. In &lt;code&gt;deploy.php&lt;/code&gt;, define a task to restart Horizon and run it right after the symlink is updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'horizon:terminate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cd {{release_or_current_path}} &amp;amp;&amp;amp; php artisan horizon:terminate'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Run this task automatically after the new code is live&lt;/span&gt;
&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deploy:symlink'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'horizon:terminate'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Note: Don't use &lt;code&gt;horizon:purge&lt;/code&gt; or &lt;code&gt;queue:restart&lt;/code&gt; if you are specifically using Horizon. &lt;code&gt;horizon:terminate&lt;/code&gt; is the official, safe way to instruct the master process to wrap up its current jobs and shut down).&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Deployer is an absolute game-changer for deploying PHP applications, but understanding &lt;em&gt;how&lt;/em&gt; it interacts with the server environment is crucial. &lt;/p&gt;

&lt;p&gt;If you're setting up Deployer for a Laravel app, save yourself a headache and remember these two rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;set('update_code_strategy', 'clone');&lt;/code&gt; if you run into &lt;code&gt;.dep&lt;/code&gt; cache corruption.&lt;/li&gt;
&lt;li&gt;Always run &lt;code&gt;php artisan horizon:terminate&lt;/code&gt; after your &lt;code&gt;deploy:symlink&lt;/code&gt; step to ensure your background workers aren't running ghost code from memory!&lt;/li&gt;
&lt;/ol&gt;

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

</description>
      <category>laravel</category>
      <category>php</category>
      <category>devops</category>
      <category>deployer</category>
    </item>
    <item>
      <title>How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Mon, 10 Aug 2026 09:54:00 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/how-to-secure-your-wordpress-dashboard-and-prevent-clients-from-breaking-their-sites-1e57</link>
      <guid>https://dev.to/abbeymaniak/how-to-secure-your-wordpress-dashboard-and-prevent-clients-from-breaking-their-sites-1e57</guid>
      <description>&lt;p&gt;&lt;em&gt;A guide on using Admin Extension Access Control to lock down WordPress plugins and prevent unauthorized changes.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Secure Your WordPress Dashboard and Prevent Clients from Breaking Their Sites
&lt;/h2&gt;

&lt;p&gt;If you are a freelance web developer or run an agency, you have probably experienced the dread of a client accidentally bringing down their WordPress site. You spend weeks building a robust, performant website, only for an unauthorized user to log into the dashboard, start deactivating essential plugins, or install poorly coded extensions that break everything.&lt;/p&gt;

&lt;p&gt;WordPress is fantastic because of its flexibility, but out of the box, any Administrator can touch &lt;em&gt;everything&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;To solve this problem, I want to introduce a lightweight solution: &lt;strong&gt;Admin Extension Access Control&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Admin Extension Access Control?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/restrictify-extension-access-control" rel="noopener noreferrer"&gt;Admin Extension Access Control&lt;/a&gt; is a WordPress plugin designed to give you granular control over who can see, modify, install, or delete plugins on your site. &lt;/p&gt;

&lt;p&gt;Built for modern environments (PHP 8.1+ and WordPress 6.0+), it allows you to configure strict role-based access rules without writing custom PHP functions in your &lt;code&gt;functions.php&lt;/code&gt; file every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global Lockdown&lt;/strong&gt;: Completely remove the plugins page for specific user roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Granular Permissions&lt;/strong&gt;: Restrict the ability to add, delete, activate, deactivate, or install plugins on a per-role basis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exempt Users Whitelist&lt;/strong&gt;: Designate trusted administrators (like yourself) who bypass all lockdown rules. Only exempt users can configure the access control settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard Cleanup&lt;/strong&gt;: Hide the plugins menu item from unauthorized users to keep the dashboard less confusing for clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Once installed and activated, the user who activates the plugin is automatically added to the &lt;strong&gt;Exempt Users&lt;/strong&gt; list. This prevents you from accidentally locking yourself out. &lt;/p&gt;

&lt;p&gt;From the settings panel, you can select which roles should be restricted from managing plugins. For example, you can give your client an "Administrator" role (so they feel in control of their site) but restrict their ability to install or delete plugins. If they try to access the restricted URLs directly, they will be safely redirected.&lt;/p&gt;

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

&lt;p&gt;The plugin is currently actively developed, and there are some exciting advanced features on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Individual Plugin Selection&lt;/strong&gt;: Hide specific plugins from users while leaving others visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activity &amp;amp; Audit Logs&lt;/strong&gt;: A timestamped log of all access attempts and plugin visibility events.(Coming soon)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack Notifications&lt;/strong&gt;: Get instant alerts when someone attempts to breach a lockdown rule.(Coming soon)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multisite Compatibility&lt;/strong&gt;: Manage rules across a network of sites.(Coming soon)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Securing the WordPress admin area is a critical step in maintaining the longevity and stability of your client projects. By restricting plugin access, you save yourself hours of troubleshooting and protect your clients from their own curiosity.&lt;/p&gt;

&lt;p&gt;Check out the repository on (&lt;a href="https://wordpress.org/plugins/restrictify-extension-access-control/" rel="noopener noreferrer"&gt;https://wordpress.org/plugins/restrictify-extension-access-control/&lt;/a&gt;) to contribute, or download the latest release to try it out on your next project!&lt;/p&gt;

&lt;p&gt;If you find it useful, let me know in the comments below!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Laravel Packages Every Developer Should Know (After Building a Real-World Product)</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:35:36 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/laravel-packages-every-developer-should-know-after-building-a-real-world-product-5g5l</link>
      <guid>https://dev.to/abbeymaniak/laravel-packages-every-developer-should-know-after-building-a-real-world-product-5g5l</guid>
      <description>&lt;p&gt;Laravel is one of my favorite frameworks because it allows you to move from idea to production incredibly fast. But after spending months building &lt;strong&gt;CelebrateMe&lt;/strong&gt; a platform that helps people celebrate life's special moments through virtual gifts, wishlists, messages, and verified vendors—I realized something.&lt;/p&gt;

&lt;p&gt;I wasn't just using Laravel.&lt;/p&gt;

&lt;p&gt;I was relying heavily on the incredible ecosystem around it.&lt;/p&gt;

&lt;p&gt;Some packages solved problems that would have taken days (or weeks) to build myself. Others helped me monitor, debug, and secure the application as it grew.&lt;/p&gt;

&lt;p&gt;Here are the Laravel packages I now consider essential for almost every project.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Laravel Sanctum
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; API Authentication&lt;/p&gt;

&lt;p&gt;CelebrateMe has a Laravel API with a React frontend, so authentication needed to be secure without adding unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Laravel Sanctum was the perfect choice.&lt;/p&gt;

&lt;p&gt;It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal access tokens&lt;/li&gt;
&lt;li&gt;SPA authentication&lt;/li&gt;
&lt;li&gt;Mobile API authentication&lt;/li&gt;
&lt;li&gt;Lightweight implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most APIs, Sanctum is more than enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Laravel Horizon
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Queue Monitoring&lt;/p&gt;

&lt;p&gt;As CelebrateMe grew, background jobs became increasingly important.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending emails&lt;/li&gt;
&lt;li&gt;Processing uploads&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Payment-related jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of wondering whether jobs were running correctly, Horizon gave me a beautiful dashboard to monitor everything in real time.&lt;/p&gt;

&lt;p&gt;If you're using queues and not using Horizon, you're missing out.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Laravel Telescope
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Debugging&lt;/p&gt;

&lt;p&gt;Telescope quickly became one of my favorite development tools.&lt;/p&gt;

&lt;p&gt;Instead of scattering &lt;code&gt;dd()&lt;/code&gt; statements throughout my code, I could inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests&lt;/li&gt;
&lt;li&gt;SQL queries&lt;/li&gt;
&lt;li&gt;Jobs&lt;/li&gt;
&lt;li&gt;Exceptions&lt;/li&gt;
&lt;li&gt;Cache operations&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It made debugging significantly easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Spatie Laravel Permission
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Roles &amp;amp; Permissions&lt;/p&gt;

&lt;p&gt;CelebrateMe has multiple user types, each requiring different permissions.&lt;/p&gt;

&lt;p&gt;Managing authorization manually would have become difficult very quickly.&lt;/p&gt;

&lt;p&gt;Spatie's Permission package made it straightforward to assign roles and permissions while integrating cleanly with Laravel's authorization system.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Laravel Excel
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Importing and Exporting Data&lt;/p&gt;

&lt;p&gt;Every business application eventually needs reporting.&lt;/p&gt;

&lt;p&gt;Whether it's exporting users, vendors, or financial reports, Laravel Excel saves an enormous amount of development time.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Spatie Media Library
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; File Management&lt;/p&gt;

&lt;p&gt;Users upload profile pictures, event images, product photos, and other assets.&lt;/p&gt;

&lt;p&gt;Media Library handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple collections&lt;/li&gt;
&lt;li&gt;Image conversions&lt;/li&gt;
&lt;li&gt;Cloud storage&lt;/li&gt;
&lt;li&gt;Responsive images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of building a custom media management solution, I could focus on building product features.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Laravel Activity Log
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Audit Trails&lt;/p&gt;

&lt;p&gt;As applications grow, knowing &lt;em&gt;who changed what&lt;/em&gt; becomes increasingly valuable.&lt;/p&gt;

&lt;p&gt;Whether it's an administrator updating information or users modifying their profiles, Activity Log creates a reliable audit trail with minimal effort.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Laravel Pint
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Code Formatting&lt;/p&gt;

&lt;p&gt;This may seem like a small addition, but it improves team productivity.&lt;/p&gt;

&lt;p&gt;Instead of discussing formatting during code reviews, Pint keeps everything consistent automatically.&lt;/p&gt;

&lt;p&gt;One command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vendor/bin/pint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Laravel Pulse
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Application Monitoring&lt;/p&gt;

&lt;p&gt;Performance issues are much easier to solve when you can actually see what's happening.&lt;/p&gt;

&lt;p&gt;Pulse provides visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow requests&lt;/li&gt;
&lt;li&gt;Database performance&lt;/li&gt;
&lt;li&gt;Cache usage&lt;/li&gt;
&lt;li&gt;Queue health&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a great addition once your application reaches production.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Laravel Socialite
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use it for:&lt;/strong&gt; Social Authentication&lt;/p&gt;

&lt;p&gt;If your application supports "Sign in with Google" or GitHub, don't build OAuth yourself.&lt;/p&gt;

&lt;p&gt;Socialite makes authentication simple and integrates beautifully with Laravel.&lt;/p&gt;




&lt;h1&gt;
  
  
  My Biggest Lesson
&lt;/h1&gt;

&lt;p&gt;One thing I learned while building CelebrateMe is that being productive isn't about writing the most code.&lt;/p&gt;

&lt;p&gt;It's about writing the &lt;em&gt;right&lt;/em&gt; code.&lt;/p&gt;

&lt;p&gt;Laravel's ecosystem already solves many common problems with mature, well-maintained packages. Instead of reinventing those solutions, I could spend my time building features that actually made CelebrateMe better for its users.&lt;/p&gt;

&lt;p&gt;That shift in mindset probably saved me weeks of development.&lt;/p&gt;




&lt;h1&gt;
  
  
  Honorable Mentions
&lt;/h1&gt;

&lt;p&gt;Some excellent packages that are also worth exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Reverb&lt;/li&gt;
&lt;li&gt;Laravel Scout&lt;/li&gt;
&lt;li&gt;Laravel Octane&lt;/li&gt;
&lt;li&gt;Spatie Laravel Data&lt;/li&gt;
&lt;li&gt;Spatie Query Builder&lt;/li&gt;
&lt;li&gt;Barryvdh DomPDF&lt;/li&gt;
&lt;li&gt;Intervention Image&lt;/li&gt;
&lt;li&gt;Laravel Backup&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;These are the packages that have consistently made my development workflow faster and my applications more maintainable.&lt;/p&gt;

&lt;p&gt;The Laravel ecosystem is one of the framework's biggest strengths, and learning which packages to trust is just as important as learning the framework itself.&lt;/p&gt;

&lt;p&gt;I'm always looking for new tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Laravel package do you install on every project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd love to discover a few more.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title># Using FlyEnv for Laravel and WordPress Development: My First Experience</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Sun, 28 Jun 2026 00:43:29 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/-using-flyenv-for-laravel-and-wordpress-development-my-first-experience-5m0</link>
      <guid>https://dev.to/abbeymaniak/-using-flyenv-for-laravel-and-wordpress-development-my-first-experience-5m0</guid>
      <description>&lt;p&gt;As someone who works with both Laravel and WordPress projects often, one recurring challenge is managing local development environments efficiently. Switching between projects, maintaining configurations, and ensuring everything works consistently can sometimes take more effort than expected.&lt;/p&gt;

&lt;p&gt;Recently, I decided to try FlyEnv and wanted to share my experience using it in my Laravel and WordPress workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Tried FlyEnv
&lt;/h2&gt;

&lt;p&gt;I was looking for a simpler way to manage local development without spending too much time on setup and configuration.since upgrading XAMPP on mac was a hassle.&lt;/p&gt;

&lt;p&gt;For my workflow, I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier environment management&lt;/li&gt;
&lt;li&gt;Faster project setup&lt;/li&gt;
&lt;li&gt;Less manual configuration&lt;/li&gt;
&lt;li&gt;Ability to change from different versions of PHP&lt;/li&gt;
&lt;li&gt;A smoother transition between Laravel and WordPress projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Experience Using FlyEnv
&lt;/h2&gt;

&lt;p&gt;I tested FlyEnv with both Laravel and WordPress development projects.&lt;/p&gt;

&lt;p&gt;The initial setup process was straightforward and did not take long. I was able to get started without spending excessive time trying to understand the interface or changing multiple settings.&lt;/p&gt;

&lt;p&gt;For Laravel projects, I liked having a cleaner environment setup because it helped me focus more on building features rather than handling configuration issues.&lt;/p&gt;

&lt;p&gt;For WordPress development, having a simpler local workflow also made things easier when working on themes, creating custom plugins and testing changes.&lt;/p&gt;

&lt;p&gt;Since I regularly move between Laravel applications and WordPress projects, having a tool that helps reduce friction during development is valuable.&lt;/p&gt;

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

&lt;p&gt;A few things stood out during my use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple onboarding process&lt;/li&gt;
&lt;li&gt;Cleaner development workflow&lt;/li&gt;
&lt;li&gt;Less time spent on manual environment setup&lt;/li&gt;
&lt;li&gt;Useful for handling different project types&lt;/li&gt;
&lt;li&gt;Easy enough to start using quickly&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Overall, my experience with FlyEnv for Laravel and WordPress development was positive.&lt;/p&gt;

&lt;p&gt;I appreciate tools that reduce setup overhead and allow developers to spend more time writing code rather than troubleshooting environments.&lt;/p&gt;

&lt;p&gt;I'll continue using it and testing additional workflows, but so far it has been a useful addition to my development setup.&lt;/p&gt;

&lt;p&gt;Have you used FlyEnv in your projects? I'd be interested in hearing how others are using it in their workflows.&lt;/p&gt;

</description>
      <category>flyenv</category>
      <category>env</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Custom Home Casting App with Rust and React</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Sat, 06 Jun 2026 13:26:17 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/building-a-custom-home-casting-app-with-rust-and-react-2pe0</link>
      <guid>https://dev.to/abbeymaniak/building-a-custom-home-casting-app-with-rust-and-react-2pe0</guid>
      <description>&lt;p&gt;Have you ever wanted a simple, lightweight way to cast videos from your phone to your smart TV without relying on third-party apps or dealing with complex Chromecast/DLNA setups? In this article, I am going to show you how I built "Home Cast" – a fully self-hosted video casting application using a Rust backend and a sleek React frontend.&lt;/p&gt;

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

&lt;p&gt;The system consists of two main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Server (Rust/Axum): A fast, concurrent backend that handles video uploads via multipart forms and communicates with the TV using WebSockets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Client (React/Vite): A beautiful, modern interface that lets you upload videos and cast them with a single tap. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic relies on WebSockets. When you open the application on your TV's web browser, it connects to the Rust server's WebSocket endpoint and passively listens. When you upload a video from your phone, the Rust server saves it locally. Then, when you click "Cast to TV," the server broadcasts a message containing the new video URL to the TV. The TV receives the message, dynamically updates its HTML5 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; player, and immediately begins playback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Browser Autoplay Policies
&lt;/h2&gt;

&lt;p&gt;One major hurdle with web-based video players on TVs is the dreaded "Playback Blocked" error. Modern browsers restrict videos with audio from playing automatically unless the user interacts with the page first. I solved this by catching the rejected play promise and gracefully prompting the user to press the OK button on their TV remote (which simulates a click event). This bypasses the restriction and starts the stream seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;Using Axum and Tokio made it incredibly easy to handle WebSocket connections and file uploads concurrently. Rust's memory safety guarantees mean we never have to worry about null pointer exceptions or memory leaks crashing our server right in the middle of movie night.&lt;/p&gt;

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

&lt;p&gt;Building your own casting app is a fun weekend project that teaches you a lot about WebSockets, multipart uploads, and HTML5 video APIs. Best of all, you get a custom media solution that works entirely on your local network, without any cloud dependencies!&lt;/p&gt;

&lt;p&gt;I probably will be adding more features as time goes on, below is the github repo: &lt;a href="https://github.com/abbeymaniak/cast-app" rel="noopener noreferrer"&gt;https://github.com/abbeymaniak/cast-app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>axum</category>
      <category>react</category>
      <category>websocket</category>
    </item>
    <item>
      <title>OAuth vs OAuth 2.0 Explained Simply for Beginners</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Fri, 01 May 2026 18:34:00 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/oauth-vs-oauth-20-explained-simply-for-beginners-15bp</link>
      <guid>https://dev.to/abbeymaniak/oauth-vs-oauth-20-explained-simply-for-beginners-15bp</guid>
      <description>&lt;p&gt;If you’ve ever clicked "Continue with Google" or "Login with Facebook", then you’ve already used OAuth 2.0 even if you didn’t know it.&lt;/p&gt;

&lt;p&gt;As developers, especially backend developers, understanding OAuth is important because it powers modern authentication and authorization systems across APIs, web apps, and mobile applications.&lt;/p&gt;

&lt;p&gt;Let’s break it down in the simplest way possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does OAuth Solve?
&lt;/h2&gt;

&lt;p&gt;Imagine a third-party app wants access to your Google contacts or GitHub repositories.&lt;/p&gt;

&lt;p&gt;Before OAuth existed, the only way to do this was to give the app your actual username and password.&lt;/p&gt;

&lt;p&gt;That created huge security problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app could store your password&lt;/li&gt;
&lt;li&gt;Your credentials could be leaked if the app got hacked&lt;/li&gt;
&lt;li&gt;You had no control over what the app could access
OAuth solved this problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of sharing your password, OAuth allows applications to access specific parts of your account using temporary tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth in Simple Terms
&lt;/h2&gt;

&lt;p&gt;OAuth is an authorization framework that allows one application to access another application’s resources on behalf of a user without exposing the user’s password.&lt;/p&gt;

&lt;p&gt;In simple English:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;OAuth lets users give limited access to their data without sharing login credentials.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How OAuth 2.0 Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s the basic flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: User Clicks Login&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A user clicks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continue with Google&lt;/li&gt;
&lt;li&gt;Sign in with GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Redirect to Provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The app redirects the user to the provider (Google, GitHub, Facebook).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: User Grants Permission&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The provider asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you allow this app to access your profile/email?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Token Issued&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If approved, the provider generates an access token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Access Granted&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application uses that token to access allowed resources.&lt;/p&gt;

&lt;p&gt;No password is shared with the third-party application.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth vs OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 is simply the modern version of OAuth.&lt;/p&gt;

&lt;p&gt;It was designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simpler&lt;/li&gt;
&lt;li&gt;Faster&lt;/li&gt;
&lt;li&gt;More flexible&lt;/li&gt;
&lt;li&gt;Better for APIs and mobile apps
Today, OAuth 2.0 is the industry standard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Important OAuth 2.0 Terms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Access Token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A temporary token used to access resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refresh Token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used to generate a new access token when the old one expires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defines what the app can access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;li&gt;profile&lt;/li&gt;
&lt;li&gt;contacts&lt;/li&gt;
&lt;li&gt;repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authorization Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server responsible for issuing tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server that stores the protected data.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth Is NOT Authentication
&lt;/h2&gt;

&lt;p&gt;One of the biggest beginner mistakes is thinking OAuth is authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OAuth handles:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authorization -&amp;gt; What can this app access?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Authentication answers:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is this user?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why technologies like OpenID Connect exist on top of OAuth 2.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Backend Developers Should Learn OAuth
&lt;/h2&gt;

&lt;p&gt;As a backend developer, OAuth 2.0 is everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API authentication&lt;/li&gt;
&lt;li&gt;Social login systems&lt;/li&gt;
&lt;li&gt;Microservices&lt;/li&gt;
&lt;li&gt;SaaS integrations&lt;/li&gt;
&lt;li&gt;Mobile app authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frameworks like Laravel make implementation easier using tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Passport&lt;/li&gt;
&lt;li&gt;Laravel Sanctum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding OAuth helps you build more secure systems and integrate with modern platforms confidently.&lt;/p&gt;

&lt;p&gt;If you’re learning backend development, mastering OAuth 2.0 is one of the most valuable concepts you can add to your skill set.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>backend</category>
    </item>
    <item>
      <title>Understanding and Fixing the N+1 Query Problem in Laravel</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Wed, 22 Apr 2026 11:28:16 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/understanding-and-fixing-the-n1-query-problem-in-laravel-2pac</link>
      <guid>https://dev.to/abbeymaniak/understanding-and-fixing-the-n1-query-problem-in-laravel-2pac</guid>
      <description>&lt;p&gt;If you’ve ever built a Laravel app that felt fast locally but slow in production, there’s a good chance you’ve encountered the N+1 query problem.&lt;/p&gt;

&lt;p&gt;It’s one of the most common performance issues in backend development and many developers don’t realize it’s happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the N+1 Query Problem?
&lt;/h2&gt;

&lt;p&gt;The N+1 problem occurs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You run 1 query to fetch data&lt;/li&gt;
&lt;li&gt;Then N additional queries to fetch related data
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have 10 users, Laravel executes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query for users&lt;/li&gt;
&lt;li&gt;10 queries for posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;11 queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why This is Dangerous&lt;/p&gt;

&lt;p&gt;At small scale, it looks harmless.&lt;/p&gt;

&lt;p&gt;At scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 users = 101 queries&lt;/li&gt;
&lt;li&gt;1000 users = 1001 queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow response times&lt;/li&gt;
&lt;li&gt;High database load&lt;/li&gt;
&lt;li&gt;Poor user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: Eager Loading
&lt;/h2&gt;

&lt;p&gt;Laravel provides a simple fix using &lt;code&gt;with()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Laravel executes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query for users&lt;/li&gt;
&lt;li&gt;1 query for posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;2 queries&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Deeper: Nested Relationships
&lt;/h2&gt;

&lt;p&gt;The problem becomes worse with nested relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts.comments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&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;Quick Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always inspect queries using Laravel Debugbar&lt;/li&gt;
&lt;li&gt;Be cautious with API resources and transformers&lt;/li&gt;
&lt;li&gt;Use eager loading by default when returning relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The N+1 query problem is easy to miss—but expensive to ignore.&lt;/p&gt;

&lt;p&gt;If you’re serious about building scalable Laravel applications, mastering this concept is essential.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Built a Fully Offline AI Assistant on My Mac (Using Local LLMs)</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Mon, 13 Apr 2026 18:31:50 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/built-a-fully-offline-ai-assistant-on-my-mac-using-local-llms-3nhj</link>
      <guid>https://dev.to/abbeymaniak/built-a-fully-offline-ai-assistant-on-my-mac-using-local-llms-3nhj</guid>
      <description>&lt;p&gt;Most AI apps today rely on APIs.&lt;/p&gt;

&lt;p&gt;I wanted something different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully offline AI assistant&lt;/li&gt;
&lt;li&gt; Runs locally on my Mac&lt;/li&gt;
&lt;li&gt; No API keys, no internet required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

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

&lt;p&gt;A local AI assistant that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs using a small language model (LLaMA / Mistral)&lt;/li&gt;
&lt;li&gt;Works entirely offline&lt;/li&gt;
&lt;li&gt;Supports chat + memory&lt;/li&gt;
&lt;li&gt;Can be extended into a RAG system&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CLI / Terminal] 
      ↓ 
[Node.js App] 
      ↓ 
[Ollama (Local AI Engine)] 
      ↓ 
[Local LLM]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1 — Install Ollama on your local Machine
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run qwen2.5:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I choose qwen:2.5:latest because its just 4.7GB in size&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Build CLI Assistant
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;local-ai-assistant 
&lt;span class="nb"&gt;cd &lt;/span&gt;local-ai-assistant 
npm init &lt;span class="nt"&gt;-y&lt;/span&gt; 
npm &lt;span class="nb"&gt;install &lt;/span&gt;axios readline-sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create a file in the local-ai-assistant folder&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;assistant.js&lt;/strong&gt;&lt;/em&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="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/usr/&lt;/span&gt;&lt;span class="nx"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&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;readline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;readline-sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&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;chat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;history&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;response&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;axios&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;http://localhost:11434/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qwen2.5:latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&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;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nx"&gt;history&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 — Make It a Global CLI Tool
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json:&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local-ai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"local-ai"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./assistant.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;local-ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Local LLMs are powerful enough for real use cases&lt;/li&gt;
&lt;li&gt;You don’t need Python to start — Node works fine&lt;/li&gt;
&lt;li&gt;The real value is in architecture, not just models&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What i intend to build next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add file-based memory (RAG)&lt;/li&gt;
&lt;li&gt;Add tools (agent behavior)&lt;/li&gt;
&lt;li&gt;Connect to Laravel API&lt;/li&gt;
&lt;li&gt;Deploy via Docker + AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Running AI locally changes how you think about systems.&lt;/p&gt;

&lt;p&gt;You’re no longer dependent on external APIs — you own the stack.&lt;/p&gt;

&lt;p&gt;If you’re building something similar, I’d love to connect &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/abbeymaniak" rel="noopener noreferrer"&gt;https://github.com/abbeymaniak&lt;/a&gt;&lt;br&gt;
&lt;a href="https://linkedin.com/in/abiodun-paul-ogunnaike" rel="noopener noreferrer"&gt;https://linkedin.com/in/abiodun-paul-ogunnaike&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>node</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
