<?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>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>
    <item>
      <title>Stateless vs. Stateful Systems in Software Architecture</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Fri, 27 Mar 2026 19:58:22 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/stateless-vs-stateful-systems-in-software-architecture-nl8</link>
      <guid>https://dev.to/abbeymaniak/stateless-vs-stateful-systems-in-software-architecture-nl8</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;TD;LR:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Stateless systems scale easily and recover quickly.&lt;/em&gt; (Scale with stateless.)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Stateful systems enable deeper, continuous user experiences.&lt;/em&gt; (Experience with stateful.)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Great architecture isn’t about choosing one it’s about placing state intentionally.&lt;/em&gt; (Win by controlling where state lives.)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Modern software architecture often comes down to one fundamental question: Should your system remember anything between requests? That single decision shapes scalability, complexity, performance, and even cost. Let’s break it down clearly and practically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does “Remembering Between Requests” Really Mean?
&lt;/h2&gt;

&lt;p&gt;When a system remembers, it retains information about a user or process across multiple interactions.&lt;/p&gt;

&lt;p&gt;A stateful system keeps that memory internally (e.g., user session stored on a server).&lt;br&gt;
A stateless system treats every request as independent no memory of the past.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;p&gt;Stateful = a waiter who remembers your order without asking again&lt;br&gt;
Stateless = a waiter who asks you to repeat your order every time&lt;/p&gt;

&lt;h2&gt;
  
  
  Stateless Systems: Simple, Scalable, Cloud-Friendly
&lt;/h2&gt;

&lt;p&gt;A stateless system does not store client context between requests. Each request must contain all the information needed to process it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No session stored on the server
State is passed via: &lt;/li&gt;
&lt;li&gt;   Tokens (e.g., JWT)&lt;/li&gt;
&lt;li&gt;   Query params / headers&lt;/li&gt;
&lt;li&gt;   External storage (DB, cache)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Horizontal scalability (effortless scaling)&lt;/strong&gt;&lt;br&gt;
You can spin up multiple servers behind a load balancer without worrying about where a user’s data lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fault tolerance&lt;/strong&gt;&lt;br&gt;
If one server dies, another can handle the next request seamlessly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Simpler infrastructure&lt;/strong&gt;&lt;br&gt;
No need for session replication or sticky sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Larger request sizes (state sent every time)&lt;/li&gt;
&lt;li&gt;More work pushed to clients or external systems&lt;/li&gt;
&lt;li&gt;Can feel less intuitive for complex workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world examples&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;Microservices architectures&lt;/li&gt;
&lt;li&gt;Serverless functions (e.g., AWS Lambda)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stateful Systems: Powerful but Complex
&lt;/h2&gt;

&lt;p&gt;A stateful system stores data about a user/session on the server across multiple requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server stores session data (memory, Redis, DB)&lt;/li&gt;
&lt;li&gt;Client sends a session ID (cookie)&lt;/li&gt;
&lt;li&gt;Server retrieves stored context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Simpler client logic&lt;/strong&gt;&lt;br&gt;
The server “remembers,” so the client doesn’t have to send everything every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Efficient for long-lived interactions&lt;/strong&gt;&lt;br&gt;
   Great for workflows like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shopping carts&lt;/li&gt;
&lt;li&gt;Multiplayer games&lt;/li&gt;
&lt;li&gt;Real-time collaboration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Harder to scale&lt;/strong&gt;&lt;br&gt;
You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sticky sessions, or&lt;/li&gt;
&lt;li&gt;Shared session stores (e.g., Redis)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Reduced fault tolerance&lt;/strong&gt;&lt;br&gt;
If the server holding the session crashes, state may be lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Operational complexity&lt;/strong&gt;&lt;br&gt;
Session replication, synchronization, and consistency become real challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hybrid Reality (What Most Systems Actually Do)
&lt;/h2&gt;

&lt;p&gt;Here’s the truth: most modern systems are not purely stateless or stateful—they’re a mix.&lt;/p&gt;

&lt;p&gt;A common pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep application servers stateless
Store state in external systems:&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;Caches (Redis)&lt;/li&gt;
&lt;li&gt;Object storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stateless scalability&lt;/li&gt;
&lt;li&gt;Stateful capability (via external persistence)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Example in &lt;strong&gt;Celebrateme.co&lt;/strong&gt; a digital platform focused on helping people to never miss important moments like birthdays and making those moments more meaningful with Physical and digital gifts currently in development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API is stateless&lt;/li&gt;
&lt;li&gt;Authentication uses JWT (stateless)&lt;/li&gt;
&lt;li&gt;User data lives in a database (stateful, but externalized)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following Non-Function Requirements will determine which approach to take:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. How much scale do you need?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Massive / unpredictable traffic → Stateless (JWT)&lt;/li&gt;
&lt;li&gt;Controlled / predictable → Stateful can work (cookies)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. How critical is resilience?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High uptime requirement → Stateless wins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. How complex is the interaction?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-step workflows → Stateful or hybrid&lt;/li&gt;
&lt;li&gt;Simple request-response → Stateless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. What’s your team’s operational maturity?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small team → stateless reduces headaches&lt;/li&gt;
&lt;li&gt;Experienced infra team → stateful is manageable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;My advice: default to stateless, and introduce state only where it’s truly needed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This keeps your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier to scale&lt;/li&gt;
&lt;li&gt;Easier to debug&lt;/li&gt;
&lt;li&gt;Easier to deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Stateless systems optimize for scale and resilience&lt;/em&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Stateful systems optimize for rich, continuous interactions.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The best architects don’t pick one blindly, they control where state lives.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>10 REST API MISTAKES TO AVOID</title>
      <dc:creator>Abiodun Paul Ogunnaike</dc:creator>
      <pubDate>Fri, 20 Mar 2026 15:47:55 +0000</pubDate>
      <link>https://dev.to/abbeymaniak/10-rest-api-mistakes-to-avoid-1a94</link>
      <guid>https://dev.to/abbeymaniak/10-rest-api-mistakes-to-avoid-1a94</guid>
      <description>&lt;p&gt;If you're building APIs with Wordpress, Laravel or Node.js, this might hit a bit close to home&lt;/p&gt;

&lt;p&gt;A lot of developers aren’t really designing APIs&lt;br&gt;
they’re just returning JSON and calling it a day.&lt;/p&gt;

&lt;p&gt;And that’s where things start to fall apart.&lt;/p&gt;

&lt;p&gt;Because a solid API isn’t just something that “works.”&lt;br&gt;
It should be consistent, scalable, predictable, and easy for other developers to use.&lt;/p&gt;

&lt;p&gt;Let’s break down some of the most common mistakes and how to fix them 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 1. Using Verbs in URLs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /getBooks  
POST /createBook 
DELETE /deleteBook/1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /books  
POST /books 
DELETE /books/1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;👉 URLs should represent resources (nouns) — not actions.&lt;br&gt;
HTTP methods already define the action.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 2. Ignoring HTTP Status Codes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;200 OK
{ "status": "error", "message": "User not found" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;200 → Success&lt;/li&gt;
&lt;li&gt;201 → Created&lt;/li&gt;
&lt;li&gt;400 → Bad Request&lt;/li&gt;
&lt;li&gt;401 → Unauthorized&lt;/li&gt;
&lt;li&gt;403 → Forbidden&lt;/li&gt;
&lt;li&gt;404 → Not Found&lt;/li&gt;
&lt;li&gt;422 → Validation Error&lt;/li&gt;
&lt;li&gt;500 → Server Error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Status codes are part of your API contract.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 3. Inconsistent JSON Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"userName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paul"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paul"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick one style: snake_case OR camelCase&lt;/li&gt;
&lt;li&gt;Use it everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;👉 Consistency builds trust.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 4. No API Versioning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/v1/books
/api/v2/books
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Versioning prevents breaking existing clients.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 5. No Pagination&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /books → returns 10,000 records

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /books?page=1&amp;amp;limit=10
{
  "data": [],
  "meta": {
    "current_page": 1,
    "total": 1000
  }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Pagination improves performance, scalability, and UX.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 6. Mixing Authentication &amp;amp; Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication = Who are you?&lt;/li&gt;
&lt;li&gt;Authorization = What can you do?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👉 Use proper tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT / OAuth&lt;/li&gt;
&lt;li&gt;Laravel Sanctum / Passport&lt;/li&gt;
&lt;li&gt;Never trust frontend validation alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ 7. Poor Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Something went wrong"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Book not found"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Errors should be predictable and structured.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 8. Bad Filtering &amp;amp; Sorting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /getActiveEventsSortedByName

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /events?status=active&amp;amp;sort=name
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Keep it clean, flexible, and scalable.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ 9. Ignoring Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No rate limiting&lt;/li&gt;
&lt;li&gt;No validation&lt;/li&gt;
&lt;li&gt;No HTTPS&lt;/li&gt;
&lt;li&gt;Exposing sensitive data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👉 Best practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use HTTPS&lt;/li&gt;
&lt;li&gt;Validate all inputs&lt;/li&gt;
&lt;li&gt;Add rate limiting&lt;/li&gt;
&lt;li&gt;Hide sensitive/internal fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ 10. Designing APIs Around Your Database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad mindset&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“This is my table structure”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good mindset&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“This is what the client needs”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;👉 Your API is a contract, not a database mirror.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Good APIs Look Like&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versioned endpoints&lt;/li&gt;
&lt;li&gt;Consistent naming&lt;/li&gt;
&lt;li&gt;Proper status codes&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;li&gt;Filtering &amp;amp; sorting&lt;/li&gt;
&lt;li&gt;Secure authentication&lt;/li&gt;
&lt;li&gt;Structured errors&lt;/li&gt;
&lt;li&gt;Resource-based URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;REST API design isn’t about returning JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistency&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Predictability&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Developer Experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When your API is clean and predictable…&lt;br&gt;
developers love working with it.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>restapi</category>
      <category>webdev</category>
      <category>data</category>
      <category>php</category>
    </item>
  </channel>
</rss>
