<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mohamed Idris</title>
    <description>The latest articles on Forem by Mohamed Idris (@edriso).</description>
    <link>https://forem.com/edriso</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%2F542036%2F3d38f955-495f-4ee8-9445-db0d27f2fd7b.png</url>
      <title>Forem: Mohamed Idris</title>
      <link>https://forem.com/edriso</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/edriso"/>
    <language>en</language>
    <item>
      <title>🧠 The Backend Developer Fundamentals Guide</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Wed, 08 Apr 2026 11:22:54 +0000</pubDate>
      <link>https://forem.com/edriso/the-backend-developer-fundamentals-guide-3j55</link>
      <guid>https://forem.com/edriso/the-backend-developer-fundamentals-guide-3j55</guid>
      <description>&lt;h3&gt;
  
  
  &lt;em&gt;Understand the engine before you drive the car.&lt;/em&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide teaches you the &lt;strong&gt;concepts behind every framework&lt;/strong&gt;. Learn this once, and Spring, Node, Laravel, Django — they'll all just be different flavors of the same thing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;How The Internet Actually Works&lt;/li&gt;
&lt;li&gt;Request &amp;amp; Response — The Heart of Everything&lt;/li&gt;
&lt;li&gt;What a Server Actually Does&lt;/li&gt;
&lt;li&gt;How Data Flows Through a System&lt;/li&gt;
&lt;li&gt;Memory, Threads &amp;amp; Processes&lt;/li&gt;
&lt;li&gt;Databases — Where Data Lives&lt;/li&gt;
&lt;li&gt;APIs — How Systems Talk to Each Other&lt;/li&gt;
&lt;li&gt;Authentication &amp;amp; Authorization&lt;/li&gt;
&lt;li&gt;Caching — Making Things Fast&lt;/li&gt;
&lt;li&gt;Security Basics&lt;/li&gt;
&lt;li&gt;Architecture Patterns&lt;/li&gt;
&lt;li&gt;Networking Essentials&lt;/li&gt;
&lt;li&gt;Linux &amp;amp; The Command Line&lt;/li&gt;
&lt;li&gt;Version Control (Git)&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;DevOps Basics&lt;/li&gt;
&lt;li&gt;The Learning Order (Roadmap)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. How The Internet Actually Works
&lt;/h2&gt;

&lt;p&gt;Before you write a single line of backend code, you need to understand how the internet works. It's simpler than you think.&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS — The Phone Book of the Internet
&lt;/h3&gt;

&lt;p&gt;When you type &lt;code&gt;google.com&lt;/code&gt; in your browser, your computer doesn't know what that means. Computers only understand numbers (IP addresses like &lt;code&gt;142.250.80.46&lt;/code&gt;). So your computer asks a &lt;strong&gt;DNS server&lt;/strong&gt; (Domain Name System): "Hey, what's the IP address for google.com?"&lt;/p&gt;

&lt;p&gt;Think of it like this: you know your friend's name, but you need their phone number to call them. DNS is the contact list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;br&gt;
You type &lt;code&gt;google.com&lt;/code&gt; → Your computer asks DNS → DNS says "that's &lt;code&gt;142.250.80.46&lt;/code&gt;" → Your computer connects to that IP address.&lt;/p&gt;
&lt;h3&gt;
  
  
  TCP/IP — The Delivery System
&lt;/h3&gt;

&lt;p&gt;Once your computer knows the IP address, it needs to send and receive data. That's where TCP/IP comes in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP (Internet Protocol):&lt;/strong&gt; This is like the address on a package. It tells data where to go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TCP (Transmission Control Protocol):&lt;/strong&gt; This makes sure the data arrives completely and in order. It's like a delivery service that makes you sign for the package — it confirms everything arrived.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters for backend:&lt;/strong&gt; Every single thing your server does involves TCP/IP. When a user opens your app, their phone creates a TCP connection to your server. Understanding this helps you debug network issues later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ports — Doors Into Your Server
&lt;/h3&gt;

&lt;p&gt;Your server is like a building, and &lt;strong&gt;ports&lt;/strong&gt; are the doors. Different services use different doors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port &lt;strong&gt;80&lt;/strong&gt; → Regular web traffic (HTTP)&lt;/li&gt;
&lt;li&gt;Port &lt;strong&gt;443&lt;/strong&gt; → Secure web traffic (HTTPS)&lt;/li&gt;
&lt;li&gt;Port &lt;strong&gt;5432&lt;/strong&gt; → PostgreSQL database&lt;/li&gt;
&lt;li&gt;Port &lt;strong&gt;3306&lt;/strong&gt; → MySQL database&lt;/li&gt;
&lt;li&gt;Port &lt;strong&gt;27017&lt;/strong&gt; → MongoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When someone visits your website, they're knocking on port 80 or 443 of your server.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTTP/HTTPS — The Language of the Web
&lt;/h3&gt;

&lt;p&gt;HTTP (HyperText Transfer Protocol) is the language browsers and servers speak. HTTPS is the same thing but encrypted (the "S" stands for Secure).&lt;/p&gt;

&lt;p&gt;Every time you load a webpage, your browser sends an HTTP &lt;strong&gt;request&lt;/strong&gt;, and the server sends back an HTTP &lt;strong&gt;response&lt;/strong&gt;. That's literally all web development is at its core — requests and responses.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Request &amp;amp; Response — The Heart of Everything
&lt;/h2&gt;

&lt;p&gt;This is the &lt;strong&gt;single most important concept&lt;/strong&gt; in backend development. Everything else is built on top of this.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is a Request?
&lt;/h3&gt;

&lt;p&gt;A request is a message from a client (browser, mobile app, another server) asking your server to do something. Every request has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Method (Verb):&lt;/strong&gt; What action do you want?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; → "Give me some data" (like loading a page)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; → "Here's some new data, save it" (like submitting a form)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT&lt;/code&gt; → "Update this existing data completely"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PATCH&lt;/code&gt; → "Update just part of this data"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; → "Remove this data"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;URL (Path):&lt;/strong&gt; What resource do you want?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/users&lt;/code&gt; → the users resource&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/users/42&lt;/code&gt; → user number 42 specifically&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/users/42/orders&lt;/code&gt; → orders belonging to user 42&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Headers:&lt;/strong&gt; Extra info about the request (like metadata)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Content-Type: application/json&lt;/code&gt; → "I'm sending JSON data"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Authorization: Bearer abc123&lt;/code&gt; → "Here's my login token"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accept: application/json&lt;/code&gt; → "Please respond with JSON"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Body:&lt;/strong&gt; The actual data (only for POST, PUT, PATCH)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ahmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ahmed@example.com"&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;h3&gt;
  
  
  What is a Response?
&lt;/h3&gt;

&lt;p&gt;The response is your server's answer. Every response has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Status Code:&lt;/strong&gt; A number that tells the client what happened&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; → "OK, here's what you asked for"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;201&lt;/code&gt; → "Created — I made the new thing you wanted"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; → "Bad Request — you sent me something wrong"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt; → "Unauthorized — who are you? Log in first"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403&lt;/code&gt; → "Forbidden — I know who you are, but you can't do this"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; → "Not Found — that thing doesn't exist"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;500&lt;/code&gt; → "Internal Server Error — I broke, it's my fault"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Headers:&lt;/strong&gt; Extra info about the response&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Body:&lt;/strong&gt; The actual data being sent back&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ahmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ahmed@example.com"&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;h3&gt;
  
  
  The Full Picture
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks "Sign Up" button
        ↓
Browser sends POST request to /api/users
  with body: { name: "Ahmed", email: "ahmed@example.com", password: "..." }
        ↓
Server receives the request
        ↓
Server validates the data (is the email real? is the password strong enough?)
        ↓
Server hashes the password (never store plain passwords!)
        ↓
Server saves the user to the database
        ↓
Server sends back a response: 201 Created
  with body: { id: 42, name: "Ahmed", message: "Account created!" }
        ↓
Browser shows "Welcome, Ahmed!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;This flow is identical in every framework.&lt;/strong&gt; Express, Spring, Django, Laravel — they all do this exact thing. The only difference is the syntax.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. What a Server Actually Does
&lt;/h2&gt;

&lt;p&gt;A server is just a computer that's always on, always connected to the internet, and always listening for requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Server's Job, Step by Step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Listen&lt;/strong&gt; — The server sits and waits on a specific port (usually 80 or 443), doing nothing until someone sends a request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receive&lt;/strong&gt; — A request arrives. The server reads the method, URL, headers, and body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route&lt;/strong&gt; — The server figures out which piece of code should handle this request. "Oh, they want &lt;code&gt;GET /users/42&lt;/code&gt;? Let me call the &lt;code&gt;getUserById&lt;/code&gt; function."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process&lt;/strong&gt; — The actual business logic happens here: validate data, talk to the database, calculate things, call other services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond&lt;/strong&gt; — The server builds a response with a status code and data, then sends it back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go back to step 1&lt;/strong&gt; — The server goes back to listening for the next request.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Middleware — The Assembly Line
&lt;/h3&gt;

&lt;p&gt;In most frameworks, before your request reaches the actual handler, it passes through a chain of &lt;strong&gt;middleware&lt;/strong&gt;. Think of it like a factory assembly line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request arrives
    ↓
[Logging Middleware] — "Let me record that this request happened"
    ↓
[Auth Middleware] — "Let me check if this user is logged in"
    ↓
[Validation Middleware] — "Let me check if the data is correct"
    ↓
[Your Actual Handler] — "OK, now I'll do the real work"
    ↓
Response goes back
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every framework has middleware. Express calls it middleware. Django calls it middleware. Spring calls it filters/interceptors. Laravel calls it middleware. Same concept, different names.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. How Data Flows Through a System
&lt;/h2&gt;

&lt;p&gt;When you build a real app, data moves through multiple layers. Understanding this flow is what separates someone who just copies code from someone who actually understands what's happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Typical Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLIENT (Browser/App)
    ↕ HTTP Request/Response
CONTROLLER / ROUTE HANDLER
    → Receives the request, extracts data from it
    → Calls the right service
    ↕
SERVICE / BUSINESS LOGIC
    → The "brain" — decides what to do
    → Applies rules: "Users under 18 can't buy this"
    → Coordinates between different pieces
    ↕
REPOSITORY / DATA ACCESS
    → Talks to the database
    → Translates between your code's objects and database rows
    ↕
DATABASE
    → Stores data permanently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Layers Matter
&lt;/h3&gt;

&lt;p&gt;Imagine you're building a food delivery app. A user places an order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Controller&lt;/strong&gt; receives: &lt;code&gt;POST /orders&lt;/code&gt; with &lt;code&gt;{ restaurantId: 5, items: [...] }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service&lt;/strong&gt; checks: Is the restaurant open? Does the user have enough balance? Is there a driver available?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt; saves the order to the database, updates the user's balance, notifies the restaurant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response&lt;/strong&gt; goes back: &lt;code&gt;201 Created&lt;/code&gt; with the order details&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you dump all of this into one file, it becomes an unreadable mess. Layers keep things organized and reusable. Every professional codebase uses layers, regardless of the framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Memory, Threads &amp;amp; Processes
&lt;/h2&gt;

&lt;p&gt;This is where most tutorials skip and where most junior devs get confused. But it's essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory — Your Program's Workspace
&lt;/h3&gt;

&lt;p&gt;When your server starts, the operating system gives it a chunk of &lt;strong&gt;RAM&lt;/strong&gt; (memory). Think of RAM like a desk — it's where your program puts things it's currently working with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack Memory:&lt;/strong&gt; Fast, organized, automatic. Used for simple variables and function calls. When a function finishes, its stack memory is automatically cleaned up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap Memory:&lt;/strong&gt; Bigger, messier, manual (or garbage-collected). Used for objects, arrays, and anything complex. Stays around until your code (or the garbage collector) removes it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; If your server creates tons of objects and never cleans them up, you get a &lt;strong&gt;memory leak&lt;/strong&gt;. Your server slowly uses more and more RAM until it crashes. This is a real-world problem you'll face.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processes — Independent Workers
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;process&lt;/strong&gt; is a running program. When you start your server, that's one process. Each process gets its own memory — processes don't share memory with each other.&lt;/p&gt;

&lt;p&gt;If your server crashes, the process dies. That's why in production we use tools that automatically restart crashed processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Threads — Workers Inside a Worker
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;thread&lt;/strong&gt; is like a worker inside a process. One process can have multiple threads, and they all share the same memory.&lt;/p&gt;

&lt;p&gt;Think of a restaurant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;process&lt;/strong&gt; is the restaurant itself&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;threads&lt;/strong&gt; are the waiters&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;memory&lt;/strong&gt; is the kitchen and the order board&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple waiters (threads) can serve different tables (requests) at the same time, and they all share the same kitchen (memory).&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking vs Non-Blocking
&lt;/h3&gt;

&lt;p&gt;This is a critical concept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blocking:&lt;/strong&gt; When your code does something slow (like reading a file or querying a database) and the thread just sits and waits. Nothing else can happen on that thread until it's done. Like a waiter standing at the kitchen window waiting for one dish instead of taking other orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Non-Blocking (Async):&lt;/strong&gt; The thread says "I'll come back for this later" and goes to handle other requests. When the slow operation finishes, it picks up where it left off. Like a waiter who takes another table's order while the kitchen cooks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt; is single-threaded but non-blocking (one waiter who never stops moving). &lt;strong&gt;Java/Spring&lt;/strong&gt; is multi-threaded (multiple waiters, each can wait). Both approaches work; they have different tradeoffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency vs Parallelism
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency:&lt;/strong&gt; Handling multiple tasks by switching between them quickly (one CPU core, many tasks). Like one person cooking three dishes by switching between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallelism:&lt;/strong&gt; Actually doing multiple tasks at the exact same time (multiple CPU cores). Like three people each cooking one dish.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to master this immediately, but keep it in mind — as your app grows, these concepts will determine if your server can handle 100 users or 100,000.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Databases — Where Data Lives
&lt;/h2&gt;

&lt;p&gt;Memory is temporary — when your server restarts, everything in RAM is gone. Databases store data &lt;strong&gt;permanently&lt;/strong&gt; (on disk).&lt;/p&gt;

&lt;h3&gt;
  
  
  Relational Databases (SQL)
&lt;/h3&gt;

&lt;p&gt;These store data in &lt;strong&gt;tables&lt;/strong&gt; (like spreadsheets). Examples: PostgreSQL, MySQL, SQLite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;USERS TABLE:
| id | name   | email              | created_at |
|----|--------|--------------------|------------|
| 1  | Ahmed  | ahmed@example.com  | 2025-01-15 |
| 2  | Sara   | sara@example.com   | 2025-02-20 |

ORDERS TABLE:
| id | user_id | product     | amount |
|----|---------|-------------|--------|
| 1  | 1       | Laptop      | 999    |
| 2  | 1       | Mouse       | 25     |
| 3  | 2       | Keyboard    | 75     |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;user_id&lt;/code&gt; in ORDERS &lt;strong&gt;relates&lt;/strong&gt; to the &lt;code&gt;id&lt;/code&gt; in USERS. That's why they're called "relational." You can ask: "Give me all orders for user Ahmed" by joining the tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL (Structured Query Language)&lt;/strong&gt; is how you talk to these databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Get all users&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Get a specific user&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Add a new user&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ali'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ali@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Update a user&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Ahmed Ali'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Delete a user&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Get all orders for a user (JOIN)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Learn SQL. It's not optional.&lt;/strong&gt; Every backend framework uses SQL under the hood, even when they hide it behind an ORM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key SQL Concepts You Must Know
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary Key:&lt;/strong&gt; The unique ID for each row (usually &lt;code&gt;id&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foreign Key:&lt;/strong&gt; A column that references another table's primary key (like &lt;code&gt;user_id&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index:&lt;/strong&gt; A shortcut that makes searching faster. Like a book's index — instead of reading every page, you jump straight to the right one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions:&lt;/strong&gt; A way to group multiple operations. Either ALL of them succeed, or NONE of them do. Think of transferring money: subtract from account A AND add to account B. You can't do just one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalization:&lt;/strong&gt; Organizing data to avoid repetition. Instead of writing "Ahmed, &lt;a href="mailto:ahmed@email.com"&gt;ahmed@email.com&lt;/a&gt;" on every order, you just reference user_id = 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrations:&lt;/strong&gt; Version control for your database structure. When you need to add a new column, you write a migration file. This way, everyone on the team can update their database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NoSQL Databases
&lt;/h3&gt;

&lt;p&gt;These don't use tables. Instead, they use different structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document DBs (MongoDB):&lt;/strong&gt; Store data as JSON-like documents. Good for flexible or nested data.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ahmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ahmed@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"orders"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Laptop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mouse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key-Value (Redis):&lt;/strong&gt; Super simple. A key and a value. Blazing fast. Used for caching.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"session:user42"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{ loggedIn: true, name: 'Ahmed' }"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When to use what?&lt;/strong&gt; Start with PostgreSQL. It handles 95% of use cases. Use Redis for caching. Use MongoDB only if your data truly doesn't fit into tables. Don't overcomplicate your first projects.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. APIs — How Systems Talk to Each Other
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;API&lt;/strong&gt; (Application Programming Interface) is a set of rules that lets different software talk to each other. Your backend's main job is to expose an API that frontends (or other services) can use.&lt;/p&gt;

&lt;h3&gt;
  
  
  REST — The Most Common API Style
&lt;/h3&gt;

&lt;p&gt;REST isn't a technology — it's a set of conventions for designing APIs using HTTP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List all users&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/users&lt;/td&gt;
&lt;td&gt;Returns an array of users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get one user&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/api/users/42&lt;/td&gt;
&lt;td&gt;Returns user with id 42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create a user&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/api/users&lt;/td&gt;
&lt;td&gt;Creates a new user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update a user&lt;/td&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/api/users/42&lt;/td&gt;
&lt;td&gt;Replaces user 42's data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete a user&lt;/td&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/api/users/42&lt;/td&gt;
&lt;td&gt;Removes user 42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;REST is just a pattern. It works the same in Express, Django, Spring, Laravel — everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON — The Universal Data Format
&lt;/h3&gt;

&lt;p&gt;JSON (JavaScript Object Notation) is how APIs send data. Even non-JavaScript APIs use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ahmed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isActive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&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="s2"&gt;"developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"student"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&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;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alexandria"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Egypt"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API Design Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;nouns&lt;/strong&gt; for URLs, not verbs: &lt;code&gt;/users&lt;/code&gt; not &lt;code&gt;/getUsers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;plural&lt;/strong&gt; names: &lt;code&gt;/users&lt;/code&gt; not &lt;code&gt;/user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;HTTP methods&lt;/strong&gt; for actions (GET, POST, etc.) instead of putting the action in the URL&lt;/li&gt;
&lt;li&gt;Return &lt;strong&gt;proper status codes&lt;/strong&gt;: don't return &lt;code&gt;200 OK&lt;/code&gt; when something failed&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;pagination&lt;/strong&gt; for lists: &lt;code&gt;/users?page=2&amp;amp;limit=20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Be &lt;strong&gt;consistent&lt;/strong&gt;: if one endpoint uses camelCase, all should&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GraphQL — The Alternative
&lt;/h3&gt;

&lt;p&gt;REST makes you hit multiple endpoints to get related data. GraphQL lets you ask for exactly what you want in one request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&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="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&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="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;orders&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="n"&gt;product&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;Learn REST first. It's the standard. GraphQL is nice to know later.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Authentication &amp;amp; Authorization
&lt;/h2&gt;

&lt;p&gt;These are two different things and mixing them up is a classic mistake.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication (AuthN):&lt;/strong&gt; "Who are you?" → Proving your identity (logging in)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization (AuthZ):&lt;/strong&gt; "What are you allowed to do?" → Checking permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Login Works (The Real Flow)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User sends POST /login with { email, password }
2. Server finds the user in the database
3. Server checks: does the hashed password match?
4. If yes → Server creates a TOKEN (a signed string)
5. Server sends the token back to the client
6. Client stores the token (usually in memory or a cookie)
7. On every future request, client sends the token in the Authorization header
8. Server reads the token, verifies it's valid and not expired
9. Server now knows who this user is
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Passwords — Never Store Plain Text
&lt;/h3&gt;

&lt;p&gt;When a user creates a password, you &lt;strong&gt;hash&lt;/strong&gt; it (turn it into a scrambled string that can't be reversed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Password: "mypassword123"
Hashed:   "$2b$10$N9qo8uLOickgx2ZMRZoMye..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When they log in, you hash what they typed and compare it to the stored hash. You never know their actual password. This means if your database gets stolen, the attacker can't read passwords.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use bcrypt or argon2.&lt;/strong&gt; Every language has a library for this.&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT (JSON Web Tokens)
&lt;/h3&gt;

&lt;p&gt;A JWT is a token that contains information about the user, signed with a secret key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Header.Payload.Signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload might be: &lt;code&gt;{ userId: 42, role: "admin", exp: 1234567890 }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server can verify this token without checking the database every time. The signature proves it wasn't tampered with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sessions vs Tokens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sessions:&lt;/strong&gt; Server stores login state in memory or a database. The client gets a session ID cookie. Server-side state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens (JWT):&lt;/strong&gt; Client holds the token. Server is stateless — it just verifies the signature. No server-side state needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are valid. Tokens are more popular for APIs; sessions are more traditional for web apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth 2.0 — "Login with Google/GitHub"
&lt;/h3&gt;

&lt;p&gt;OAuth lets users log into your app using their Google/GitHub/etc. account. You don't handle their password at all. The basic flow: your app redirects to Google → user logs in there → Google sends a code back to your app → your app exchanges that code for user info.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Caching — Making Things Fast
&lt;/h2&gt;

&lt;p&gt;Caching means storing frequently used data in a fast place so you don't have to recalculate or re-fetch it every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Caching Happens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser Cache:&lt;/strong&gt; The browser stores static files (images, CSS, JS) so it doesn't download them again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN Cache:&lt;/strong&gt; A Content Delivery Network stores your files on servers worldwide, so a user in Egypt gets served from a nearby server instead of one in the US.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Cache (Redis/Memcached):&lt;/strong&gt; Your server stores frequently accessed data in memory. Way faster than hitting the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Cache:&lt;/strong&gt; The database itself caches frequent queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Why Caching Matters
&lt;/h3&gt;

&lt;p&gt;Without cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User requests GET /popular-products
→ Server runs a complex database query (200ms)
→ This happens for EVERY user, EVERY time
→ 1000 users/second = 1000 database queries/second = 💀
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First request: GET /popular-products
→ Server runs the query (200ms)
→ Server stores the result in Redis with a 5-minute expiration
→ Returns result

Next 999 requests in 5 minutes:
→ Server checks Redis first (1ms)
→ Data is there! Returns it instantly
→ Database rests peacefully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Invalidation
&lt;/h3&gt;

&lt;p&gt;The hardest problem in caching: when do you update the cache? If you cache product prices and then change a price, users might see stale data. Strategies include time-based expiration (cache for 5 minutes), event-based invalidation (clear cache when data changes), and cache-aside (check cache first, fall back to database).&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Security Basics
&lt;/h2&gt;

&lt;p&gt;If you build a backend, you're responsible for protecting user data. Here are the most important threats:&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Injection
&lt;/h3&gt;

&lt;p&gt;The attacker puts SQL code inside your input fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"admin@site.com"&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"' OR 1=1 --"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build SQL strings by concatenating user input, this can expose your entire database. &lt;strong&gt;Never do this.&lt;/strong&gt; Always use &lt;strong&gt;parameterized queries&lt;/strong&gt; or your framework's ORM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Site Scripting (XSS)
&lt;/h3&gt;

&lt;p&gt;The attacker injects JavaScript into your site that runs in other users' browsers. For example, putting &lt;code&gt;&amp;lt;script&amp;gt;steal(cookies)&amp;lt;/script&amp;gt;&lt;/code&gt; in a comment field. &lt;strong&gt;Always sanitize user input&lt;/strong&gt; and escape HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Site Request Forgery (CSRF)
&lt;/h3&gt;

&lt;p&gt;The attacker tricks a logged-in user into making a request they didn't intend. Like clicking a link that secretly transfers money. &lt;strong&gt;Use CSRF tokens&lt;/strong&gt; — every framework has built-in protection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Without rate limiting, someone can send millions of requests to your server (DDoS), or try millions of passwords (brute force). &lt;strong&gt;Limit how many requests one IP can make&lt;/strong&gt; per minute.&lt;/p&gt;

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

&lt;p&gt;Always use HTTPS. Never HTTP. HTTPS encrypts data between the client and server so nobody in the middle can read it. Free certificates from Let's Encrypt make this a no-brainer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The OWASP Top 10
&lt;/h3&gt;

&lt;p&gt;OWASP maintains a list of the top 10 web security risks. Read it, understand it, and check your apps against it. This is a checklist every professional developer uses.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Architecture Patterns
&lt;/h2&gt;

&lt;p&gt;As your app grows, how you organize your code matters more and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monolith
&lt;/h3&gt;

&lt;p&gt;Everything in one big application. One codebase, one deployment. &lt;strong&gt;Start here.&lt;/strong&gt; It's simpler, easier to debug, and perfect for learning and small-to-medium apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;

&lt;p&gt;Breaking your app into small, independent services that communicate over the network. The Users service, Orders service, and Payments service are separate apps. &lt;strong&gt;Don't do this until you have a reason to.&lt;/strong&gt; It adds massive complexity. Companies like Netflix and Amazon use it because they have thousands of developers — not because it's always better.&lt;/p&gt;

&lt;h3&gt;
  
  
  MVC (Model-View-Controller)
&lt;/h3&gt;

&lt;p&gt;The most common pattern inside any backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; Represents your data (the User class, the Order class)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View:&lt;/strong&gt; What the user sees (HTML pages, or JSON responses for APIs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controller:&lt;/strong&gt; Handles requests, calls the right models, returns the right views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every framework is built around MVC. Rails, Laravel, Django, Spring MVC — they're all variations of this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event-Driven Architecture
&lt;/h3&gt;

&lt;p&gt;Instead of services calling each other directly, they publish &lt;strong&gt;events&lt;/strong&gt; to a message queue (like RabbitMQ or Kafka). Other services listen and react.&lt;/p&gt;

&lt;p&gt;Example: When someone places an order, the Order Service publishes an "OrderPlaced" event. The Email Service hears it and sends a confirmation. The Inventory Service hears it and updates stock. They don't know about each other — they just react to events.&lt;/p&gt;

&lt;p&gt;This is an advanced pattern. Learn it conceptually now; implement it when you need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Networking Essentials
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The OSI Model (Simplified)
&lt;/h3&gt;

&lt;p&gt;You don't need to memorize all 7 layers. Just know the ones that matter for backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Layer (HTTP, HTTPS, WebSocket):&lt;/strong&gt; Where your code lives. You deal with requests and responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport Layer (TCP, UDP):&lt;/strong&gt; TCP guarantees delivery. UDP is faster but doesn't guarantee (used for video calls, gaming).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Layer (IP):&lt;/strong&gt; Routing data between machines using IP addresses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WebSockets
&lt;/h3&gt;

&lt;p&gt;HTTP is one-way: client asks, server responds. But what about live chat, real-time notifications, or live scores? That's where WebSockets come in.&lt;/p&gt;

&lt;p&gt;A WebSocket is a persistent connection between client and server. Both can send messages at any time, without the client having to ask first. Think of HTTP as sending letters and WebSockets as a phone call — the line stays open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load Balancing
&lt;/h3&gt;

&lt;p&gt;When one server can't handle all the traffic, you add more servers and put a &lt;strong&gt;load balancer&lt;/strong&gt; in front of them. The load balancer distributes incoming requests across your servers. It's like having multiple checkout lines at a supermarket.&lt;/p&gt;

&lt;p&gt;Common strategies: round-robin (take turns), least connections (send to the least busy server), or IP hash (same user always goes to the same server).&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Linux &amp;amp; The Command Line
&lt;/h2&gt;

&lt;p&gt;Most servers run Linux. You don't need to be a Linux expert, but you need to be comfortable in the terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Essential Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigation&lt;/span&gt;
&lt;span class="nb"&gt;pwd&lt;/span&gt;                  &lt;span class="c"&gt;# Where am I?&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt;                   &lt;span class="c"&gt;# What's in this folder?&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/folder   &lt;span class="c"&gt;# Go to a folder&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..                &lt;span class="c"&gt;# Go up one level&lt;/span&gt;

&lt;span class="c"&gt;# Files&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;file.txt         &lt;span class="c"&gt;# Show file contents&lt;/span&gt;
nano file.txt        &lt;span class="c"&gt;# Edit a file&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;file.txt copy.txt &lt;span class="c"&gt;# Copy a file&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;old.txt new.txt   &lt;span class="c"&gt;# Rename/move&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;file.txt          &lt;span class="c"&gt;# Delete a file (careful!)&lt;/span&gt;

&lt;span class="c"&gt;# Searching&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"error"&lt;/span&gt; log.txt &lt;span class="c"&gt;# Find lines containing "error"&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.js"&lt;/span&gt;  &lt;span class="c"&gt;# Find all .js files&lt;/span&gt;

&lt;span class="c"&gt;# Processes&lt;/span&gt;
ps aux               &lt;span class="c"&gt;# Show running processes&lt;/span&gt;
top                  &lt;span class="c"&gt;# Live process monitor&lt;/span&gt;
&lt;span class="nb"&gt;kill &lt;/span&gt;1234            &lt;span class="c"&gt;# Kill process with ID 1234&lt;/span&gt;

&lt;span class="c"&gt;# Networking&lt;/span&gt;
curl http://example.com  &lt;span class="c"&gt;# Make an HTTP request&lt;/span&gt;
ping google.com          &lt;span class="c"&gt;# Check if a server is reachable&lt;/span&gt;

&lt;span class="c"&gt;# Permissions&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;755 script.sh  &lt;span class="c"&gt;# Make a script executable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Package Managers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu/Debian: &lt;code&gt;apt install package-name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;macOS: &lt;code&gt;brew install package-name&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SSH — Remote Server Access
&lt;/h3&gt;

&lt;p&gt;SSH (Secure Shell) lets you control a remote server from your terminal:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You'll use this constantly in your career to manage production servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Version Control (Git)
&lt;/h2&gt;

&lt;p&gt;Git tracks changes to your code. It's not optional — it's required for every developer job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Concepts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository (Repo):&lt;/strong&gt; Your project folder, tracked by Git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit:&lt;/strong&gt; A snapshot of your code at a point in time, with a message explaining what changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch:&lt;/strong&gt; A parallel version of your code. Work on features without affecting the main code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge:&lt;/strong&gt; Combining two branches together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Request (PR):&lt;/strong&gt; A proposal to merge your changes, reviewed by teammates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Essential Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init                    &lt;span class="c"&gt;# Start tracking a project&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;                   &lt;span class="c"&gt;# Stage all changes&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add login"&lt;/span&gt;   &lt;span class="c"&gt;# Save a snapshot&lt;/span&gt;
git branch feature-x        &lt;span class="c"&gt;# Create a new branch&lt;/span&gt;
git checkout feature-x      &lt;span class="c"&gt;# Switch to that branch&lt;/span&gt;
git merge feature-x         &lt;span class="c"&gt;# Merge feature-x into current branch&lt;/span&gt;
git push origin main        &lt;span class="c"&gt;# Upload to GitHub&lt;/span&gt;
git pull origin main        &lt;span class="c"&gt;# Download latest changes&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;           &lt;span class="c"&gt;# See commit history&lt;/span&gt;
git status                  &lt;span class="c"&gt;# See what's changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Branching Strategy
&lt;/h3&gt;

&lt;p&gt;The most common: &lt;code&gt;main&lt;/code&gt; is production-ready. Create a branch for each feature. Merge back to &lt;code&gt;main&lt;/code&gt; via pull request after review. Never commit directly to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Testing
&lt;/h2&gt;

&lt;p&gt;Writing code that verifies your other code works correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests:&lt;/strong&gt; Test a single function in isolation. "Does my &lt;code&gt;calculateTax(100)&lt;/code&gt; return &lt;code&gt;15&lt;/code&gt;?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests:&lt;/strong&gt; Test multiple pieces working together. "Does my API endpoint actually save to the database and return the right response?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-End (E2E) Tests:&lt;/strong&gt; Test the whole system from the user's perspective. "Can a user sign up, log in, and place an order?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Testing Pyramid
&lt;/h3&gt;

&lt;p&gt;Write &lt;strong&gt;many&lt;/strong&gt; unit tests (fast, cheap), &lt;strong&gt;some&lt;/strong&gt; integration tests (medium), and &lt;strong&gt;few&lt;/strong&gt; E2E tests (slow, expensive). Most of your bugs will be caught by unit tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Testing Matters
&lt;/h3&gt;

&lt;p&gt;Without tests, every change you make could break something else without you knowing. With tests, you change code confidently. Tests are your safety net. Every serious company requires them.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. DevOps Basics
&lt;/h2&gt;

&lt;p&gt;DevOps is the bridge between writing code and running code in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker — Portable Environments
&lt;/h3&gt;

&lt;p&gt;Docker packages your app and all its dependencies into a &lt;strong&gt;container&lt;/strong&gt;. "It works on my machine" becomes "it works everywhere."&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Dockerfile&lt;/code&gt; is a recipe for building your container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now anyone can run your app with: &lt;code&gt;docker build -t myapp . &amp;amp;&amp;amp; docker run myapp&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD — Automated Testing &amp;amp; Deployment
&lt;/h3&gt;

&lt;p&gt;CI (Continuous Integration): Every time you push code, automated tests run. If they fail, you know before the code reaches production.&lt;/p&gt;

&lt;p&gt;CD (Continuous Deployment): If tests pass, the code automatically gets deployed to production. No manual steps.&lt;/p&gt;

&lt;p&gt;Tools: GitHub Actions, GitLab CI, Jenkins. GitHub Actions is the easiest to start with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Basics
&lt;/h3&gt;

&lt;p&gt;Your server needs to live somewhere. The big three cloud providers are AWS (Amazon), GCP (Google), and Azure (Microsoft). For starting out, simpler platforms like Railway, Render, or Fly.io let you deploy without cloud complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. The Learning Order (Roadmap)
&lt;/h2&gt;

&lt;p&gt;Here's the order that makes sense. Don't jump ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: The Foundation (Weeks 1–6)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Pick ONE language (JavaScript/Node.js or Python — both are great)&lt;/li&gt;
&lt;li&gt;Learn programming basics: variables, functions, loops, arrays, objects, classes&lt;/li&gt;
&lt;li&gt;Understand how the internet works: HTTP, DNS, TCP/IP&lt;/li&gt;
&lt;li&gt;Learn the command line basics&lt;/li&gt;
&lt;li&gt;Learn Git basics&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Phase 2: Databases &amp;amp; APIs (Weeks 7–12)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Learn SQL (PostgreSQL recommended)&lt;/li&gt;
&lt;li&gt;Build a simple REST API with your language (Express for Node.js, Flask/FastAPI for Python)&lt;/li&gt;
&lt;li&gt;Connect your API to a database&lt;/li&gt;
&lt;li&gt;Learn about request/response, routing, middleware&lt;/li&gt;
&lt;li&gt;Build a CRUD app (Create, Read, Update, Delete) — like a todo list or blog&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Phase 3: Real-World Skills (Weeks 13–20)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add authentication (JWT, password hashing)&lt;/li&gt;
&lt;li&gt;Learn about validation and error handling&lt;/li&gt;
&lt;li&gt;Add caching (Redis)&lt;/li&gt;
&lt;li&gt;Learn basic security (SQL injection, XSS, CSRF)&lt;/li&gt;
&lt;li&gt;Write unit tests&lt;/li&gt;
&lt;li&gt;Build a bigger project (e-commerce API, social media API)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Phase 4: Production &amp;amp; Scale (Weeks 21–28)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Learn Docker&lt;/li&gt;
&lt;li&gt;Deploy your app to the cloud&lt;/li&gt;
&lt;li&gt;Set up CI/CD with GitHub Actions&lt;/li&gt;
&lt;li&gt;Learn about logging and monitoring&lt;/li&gt;
&lt;li&gt;Understand basic architecture patterns (MVC, microservices concepts)&lt;/li&gt;
&lt;li&gt;Study system design basics&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Phase 5: Level Up (Ongoing)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Learn a second framework to prove your concepts transfer&lt;/li&gt;
&lt;li&gt;Study message queues (RabbitMQ/Kafka)&lt;/li&gt;
&lt;li&gt;Learn GraphQL&lt;/li&gt;
&lt;li&gt;Dive deeper into system design&lt;/li&gt;
&lt;li&gt;Contribute to open source&lt;/li&gt;
&lt;li&gt;Read other people's code&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🎯 The Big Takeaway
&lt;/h2&gt;

&lt;p&gt;Frameworks change every few years. JavaScript frameworks are born and die faster than you can learn them. But the concepts in this guide? They've been the same for &lt;strong&gt;decades&lt;/strong&gt; and will stay the same for decades more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP hasn't changed.&lt;/strong&gt; Databases still use SQL. Servers still handle request→process→response. Auth still uses tokens or sessions. Caching still makes things fast.&lt;/p&gt;

&lt;p&gt;When you understand these fundamentals, picking up a new framework takes days, not months. You look at Spring and think "Oh, this is just their way of doing routing and middleware." You look at Django and think "This is just MVC with a different name."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're not learning a framework. You're learning how computers talk to each other.&lt;/strong&gt; The framework is just the language you write it in.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The person who understands WHY will always lead the person who only knows HOW."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good luck on your journey. You've already made the right decision by starting with the fundamentals. 🚀&lt;/p&gt;

&lt;p&gt;&lt;em&gt;credits: claude&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>backend</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>You Don't Need a Mobile App. A Bot Might Be Enough</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:14:04 +0000</pubDate>
      <link>https://forem.com/edriso/you-dont-need-a-mobile-app-a-bot-might-be-enough-139i</link>
      <guid>https://forem.com/edriso/you-dont-need-a-mobile-app-a-bot-might-be-enough-139i</guid>
      <description>&lt;p&gt;You build websites. You're comfortable with APIs, databases, and deployments. But here's a thought — not everyone wants to visit a website.&lt;/p&gt;

&lt;p&gt;Some users want the app to come to them. Open Telegram, tap a button, done. No downloads, no signups, no "allow notifications" popup.&lt;/p&gt;

&lt;p&gt;That's where bots fit in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between websites and apps
&lt;/h2&gt;

&lt;p&gt;You build a website for your idea. It works great on desktop. Maybe okay on mobile. But your target audience — say, kids or busy parents — won't bookmark it. They won't come back every day. They need a nudge.&lt;/p&gt;

&lt;p&gt;A mobile app would solve this, but I'm not familiar with mobile development, and there are two platforms to deal with. App store reviews. Updates. It's a whole different world.&lt;/p&gt;

&lt;p&gt;A bot? You can build one in a weekend on Discord or Telegram with the same stack you already know — TypeScript, Node.js, and a database.&lt;/p&gt;

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

&lt;p&gt;I recently built &lt;strong&gt;NumNinjas&lt;/strong&gt; — a math practice bot for kids aged 10-12. The idea is simple: 3 math questions every day, sent straight to Telegram.&lt;/p&gt;

&lt;p&gt;Here's what it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kids open the bot, take a quick quiz, and get placed in a level&lt;/li&gt;
&lt;li&gt;Every day at 2:30 PM they get 3 personalized questions (adaptive difficulty)&lt;/li&gt;
&lt;li&gt;Questions use real-life scenarios — shopping, cooking, sports, not boring textbook stuff&lt;/li&gt;
&lt;li&gt;5 ninja belt levels, points, streaks, and weekly leaderboards&lt;/li&gt;
&lt;li&gt;Parents get a weekly progress report every Sunday&lt;/li&gt;
&lt;li&gt;Available in Arabic and English&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bot handles the daily engagement. The website handles everything else — public leaderboards, player profiles, and an admin panel for managing questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://youtu.be/6CjTg_NaghM" rel="noopener noreferrer"&gt;https://youtu.be/6CjTg_NaghM&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech stack
&lt;/h2&gt;

&lt;p&gt;Nothing exotic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bot:&lt;/strong&gt; TypeScript + &lt;a href="https://grammy.dev" rel="noopener noreferrer"&gt;Grammy&lt;/a&gt; (Telegram framework) + node-cron for scheduled jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; Next.js 15 (App Router, Server Components)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Prisma 7 + MySQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared logic:&lt;/strong&gt; pnpm monorepo — bot and website share the same database package&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Railway (bot) + Hostinger (website) + Cloudflare (CDN)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both apps deploy from the same GitHub repo. Push to main, both auto-deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a bot instead of just a website?
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero friction.&lt;/strong&gt; Telegram is already on the phone. No download, no signup. Open the bot, tap start, you're solving math in 30 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push, don't pull.&lt;/strong&gt; The bot sends questions to the user at 2:30 PM. The user doesn't need to remember to visit a website. The app comes to them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's just an API.&lt;/strong&gt; If you can build a REST API, you can build a bot. Grammy (or discord.js for Discord) gives you a clean way to handle commands, buttons, and callbacks. It's event-driven — think Express routes but for chat messages.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If you want to try
&lt;/h2&gt;

&lt;p&gt;Building a bot is easier than you think. Start with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple command handler (&lt;code&gt;/start&lt;/code&gt;, &lt;code&gt;/help&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An inline keyboard (buttons the user taps)&lt;/li&gt;
&lt;li&gt;A database to store user state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. You can add complexity later — scheduled messages, adaptive algorithms, leaderboards. But the core is just: receive message → process → reply.&lt;/p&gt;

&lt;p&gt;If you're a web developer looking for a side project that's not "another todo app," try building a bot. Your users will thank you.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try NumNinjas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 Bot: &lt;a href="https://t.me/NumNinjasBot?start=go" rel="noopener noreferrer"&gt;https://t.me/NumNinjasBot?start=go&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 Website: &lt;a href="https://numninjas.com" rel="noopener noreferrer"&gt;https://numninjas.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>bot</category>
      <category>sideprojects</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Some lessons of work culture</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Wed, 01 Apr 2026 12:28:19 +0000</pubDate>
      <link>https://forem.com/edriso/some-lessons-of-work-culture-34e6</link>
      <guid>https://forem.com/edriso/some-lessons-of-work-culture-34e6</guid>
      <description>&lt;p&gt;There's a layer underneath the actual work — the culture layer.&lt;br&gt;
It quietly shapes how you spend your days, how you get paid,&lt;br&gt;
and whether people see you as reliable or just available.&lt;/p&gt;

&lt;p&gt;You pick it up as you go, and here are some of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. You are the client's teammate, not their vendor
&lt;/h2&gt;

&lt;p&gt;This one took me a moment to really internalize.&lt;/p&gt;

&lt;p&gt;In a dedicated team setup, you're embedded &lt;em&gt;inside&lt;/em&gt; the client's world. You're not sending deliverables from the outside — you're sitting (virtually) at their table, in their Jira, joining their standups, and sometimes getting pulled into their internal drama.&lt;/p&gt;

&lt;p&gt;That means how you communicate matters a lot. No matter what's happening behind the scenes on your end, you show up polished and professional. The client doesn't need to feel your internal chaos. They just need to feel like you've got it.&lt;/p&gt;

&lt;p&gt;And in some cases — when you're meeting the client's &lt;em&gt;own&lt;/em&gt; clients — you represent the agency, not your home company. That's a different hat, and you wear it clean. You don't introduce yourself as an outsider. You're just part of the team.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Ticket updates are a form of respect
&lt;/h2&gt;

&lt;p&gt;This one seems small but it's actually huge.&lt;/p&gt;

&lt;p&gt;Every ticket you touch deserves a comment. Not an essay — just a clear, honest update on where things stand. Especially if a task is running longer than expected, or you've hit a blocker, or you're waiting on someone else.&lt;/p&gt;

&lt;p&gt;It keeps everyone aligned without anyone having to ask. And asking "what's the status?" is low-key one of the most annoying things in collaborative work. A well-placed comment saves a whole Slack thread, a status meeting, and someone's afternoon.&lt;/p&gt;

&lt;p&gt;Think of it this way: your ticket trail is a real-time changelog of your workday. Keep it readable.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Time logging is not optional — and it protects &lt;em&gt;you&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Time logging felt like admin overhead at first. Then I realized: this is actually how you get paid correctly, and how disputes never happen.&lt;/p&gt;

&lt;p&gt;When your logged hours match the client's records exactly, there's nothing to argue about. When they don't, things get messy — and you're the one who has to untangle it, usually at the worst possible moment.&lt;/p&gt;

&lt;p&gt;Log daily. Screenshot on Fridays and at the end of the month. It's a five-minute habit that saves hours of stress later. Future you will be quietly grateful.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. No tickets ≠ free time. It's a signal to act.
&lt;/h2&gt;

&lt;p&gt;One of the clearest lessons from this kind of work: if you don't have anything to do, &lt;em&gt;say something immediately&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Don't wait. Don't assume the backlog will magically refill. Tell your PM or TL right away. In the meantime, look at old tickets, read through upcoming sprint items, think about edge cases you haven't tested, or review code you wrote two weeks ago with fresh eyes.&lt;/p&gt;

&lt;p&gt;There's usually always something. And if there genuinely isn't — that's a process too. There's a ticket for idle time, your manager needs to know, and it gets logged properly. It's not embarrassing. It's just how an honest system works.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Holiday bonuses and overtime exist — but you have to speak up first
&lt;/h2&gt;

&lt;p&gt;Want to work a national holiday for double pay? Totally possible. But you need to let the team know &lt;em&gt;well in advance&lt;/em&gt; — not the morning of.&lt;/p&gt;

&lt;p&gt;Same with overtime. If a client asks you to push extra hours, don't just do it and hope it shows up correctly in your paycheck. Communicate it first. Get it approved. That way the rate gets negotiated properly with the client, and you actually see it reflected in what you earn.&lt;/p&gt;

&lt;p&gt;The system works for you &lt;em&gt;when you engage with it early&lt;/em&gt;. Stay quiet and you're basically leaving money on the table.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Vacation notice is longer than you think
&lt;/h2&gt;

&lt;p&gt;Two weeks notice for most of the year. Four weeks during the busy summer months and around Christmas.&lt;/p&gt;

&lt;p&gt;That sounds like a lot until you consider that a client team is built around your availability. Sprint planning, ticket assignment, standups — your absence has a ripple effect. Giving plenty of notice isn't bureaucracy for the sake of it. It's just keeping things fair for everyone involved, including you.&lt;/p&gt;

&lt;p&gt;Book early, tell people early, come back rested.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Sick days have a process too — follow it immediately
&lt;/h2&gt;

&lt;p&gt;Nobody plans to get sick. But when it happens, the instinct to "just rest and deal with it tomorrow" can cause more hassle than it's worth.&lt;/p&gt;

&lt;p&gt;The right move: inform your PM on the client's side first, then email HR as soon as possible. Not later. Not when you feel better. Right away.&lt;/p&gt;

&lt;p&gt;Why? Because other people are rearranging their day around your absence. And your sick leave needs to be processed correctly so your paycheck isn't affected. Both of those things depend on timely communication.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Knowing when to ask for help is a skill
&lt;/h2&gt;

&lt;p&gt;Working embedded in a client team can sometimes feel isolating — you're technically part of two organizations but not fully home in either one. It's easy to sit on a problem too long because you're not sure who to ask.&lt;/p&gt;

&lt;p&gt;What I learned: when in doubt, ask your direct manager or department lead. Not a week later. Not after you've already gone in circles. Just ask.&lt;/p&gt;

&lt;p&gt;Good teams have a clear escalation path for a reason. Use it. Being resourceful is great, but spinning your wheels alone isn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;Working in a dedicated team taught me that being a good developer isn't just about writing clean code.&lt;/p&gt;

&lt;p&gt;It's about communicating clearly, logging honestly, showing up reliably, and understanding that &lt;em&gt;you're part of something bigger than a single ticket&lt;/em&gt;. The "soft stuff" is actually load-bearing. Strip it away and even great code doesn't hold up the way it should.&lt;/p&gt;

&lt;p&gt;If you're moving into this kind of role — or already in one — I hope something here clicked for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Credits: Scandiweb&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>devjournal</category>
      <category>discuss</category>
      <category>learning</category>
    </item>
    <item>
      <title>Why Chrome Draws a Dark Line on Your CSS Scalloped Edge (and How to Fix It)</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Sat, 21 Mar 2026 23:10:01 +0000</pubDate>
      <link>https://forem.com/edriso/why-chrome-draws-a-dark-line-on-your-css-scalloped-edge-and-how-to-fix-it-4cl7</link>
      <guid>https://forem.com/edriso/why-chrome-draws-a-dark-line-on-your-css-scalloped-edge-and-how-to-fix-it-4cl7</guid>
      <description>&lt;p&gt;You've built a scalloped (wavy) edge using a repeating &lt;code&gt;radial-gradient&lt;/code&gt; on a pseudo-element. It looks perfect in Firefox. You open Chrome, and there's a faint dark line running along the entire edge.&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%2Fijqgylbiag5ao2kx71ja.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%2Fijqgylbiag5ao2kx71ja.png" alt="Scalloped edge with a 1px dark line visible in Chrome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a Chrome sub-pixel rendering bug, and it comes down to one thing: what &lt;code&gt;transparent&lt;/code&gt; actually means in CSS.&lt;/p&gt;

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

&lt;p&gt;A typical scalloped edge uses a repeating radial gradient to punch semi-circular cutouts into a solid strip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.element&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ellipse&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;99%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#1a3a2a&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;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat-y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The center of each ellipse is &lt;code&gt;transparent&lt;/code&gt; (the cutout), the outside is your solid color. The &lt;code&gt;99% → 100%&lt;/code&gt; creates a near-hard stop with just enough room for anti-aliasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Chrome Shows a Line
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;transparent&lt;/code&gt; in CSS is &lt;code&gt;rgba(0, 0, 0, 0)&lt;/code&gt; — transparent &lt;strong&gt;black&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When Chrome interpolates between &lt;code&gt;transparent&lt;/code&gt; and your color at that gradient boundary, it mixes through black. Even though the transition space is tiny, Chrome renders a visible sub-pixel line of the intermediate color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transparent = rgba(0, 0, 0, 0)   ← black at zero alpha
your color  = rgba(26, 58, 42, 1)

Chrome blends through that black → dark line artifact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firefox handles this boundary more gracefully — no visible artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Didn't Work: color-mix()
&lt;/h2&gt;

&lt;p&gt;The intuitive fix is to replace &lt;code&gt;transparent&lt;/code&gt; with a zero-opacity version of your actual color so Chrome blends between the same hue:&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;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;radial-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nt"&gt;ellipse&lt;/span&gt; &lt;span class="err"&gt;10&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="err"&gt;8&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt; &lt;span class="nt"&gt;at&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="nt"&gt;color-mix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;in&lt;/span&gt; &lt;span class="nt"&gt;srgb&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#1&lt;/span&gt;&lt;span class="nt"&gt;a3a2a&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;99&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt;
  &lt;span class="err"&gt;#1&lt;/span&gt;&lt;span class="nt"&gt;a3a2a&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In theory, this eliminates the black-channel bleed. In practice, the line persisted — Chrome's sub-pixel rendering still produced a visible artifact at the gradient boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Works: CSS Masks
&lt;/h2&gt;

&lt;p&gt;The fix is to &lt;strong&gt;separate color from shape&lt;/strong&gt;. Instead of using the gradient for both, make the background a flat solid color and move the shape into a &lt;code&gt;mask-image&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Gradient Mask
&lt;/h3&gt;

&lt;p&gt;The quickest change — move the same gradient from &lt;code&gt;background&lt;/code&gt; to &lt;code&gt;mask-image&lt;/code&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="nc"&gt;.element&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Flat color — nothing to interpolate */&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;#1a3a2a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Shape defined by mask alpha channel only */&lt;/span&gt;
  &lt;span class="py"&gt;mask-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ellipse&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;99%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;#000&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mask-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;mask-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat-y&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;CSS masks only read the &lt;strong&gt;alpha channel&lt;/strong&gt;. The gradient transitions from alpha 0 to alpha 1 — a pure opacity operation with no color data for Chrome to mix through. No color mixing → no dark line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: SVG Mask (More Control)
&lt;/h3&gt;

&lt;p&gt;If you need precise control over scallop shape — like elongated bumps or specific aspect ratios — an inline SVG mask gives you that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.element&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&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;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="py"&gt;mask-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'
    width='20' height='28'%3E%3Cdefs%3E%3Cmask id='m'%3E%3Crect width='20'
    height='28' fill='white'/%3E%3Cellipse cx='0' cy='14' rx='10' ry='12'
    fill='black'/%3E%3C/mask%3E%3C/defs%3E%3Crect width='20' height='28'
    mask='url(%23m)' fill='white'/%3E%3C/svg%3E"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mask-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;28px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;mask-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat-y&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;Here's the decoded SVG for clarity:&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="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;defs&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mask&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"m"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ellipse&lt;/span&gt; &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;"14"&lt;/span&gt; &lt;span class="na"&gt;rx=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;ry=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"black"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mask&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/defs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"28"&lt;/span&gt; &lt;span class="na"&gt;mask=&lt;/span&gt;&lt;span class="s"&gt;"url(#m)"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"white"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;White areas are visible, the black ellipse is cut out. You can tweak &lt;code&gt;rx&lt;/code&gt; and &lt;code&gt;ry&lt;/code&gt; independently to control scallop width and height, or swap in a &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; for non-circular shapes. Both approaches fix the Chrome line for the same reason: flat background color + mask handles shape through alpha only.&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%2F4dx38e9k5uct8gf53iz0.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%2F4dx38e9k5uct8gf53iz0.png" alt="Scalloped edge without the 1px line after the SVG mask fix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Result in Chrome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;transparent&lt;/code&gt; keyword in gradient&lt;/td&gt;
&lt;td&gt;Dark line (blends through black)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;color-mix()&lt;/code&gt; to 0% opacity&lt;/td&gt;
&lt;td&gt;Still a line (sub-pixel rendering)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mask-image&lt;/code&gt; (gradient or SVG) with solid background&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Clean&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you have a &lt;code&gt;radial-gradient&lt;/code&gt; scalloped edge showing a line in Chrome, move the shape to &lt;code&gt;mask-image&lt;/code&gt; and set &lt;code&gt;background&lt;/code&gt; to your solid color. Use a gradient mask for a quick fix, or an SVG mask when you need precise control over the scallop shape. Either way, masks eliminate color interpolation entirely — which is the root cause.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>css</category>
    </item>
    <item>
      <title>How I Fixed My Masonry Layout on Mobile Using window.matchMedia</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Sat, 21 Mar 2026 06:47:25 +0000</pubDate>
      <link>https://forem.com/edriso/how-i-fixed-my-masonry-layout-on-mobile-using-windowmatchmedia-29fb</link>
      <guid>https://forem.com/edriso/how-i-fixed-my-masonry-layout-on-mobile-using-windowmatchmedia-29fb</guid>
      <description>&lt;p&gt;Have you ever built a nice two-column layout, only to realize it looks broken on mobile?&lt;/p&gt;

&lt;p&gt;That happened to me. Here's what went wrong and how I fixed it — step by step.&lt;/p&gt;




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

&lt;p&gt;I had a &lt;strong&gt;Kudos page&lt;/strong&gt; — a wall of feedback cards from colleagues. On desktop, I wanted a &lt;strong&gt;masonry layout&lt;/strong&gt; (two columns, cards stacked like bricks). On mobile, just a &lt;strong&gt;simple list&lt;/strong&gt; — one card per row.&lt;/p&gt;

&lt;p&gt;My first approach: split the cards into two arrays (even/odd) and render them as two columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;kudos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// left column&lt;/span&gt;
  &lt;span class="nx"&gt;kudos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// right column&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives a nice masonry effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Desktop (md+)
┌─────────┐  ┌─────────┐
│ Card 0  │  │ Card 1  │
│         │  └─────────┘
└─────────┘  ┌─────────┐
┌─────────┐  │ Card 3  │
│ Card 2  │  │         │
└─────────┘  │         │
             └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading order: 0 → 1 → 2 → 3. Correct!&lt;/p&gt;

&lt;p&gt;But on mobile, these two column divs &lt;strong&gt;stack vertically&lt;/strong&gt;. So you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mobile (broken)
┌─────────┐
│ Card 0  │  ← left column first
├─────────┤
│ Card 2  │
├─────────┤
│ Card 4  │
├─────────┤
│ Card 1  │  ← then right column
├─────────┤
│ Card 3  │
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cards are &lt;strong&gt;out of order&lt;/strong&gt;. Not great.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 1: Render Everything Twice
&lt;/h2&gt;

&lt;p&gt;My first fix was to render two versions — one for mobile, one for desktop — and toggle with CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Mobile: simple list */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-5 md:hidden"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KudoCard&lt;/span&gt; &lt;span class="na"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Desktop: masonry */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden md:flex gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 flex flex-col gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KudoCard&lt;/span&gt; &lt;span class="na"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works visually! But there's a problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every card exists twice in the DOM.&lt;/strong&gt; One copy is hidden, but it's still there. That's wasted memory and DOM nodes for no reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 2: CSS Columns
&lt;/h2&gt;

&lt;p&gt;CSS has a built-in masonry-like feature: &lt;code&gt;columns&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"columns-1 md:columns-2 gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"break-inside-avoid"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KudoCard&lt;/span&gt; &lt;span class="na"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Single render, responsive, clean. But...&lt;/p&gt;

&lt;p&gt;CSS &lt;code&gt;columns&lt;/code&gt; fills &lt;strong&gt;top to bottom per column&lt;/strong&gt;, not left to right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSS columns reading order
┌─────────┐  ┌─────────┐
│ Card 0  │  │ Card 3  │
├─────────┤  ├─────────┤
│ Card 1  │  │ Card 4  │
├─────────┤  ├─────────┤
│ Card 2  │  │ Card 5  │
└─────────┘  └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You read 0, 1, 2... then jump to 3, 4, 5. The &lt;strong&gt;reading order is broken&lt;/strong&gt; on desktop. Back to square one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: &lt;code&gt;window.matchMedia&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The solution: use JavaScript to detect the screen size and &lt;strong&gt;render only one layout&lt;/strong&gt; at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is &lt;code&gt;window.matchMedia&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;It's like CSS &lt;code&gt;@media&lt;/code&gt; queries, but in JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(min-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// true or false right now&lt;/span&gt;
&lt;span class="nx"&gt;result&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;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// fires when it changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You give it a media query string, and it tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Does it match right now?&lt;/strong&gt; → &lt;code&gt;.matches&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell me when it changes&lt;/strong&gt; → &lt;code&gt;.addEventListener('change', ...)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using it in React
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MD_BREAKPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(min-width: 768px)&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="nf"&gt;Kudos&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isMd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsMd&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MD_BREAKPOINT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;mql&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="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MD_BREAKPOINT&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;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setIsMd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mql&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;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isMd&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MasonryLayout&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SimpleList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On mount&lt;/strong&gt; — check if the screen is &lt;code&gt;&amp;gt;= 768px&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up a listener&lt;/strong&gt; — when the user resizes across 768px, update state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean up&lt;/strong&gt; — remove the listener when the component unmounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render one layout&lt;/strong&gt; — masonry or simple list, never both&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The variable name &lt;code&gt;mql&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You'll see &lt;code&gt;mql&lt;/code&gt; a lot in code. It stands for &lt;strong&gt;Media Query List&lt;/strong&gt; — that's the type of object &lt;code&gt;matchMedia()&lt;/code&gt; returns. It's a common shorthand, like &lt;code&gt;e&lt;/code&gt; for event or &lt;code&gt;el&lt;/code&gt; for element.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MD_BREAKPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(min-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reversed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;kudos&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;reverse&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;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Kudos&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isMd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsMd&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MD_BREAKPOINT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;mql&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="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MD_BREAKPOINT&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;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setIsMd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mql&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;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isMd&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 flex flex-col gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;kudo&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KudoCard&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-5"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;kudo&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KudoCard&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;kudo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kudo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;Notice that &lt;code&gt;reversed&lt;/code&gt; and &lt;code&gt;columns&lt;/code&gt; are &lt;strong&gt;outside the component&lt;/strong&gt;. Since &lt;code&gt;kudos&lt;/code&gt; is static data that never changes, there's no reason to recompute these on every render.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Correct Order&lt;/th&gt;
&lt;th&gt;Single DOM&lt;/th&gt;
&lt;th&gt;Masonry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CSS &lt;code&gt;flex-col&lt;/code&gt; / &lt;code&gt;flex-row&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Mobile breaks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render twice + hide&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (2x nodes)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS &lt;code&gt;columns&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Desktop breaks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matchMedia&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSS &lt;code&gt;columns&lt;/code&gt;&lt;/strong&gt; fills top-to-bottom, not left-to-right. Great for text, tricky for ordered content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering twice and hiding with CSS&lt;/strong&gt; works but doubles your DOM nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;window.matchMedia&lt;/code&gt;&lt;/strong&gt; lets you use media queries in JS — render different layouts without duplication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hoist static computations&lt;/strong&gt; outside your component. If the data never changes, don't recompute it on every render.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>CSS Masonry Layout in 2 Minutes (No JS Needed)</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Thu, 19 Mar 2026 18:54:44 +0000</pubDate>
      <link>https://forem.com/edriso/css-masonry-layout-in-2-minutes-no-js-needed-1bbn</link>
      <guid>https://forem.com/edriso/css-masonry-layout-in-2-minutes-no-js-needed-1bbn</guid>
      <description>&lt;p&gt;You have cards with different content lengths. CSS Grid makes them all the same height per row, leaving ugly empty space. Here's how to fix that with pure CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Grid = Equal Row Heights
&lt;/h2&gt;

&lt;p&gt;With CSS Grid, cards in the same row are forced to the same height:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSS Grid:
┌──────────────┐  ┌──────────────┐
│ Short card   │  │ Tall card    │
│              │  │ with lots of │
│  (wasted     │  │ content that │
│   space!)    │  │ fills the    │
│              │  │ whole card   │
└──────────────┘  └──────────────┘
┌──────────────┐  ┌──────────────┐
│ Medium card  │  │ Tiny         │
│ with some    │  │              │
│ content      │  │  (wasted     │
│              │  │   space!)    │
└──────────────┘  └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The short card stretches to match its tall neighbor. Wasted space everywhere.&lt;/p&gt;

&lt;p&gt;Copy this into an HTML file and see it yourself:&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="cp"&gt;&amp;lt;!DOCTYPE html&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;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&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.0"&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;The Problem&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&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;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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&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;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&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="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="py"&gt;gap&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;.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;1.5rem&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e5e5&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="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The Problem: CSS Grid&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem; color: #666;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Notice how short cards stretch to match tall ones in the same row.&lt;span class="nt"&gt;&amp;lt;/p&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;"grid"&lt;/span&gt;&lt;span class="nt"&gt;&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;h3&amp;gt;&lt;/span&gt;Short card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Just a few words.&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;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;h3&amp;gt;&lt;/span&gt;Tall card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This card has way more content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum. Cras vehicula risus quis vulputate arcu.&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;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;h3&amp;gt;&lt;/span&gt;Medium card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Some content here, not too much.&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;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;h3&amp;gt;&lt;/span&gt;Tiny card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hi.&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;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Fix: CSS Columns
&lt;/h2&gt;

&lt;p&gt;Replace the grid with &lt;strong&gt;two CSS properties&lt;/strong&gt; and each card takes only the height it needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSS Columns:
┌──────────────┐  ┌──────────────┐
│ Short card   │  │ Tall card    │
└──────────────┘  │ with lots of │
┌──────────────┐  │ content that │
│ Medium card  │  │ fills the    │
│ with some    │  │ whole card   │
│ content      │  └──────────────┘
└──────────────┘  ┌──────────────┐
┌──────────────┐  │ Tiny         │
│ Another one  │  └──────────────┘
└──────────────┘

No wasted space! Each card = its own height.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the code:&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="cp"&gt;&amp;lt;!DOCTYPE html&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;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&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.0"&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;CSS Masonry&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&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;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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&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;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;columns&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;/* split into 2 columns */&lt;/span&gt;
      &lt;span class="py"&gt;gap&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;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;break-inside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;avoid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* don't split a card across columns */&lt;/span&gt;
      &lt;span class="nl"&gt;margin-bottom&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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e5e5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&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="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The Fix: CSS Columns&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem; color: #666;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Each card takes only the height it needs. No stretching.&lt;span class="nt"&gt;&amp;lt;/p&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;"masonry"&lt;/span&gt;&lt;span class="nt"&gt;&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;h3&amp;gt;&lt;/span&gt;Short card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Just a few words.&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;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;h3&amp;gt;&lt;/span&gt;Tall card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This card has way more content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum. Cras vehicula risus quis vulputate arcu.&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;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;h3&amp;gt;&lt;/span&gt;Medium card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Some content here, not too much.&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;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;h3&amp;gt;&lt;/span&gt;Tiny card&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hi.&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;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;h3&amp;gt;&lt;/span&gt;Another tall one&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;More content to show the layout handles varying heights gracefully. Cards flow top-to-bottom in each column, like reading a newspaper.&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;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;h3&amp;gt;&lt;/span&gt;Last one&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Done!&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;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;columns: 2&lt;/code&gt; + &lt;code&gt;break-inside: avoid&lt;/code&gt;. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Wait: Expandable Cards Break It
&lt;/h2&gt;

&lt;p&gt;If your cards have a "Read more" button that expands content, CSS columns &lt;strong&gt;rebalance all cards&lt;/strong&gt; when one changes height. Everything shifts around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE clicking "Read more":        AFTER clicking "Read more":
┌──────────────┐ ┌──────────────┐   ┌──────────────┐ ┌──────────────┐
│ Card 1       │ │ Card 3       │   │ Card 1       │ │ Card 4       │
│ [Read more]  │ └──────────────┘   │ now expanded  │ │ [Read more]  │
└──────────────┘ ┌──────────────┐   │ with all the  │ └──────────────┘
┌──────────────┐ │ Card 4       │   │ full text     │ ┌──────────────┐
│ Card 2       │ │ [Read more]  │   │ showing...    │ │ Card 5       │
└──────────────┘ └──────────────┘   │ [Show less]   │ └──────────────┘
┌──────────────┐ ┌──────────────┐   └──────────────┘
│ Card 3  ←─── jumped from      │   ┌──────────────┐
└──────────────┘  right column!     │ Card 2       │
                                    └──────────────┘
                                    ← Cards 2-5 all moved!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try it yourself:&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="cp"&gt;&amp;lt;!DOCTYPE html&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;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&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.0"&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;The Reflow Bug&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&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;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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&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;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;columns&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="py"&gt;gap&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;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;break-inside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;avoid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin-bottom&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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e5e5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.card-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.card-text.clamped&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;-webkit-line-clamp&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&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="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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="m"&gt;#3b82f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.875rem&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="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The Bug: Cards Shift on Expand&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem; color: #666;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click "Read more" and watch ALL cards jump around.&lt;span class="nt"&gt;&amp;lt;/p&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;"masonry"&lt;/span&gt;&lt;span class="nt"&gt;&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;h3&amp;gt;&lt;/span&gt;Card 1&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-text clamped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This card has a lot of text that gets clamped. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum. Cras vehicula risus.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.previousElementSibling.classList.toggle('clamped')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read more&lt;span class="nt"&gt;&amp;lt;/button&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;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;h3&amp;gt;&lt;/span&gt;Card 2&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Short card. Watch me jump when you expand Card 1!&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;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;h3&amp;gt;&lt;/span&gt;Card 3&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;I'll move too.&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;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;h3&amp;gt;&lt;/span&gt;Card 4&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-text clamped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Another long card. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.previousElementSibling.classList.toggle('clamped')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read more&lt;span class="nt"&gt;&amp;lt;/button&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;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;h3&amp;gt;&lt;/span&gt;Card 5&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Yet another card that will shift.&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;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Annoying, right? Here's the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Fix: Split Into Separate Columns
&lt;/h2&gt;

&lt;p&gt;Instead of letting CSS decide which card goes where, &lt;strong&gt;you split them into independent columns&lt;/strong&gt;. Now expanding a card only affects its own column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE clicking "Read more":        AFTER clicking "Read more":

  Column 1        Column 2           Column 1        Column 2
┌──────────────┐ ┌──────────────┐   ┌──────────────┐ ┌──────────────┐
│ Card 1       │ │ Card 2       │   │ Card 1       │ │ Card 2       │
│ [Read more]  │ │ [Read more]  │   │ now expanded  │ │ [Read more]  │
└──────────────┘ └──────────────┘   │ with all the  │ └──────────────┘
┌──────────────┐ ┌──────────────┐   │ full text     │ ┌──────────────┐
│ Card 3       │ │ Card 4       │   │ showing...    │ │ Card 4       │
└──────────────┘ └──────────────┘   │ [Show less]   │ └──────────────┘
┌──────────────┐                    └──────────────┘
│ Card 5       │                    ┌──────────────┐
└──────────────┘                    │ Card 3       │  ← only Card 3
                                    └──────────────┘    moved down
                                    ┌──────────────┐
                                    │ Card 5       │  Column 2 is
                                    └──────────────┘  untouched!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each column is its own flex container. They don't know about each other.&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="cp"&gt;&amp;lt;!DOCTYPE html&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;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&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.0"&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;Stable Masonry&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&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;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="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&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;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;gap&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;.column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;gap&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;.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;1.5rem&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e5e5e5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.card-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.card-text.clamped&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;-webkit-line-clamp&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&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="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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="m"&gt;#3b82f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.875rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;.masonry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&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="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;The Fix: Independent Columns&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 1rem; color: #666;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click "Read more" — only the same column moves. The other stays put.&lt;span class="nt"&gt;&amp;lt;/p&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;"masonry"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Column 1: items 1, 3, 5 --&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;"column"&lt;/span&gt;&lt;span class="nt"&gt;&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;h3&amp;gt;&lt;/span&gt;Card 1&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-text clamped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This card has a lot of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum. Cras vehicula risus.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.previousElementSibling.classList.toggle('clamped')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read more&lt;span class="nt"&gt;&amp;lt;/button&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;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;h3&amp;gt;&lt;/span&gt;Card 3&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;I stay still when Card 2 expands!&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;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;h3&amp;gt;&lt;/span&gt;Card 5&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Me too.&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;/div&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Column 2: items 2, 4 --&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;"column"&lt;/span&gt;&lt;span class="nt"&gt;&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;h3&amp;gt;&lt;/span&gt;Card 2&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-text clamped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Another long card. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus lacinia odio vitae vestibulum vestibulum.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"this.previousElementSibling.classList.toggle('clamped')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read more&lt;span class="nt"&gt;&amp;lt;/button&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;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;h3&amp;gt;&lt;/span&gt;Card 4&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Short and stable.&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;/div&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;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; odd items (1, 3, 5) go in column 1, even items (2, 4) go in column 2. Each column is a separate flex container, so they don't affect each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Need masonry?
│
├── Cards have fixed content?
│   └── ✅ Use CSS columns + break-inside: avoid
│
├── Cards expand/collapse?
│   └── ✅ Split into separate flex columns
│
└── All cards same height?
    └── ✅ Just use CSS Grid (no masonry needed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Three examples you can copy-paste and try right now. That's the whole trick — no libraries needed!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Why BEM Nesting Breaks in Tailwind v4</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:48:03 +0000</pubDate>
      <link>https://forem.com/edriso/why-bem-nesting-breaks-in-tailwind-v4-1855</link>
      <guid>https://forem.com/edriso/why-bem-nesting-breaks-in-tailwind-v4-1855</guid>
      <description>&lt;p&gt;So today I spent sometime debugging why some CSS styles weren't applying. Turns out, Tailwind v4 quietly broke something on was working Tailwind v3.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;I had CSS like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dropdown&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="err"&gt;&amp;amp;--open&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&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;amp;&lt;/span&gt;&lt;span class="nt"&gt;__icon&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;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Tailwind v3, this worked perfectly. It would compile to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dropdown&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.dropdown--open&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="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.dropdown__icon&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;16px&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;But in Tailwind v4? &lt;strong&gt;Nothing. No error. No warning. It just silently does nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, this still works fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dropdown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&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;amp;&lt;/span&gt; &lt;span class="nc"&gt;.child&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;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, what? &lt;code&gt;&amp;amp;:hover&lt;/code&gt; works but &lt;code&gt;&amp;amp;--open&lt;/code&gt; doesn't? Why??&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason
&lt;/h2&gt;

&lt;p&gt;It comes down to &lt;strong&gt;native CSS nesting vs SASS nesting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native CSS&lt;/strong&gt; (what browsers understand) supports &lt;code&gt;&amp;amp;&lt;/code&gt; only for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pseudo-classes: &lt;code&gt;&amp;amp;:hover&lt;/code&gt;, &lt;code&gt;&amp;amp;:focus&lt;/code&gt;, &lt;code&gt;&amp;amp;::before&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Combinators: &lt;code&gt;&amp;amp; .child&lt;/code&gt;, &lt;code&gt;&amp;amp; &amp;gt; .child&lt;/code&gt;, &lt;code&gt;&amp;amp; + .sibling&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chaining: &lt;code&gt;&amp;amp;.another-class&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SASS&lt;/strong&gt; added an extra feature on top: using &lt;code&gt;&amp;amp;&lt;/code&gt; to &lt;strong&gt;concatenate strings&lt;/strong&gt;. So &lt;code&gt;&amp;amp;--open&lt;/code&gt; inside &lt;code&gt;.dropdown&lt;/code&gt; would glue them together into &lt;code&gt;.dropdown--open&lt;/code&gt;. That's not real CSS — that's a SASS trick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind v3&lt;/strong&gt; used &lt;code&gt;postcss-nested&lt;/code&gt; under the hood, which copied the SASS behavior. So BEM nesting worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind v4&lt;/strong&gt; switched to &lt;code&gt;Lightning CSS&lt;/code&gt;, which follows the &lt;strong&gt;real CSS nesting spec&lt;/strong&gt;. No more string concatenation. BEM nesting just silently fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Simple. Just write the full class name:&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="c"&gt;/* Before (broken in v4) */&lt;/span&gt;
&lt;span class="nc"&gt;.dropdown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;--open&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&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;amp;&lt;/span&gt;&lt;span class="nt"&gt;__icon&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;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* After (works everywhere) */&lt;/span&gt;
&lt;span class="nc"&gt;.dropdown--open&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="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dropdown__icon&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;16px&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;You can still use nesting for the stuff native CSS supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.dropdown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&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;amp;&lt;/span&gt; &lt;span class="nc"&gt;.dropdown__icon&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;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Tailwind v3&lt;/th&gt;
&lt;th&gt;Tailwind v4&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;:hover&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;Native CSS nesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp; .child&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;Native CSS nesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;--modifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;broken&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SASS feature, not native CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;__element&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;works&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;broken&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SASS feature, not native CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tailwind v4 uses Lightning CSS which follows the native CSS spec. BEM string concatenation with &lt;code&gt;&amp;amp;&lt;/code&gt; is a SASS-only feature that native CSS doesn't support.&lt;/p&gt;

&lt;p&gt;If you're migrating to Tailwind v4 and styles are silently disappearing, check for BEM nesting first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href="https://github.com/tailwindlabs/tailwindcss/issues/18522" rel="noopener noreferrer"&gt;Tailwind CSS GitHub Issue #18522&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>tailwindcss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Create the Perfect OG Image (With AI + A Simple Screenshot)</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Sun, 15 Mar 2026 14:56:50 +0000</pubDate>
      <link>https://forem.com/edriso/how-to-create-the-perfect-og-image-with-ai-a-simple-screenshot-3egh</link>
      <guid>https://forem.com/edriso/how-to-create-the-perfect-og-image-with-ai-a-simple-screenshot-3egh</guid>
      <description>&lt;h2&gt;
  
  
  What is an OG Image?
&lt;/h2&gt;

&lt;p&gt;OG (Open Graph) image is the preview image that shows up when you share a link on social media, Slack, Discord, or any platform that unfurls URLs.&lt;/p&gt;

&lt;p&gt;You've seen it hundreds of times — that card with an image, title, and description that appears when someone drops a link in a chat.&lt;/p&gt;

&lt;p&gt;It's set with a simple meta tag in your HTML:&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;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;"/og-image.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Should You Care?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Links with OG images get significantly more clicks&lt;/li&gt;
&lt;li&gt;It makes your site look professional and intentional&lt;/li&gt;
&lt;li&gt;Without one, platforms show a blank card or a random page element&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Easiest Way to Create One
&lt;/h2&gt;

&lt;p&gt;You don't need Figma or Photoshop. Ask any AI (ChatGPT, Claude, etc.) to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create an HTML file for an OG image with my name, title, and website URL. Use a dark background, 1200x630 dimensions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get a simple HTML file with a styled card. Open it in your browser, and screenshot it.&lt;/p&gt;

&lt;p&gt;Here's a minimal example:&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="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&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="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;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&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;#111&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;630px&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;#1a1a2e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&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="nc"&gt;.name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;56px&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="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;28px&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="m"&gt;#888&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="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Your Name&lt;span class="nt"&gt;&amp;lt;/div&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;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Your Title&lt;span class="nt"&gt;&amp;lt;/div&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;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Taking a Pixel-Perfect Screenshot
&lt;/h2&gt;

&lt;p&gt;Here's the trick — you can't just screenshot the browser window. You need exactly &lt;strong&gt;1200x630 pixels&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps (Chrome):
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the HTML file in Chrome&lt;/li&gt;
&lt;li&gt;Open DevTools (&lt;code&gt;F12&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;device toolbar&lt;/strong&gt; icon (or &lt;code&gt;Ctrl+Shift+M&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Set the dimensions to &lt;strong&gt;1200 x 630&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now here's the part most people miss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the device toolbar, click the &lt;strong&gt;three dots menu&lt;/strong&gt; (⋮)&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;"Add device pixel ratio"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set it to &lt;strong&gt;1.0&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Without this step, if your display runs at 2x scaling (most modern laptops), your screenshot will be 2400x1260 instead of 1200x630.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Click the &lt;strong&gt;three dots menu&lt;/strong&gt; (⋮) again → &lt;strong&gt;"Capture screenshot"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done. You now have a pixel-perfect 1200x630 OG image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding It to Your Site
&lt;/h2&gt;

&lt;p&gt;Drop the image in your &lt;code&gt;public&lt;/code&gt; folder (for Vite/Next.js) or root directory, then add these meta tags to your &lt;code&gt;&amp;lt;head&amp;gt;&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;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;"/og-image.png"&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:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Name - Your 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;"A short 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;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:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"/og-image.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test It
&lt;/h2&gt;

&lt;p&gt;After deploying, paste your URL into these tools to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.opengraph.xyz" rel="noopener noreferrer"&gt;OpenGraph.xyz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cards-dev.twitter.com/validator" rel="noopener noreferrer"&gt;Twitter Card Validator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. A 5-minute task that makes every shared link to your site look polished.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to Move a Git Branch to a Different Parent</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Fri, 13 Mar 2026 11:55:53 +0000</pubDate>
      <link>https://forem.com/edriso/how-to-move-a-git-branch-to-a-different-parent-26hf</link>
      <guid>https://forem.com/edriso/how-to-move-a-git-branch-to-a-different-parent-26hf</guid>
      <description>&lt;p&gt;You branched off &lt;code&gt;feature-A&lt;/code&gt;, did your work, then realized your branch should have been based on &lt;code&gt;main&lt;/code&gt; instead. Now your branch carries all of &lt;code&gt;feature-A&lt;/code&gt;'s commits along with yours.&lt;/p&gt;

&lt;p&gt;Here's how to fix it in 4 steps.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main ---- A ---- B
                  \
                   feature-A ---- C ---- D
                                          \
                                           your-branch ---- E  &amp;lt;-- your commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only want commit &lt;code&gt;E&lt;/code&gt;, but your branch includes &lt;code&gt;C&lt;/code&gt; and &lt;code&gt;D&lt;/code&gt; from &lt;code&gt;feature-A&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Find your commit hash
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; your-branch &lt;span class="nt"&gt;--not&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lists all commits on your branch that aren't in &lt;code&gt;main&lt;/code&gt;. Identify yours — in this case, &lt;code&gt;E&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a clean branch from main
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout main
git pull origin main
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; your-branch-clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Cherry-pick your commit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &amp;lt;commit-hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This applies only your commit on top of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Replace the old branch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; your-branch
git branch &lt;span class="nt"&gt;-m&lt;/span&gt; your-branch-clean your-branch
git push origin your-branch &lt;span class="nt"&gt;--force-with-lease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main ---- A ---- B
                  \
                   your-branch ---- E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean branch, no extra baggage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why cherry-pick over rebase?
&lt;/h2&gt;

&lt;p&gt;When your branch has merge commits and commits from another feature branch mixed in, &lt;code&gt;git rebase&lt;/code&gt; can get messy — conflicts from commits that aren't even yours. Cherry-pick lets you grab exactly what you need and skip everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reminder
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;--force-with-lease&lt;/code&gt; instead of &lt;code&gt;--force&lt;/code&gt; — it won't overwrite the remote if someone else pushed to your branch.&lt;/li&gt;
&lt;li&gt;If you have multiple commits to move, cherry-pick accepts a range: &lt;code&gt;git cherry-pick A^..B&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Business-Aware Development: What Every Developer Should Know Beyond Code</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Mon, 02 Mar 2026 15:10:10 +0000</pubDate>
      <link>https://forem.com/edriso/business-aware-development-what-every-developer-should-know-beyond-code-5i3</link>
      <guid>https://forem.com/edriso/business-aware-development-what-every-developer-should-know-beyond-code-5i3</guid>
      <description>&lt;p&gt;As developers, we tend to live inside the code. But successful project delivery depends on much more — understanding quality signals, data flows between systems, and the architectural decisions that shape how a business runs online.&lt;/p&gt;

&lt;p&gt;This post distills the key lessons from Scandiweb's &lt;strong&gt;Business-Aware Development&lt;/strong&gt; course into a practical reference. Whether you work in eCommerce or not, these concepts apply anywhere software meets business.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Manual QA — Thinking Like a Tester
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a bug?
&lt;/h3&gt;

&lt;p&gt;Anything that prevents the client from operating their business as intended. It can be a broken checkout button or a subtle typo that damages brand trust. Both matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 6 bug types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Content&lt;/strong&gt; — Text/content deviations from what's expected.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spelling errors, untranslated text, placeholder content ("Lorem ipsum"), dead links, missing pages&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; Describe the exact position on the page. Leave zero room for confusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Visual&lt;/strong&gt; — Inconsistencies between the live site and approved designs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong fonts, colors, spacing, margins; missing hover states; content overflow; misaligned elements&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; Include the URL, link to the design, and provide a side-by-side comparison. For responsive issues, include the screen resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Cross-Browser&lt;/strong&gt; — Works in one browser, breaks in another.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; Always include browser name and version. Without it, reproducing is nearly impossible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Cross-Device&lt;/strong&gt; — Same browser, different device, different result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; Specify the exact device. Standard testing covers iPhones, Android phones, and tablets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Functional&lt;/strong&gt; — Behavior that doesn't work as intended or at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filters not filtering, price mismatches, failed signups that show success, infinite checkout loaders&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; A good title is 90% of the success. Always include steps to reproduce, actual result, and expected result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Performance&lt;/strong&gt; — Operations taking too long. General rule: &lt;strong&gt;nothing should exceed 2-3 seconds.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check: page load (incognito, cleared cache), search speed, filter responsiveness, image sizes (~25-30KB per listing image), add-to-cart, checkout, login&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reporting:&lt;/em&gt; Include the URL, steps taken, and measured time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reporting rules
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One issue per ticket&lt;/strong&gt; — never group multiple bugs together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser version&lt;/strong&gt; — only required for cross-browser bugs; put "ANY" for everything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority&lt;/strong&gt; — usually set by the PM, but use common sense (payments = always top priority)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intermittent bugs&lt;/strong&gt; — clearly flag if a bug doesn't reproduce consistently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  You don't always need full specs
&lt;/h3&gt;

&lt;p&gt;Use common sense. Check standard flows (search, filter, cart, checkout, login), verify essential pages exist (About Us, T&amp;amp;C, Shipping), and flag obvious inconsistencies. QA should be part of task definition &lt;strong&gt;before&lt;/strong&gt; development, not just after.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Data Flows &amp;amp; ERP Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key concepts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SKU (Stock Keeping Unit)&lt;/strong&gt; — A product's unique public identifier (e.g., &lt;code&gt;NIKE-AIR-MAX-90-BLK-US10&lt;/code&gt;). It's the common language between warehouse, website, support, and ERP. When systems need to agree on "which product," they match on SKU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ERP (Enterprise Resource Planning)&lt;/strong&gt; — The backbone system that existed before the online store. It manages what eCommerce doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Warehouse management (trucks arrive, products scanned, stock updated)&lt;/li&gt;
&lt;li&gt;Point of Sale (physical store registers)&lt;/li&gt;
&lt;li&gt;Order processing &amp;amp; fulfillment&lt;/li&gt;
&lt;li&gt;Accounting, tax reporting, business intelligence&lt;/li&gt;
&lt;li&gt;Customer support &amp;amp; after-sales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ERP is the "ugly but essential" internal system. The storefront is the "pretty face." Both need to sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common order statuses:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pending&lt;/td&gt;
&lt;td&gt;Awaiting payment confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processing&lt;/td&gt;
&lt;td&gt;Payment confirmed, being prepared&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On Hold&lt;/td&gt;
&lt;td&gt;Paused (stock issue, fraud review, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shipped&lt;/td&gt;
&lt;td&gt;Handed to carrier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivered&lt;/td&gt;
&lt;td&gt;Customer received it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancelled&lt;/td&gt;
&lt;td&gt;Cancelled before shipment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refunded&lt;/td&gt;
&lt;td&gt;Money returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;td&gt;Complete, no open disputes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why developers should care
&lt;/h3&gt;

&lt;p&gt;If systems don't sync: customers buy out-of-stock items, the website shows stale prices, and 10,000 daily orders can't be manually copy-pasted into the ERP. &lt;strong&gt;"ERP integration" is really just data exchange&lt;/strong&gt; — specific pieces of data traveling between two databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ETL pattern: Extract, Transform, Load
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Extract&lt;/strong&gt; — Get data out of the source system in a usable format (CSV, XML, JSON, API).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SKU,              Qty, StoreID
NIKE-AIR-90-BLK,   20, 50
NIKE-AIR-90-BLK,   90, 80
NIKE-AIR-90-BLK,  -34, 03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Negative quantity = backorders — people paid for items not yet in stock.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transform&lt;/strong&gt; — Shape data to fit the destination. Example: the site doesn't need per-store stock, so sum it up: 20 + 90 + (-34) = &lt;strong&gt;76 available units&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load&lt;/strong&gt; — Insert into the target system. Trigger reindexing, invalidate caches, confirm it took effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  What flows where
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ERP to eCommerce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Product catalog, pricing, stock, store info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;eCommerce to ERP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orders, customer data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Practical takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask "where does this data live?"&lt;/strong&gt; before building any feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SKU is your universal key&lt;/strong&gt; across all systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think in data flows&lt;/strong&gt;, not "integrations" — what data, which direction, how often?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the business impact&lt;/strong&gt; — a stock sync delay means overselling; a price sync failure means revenue loss&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request only what you need&lt;/strong&gt; — don't import full database dumps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delta over full sync&lt;/strong&gt; — once basics work, sync only what changed&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Part 3: From SSR to CSR — The Magento to ScandiPWA Shift
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server-Side Rendering (the old way)
&lt;/h3&gt;

&lt;p&gt;Traditional Magento: user requests a URL, the server builds a complete HTML page (fetching data, merging with templates and CSS), sends it to the browser. Every click = full page rebuild.&lt;/p&gt;

&lt;p&gt;The server does everything. The browser just displays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Side Rendering with ScandiPWA (the new way)
&lt;/h3&gt;

&lt;p&gt;ScandiPWA flips this. The browser becomes the rendering engine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First load&lt;/strong&gt; — Browser downloads the entire app (layouts, styles, fonts). It's an empty store — no product data yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data requests&lt;/strong&gt; — The app asks the server for specific data via &lt;strong&gt;GraphQL&lt;/strong&gt;: "give me the 6 best sellers," not "give me the whole homepage."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering&lt;/strong&gt; — React takes the data, injects it into components, renders locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation&lt;/strong&gt; — No full reloads. The app already has the layout; it just swaps in new data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The architecture at a glance
&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;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React (ScandiPWA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The app — all UI, components, styles, routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GraphQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The only data pipe between frontend and backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Worker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local proxy — caching, offline mode, decides server vs. cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Varnish&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server-side cache for GraphQL responses (~20ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client-side global state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SSR vs CSR compared
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SSR (Traditional)&lt;/th&gt;
&lt;th&gt;CSR (ScandiPWA)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What's sent&lt;/td&gt;
&lt;td&gt;Full HTML pages&lt;/td&gt;
&lt;td&gt;Small JSON data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who renders&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page transitions&lt;/td&gt;
&lt;td&gt;Full reload&lt;/td&gt;
&lt;td&gt;Instant, app-like&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline support&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Service Worker cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First load&lt;/td&gt;
&lt;td&gt;Fast (one page)&lt;/td&gt;
&lt;td&gt;Slower (full app shell)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subsequent loads&lt;/td&gt;
&lt;td&gt;Same speed&lt;/td&gt;
&lt;td&gt;Much faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response size&lt;/td&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;Up to 10x smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What happens to Magento extensions?
&lt;/h3&gt;

&lt;p&gt;The old Magento frontend is gone. Extensions don't "just work" in ScandiPWA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New extension&lt;/strong&gt; — You build two packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend (PHP): admin grids, DB tables, business logic + &lt;strong&gt;GraphQL resolvers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Frontend (React): ScandiPWA extension with components, styles, GraphQL queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Existing marketplace extension&lt;/strong&gt; — Backend works, but needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GraphQL resolvers (many extensions already include them — check first)&lt;/li&gt;
&lt;li&gt;A React "compatibility extension" on the frontend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example (Stripe):&lt;/strong&gt; Backend already handles payments. Frontend work = 3-4 input fields in checkout + success/error message. Minimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend-only extensions&lt;/strong&gt; (reporting, analytics) need no frontend work at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL is the only bridge&lt;/strong&gt; — if data isn't exposed via GraphQL, the frontend can't use it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think in two packages&lt;/strong&gt; — backend (PHP + GraphQL) and frontend (React), developed separately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Varnish still matters&lt;/strong&gt; — tag your data with &lt;code&gt;X-Tag&lt;/code&gt; headers for proper cache invalidation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Worker = local intelligence&lt;/strong&gt; — key to debugging "why isn't my data updating?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assess extension impact first&lt;/strong&gt; — "does it have GraphQL?" determines the scope of work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The frontend is an application, not a template&lt;/strong&gt; — you're building a React app that consumes an API&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Writing code is one part of the job. Understanding &lt;em&gt;why&lt;/em&gt; the business needs it, &lt;em&gt;how&lt;/em&gt; quality is measured, &lt;em&gt;where&lt;/em&gt; data lives, and &lt;em&gt;what changes&lt;/em&gt; when architecture evolves — that's what makes you effective on a real project.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is based on learnings from Scandiweb's Business-Aware Development course. All credit for the original course content goes to Scandiweb.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The 4 Rules of Client Communication Nobody Taught You</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Mon, 02 Mar 2026 14:51:58 +0000</pubDate>
      <link>https://forem.com/edriso/the-4-rules-of-client-communication-nobody-taught-you-2mo3</link>
      <guid>https://forem.com/edriso/the-4-rules-of-client-communication-nobody-taught-you-2mo3</guid>
      <description>&lt;p&gt;Most communication advice boils down to "be professional" or "be nice." That's not useful. What clients actually need is to feel that the situation is under control.&lt;/p&gt;

&lt;p&gt;After years of working in client-facing roles, I've distilled this into four rules. Every message, every call, every comment — if it follows these four things, the experience holds. If even one is missing, trust starts to erode, even when the work itself is solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Own the Next Step
&lt;/h2&gt;

&lt;p&gt;The person reading your message should never have to wonder: &lt;em&gt;"So... who's doing what now?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ownership means someone visibly holds the ball. Not "the team," not "the process" — a person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This works:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm looking into this and will update you by end of day."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This doesn't:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Waiting for QA."&lt;/p&gt;

&lt;p&gt;"This is not on us."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;...silence...&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The test is simple: &lt;strong&gt;after reading your message, does the other person know who owns the next step?&lt;/strong&gt; If not, ownership is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Make It Simpler, Not Longer
&lt;/h2&gt;

&lt;p&gt;Clarity doesn't mean dumbing things down. It means structuring information so the reader can orient fast.&lt;/p&gt;

&lt;p&gt;A good message follows this shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What happened&lt;/li&gt;
&lt;li&gt;What it means&lt;/li&gt;
&lt;li&gt;What happens next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;This works:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We identified the issue — it affects the checkout flow. We're deploying a fix this afternoon."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This doesn't:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Three paragraphs of technical context, a "just for context..." section, and no clear takeaway.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The test: &lt;strong&gt;do you feel more oriented after reading this, or do you need to re-read it to figure out "so what?"&lt;/strong&gt; If it needs a second read, clarity is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Lower the Temperature
&lt;/h2&gt;

&lt;p&gt;When a client is frustrated, your job is to absorb the pressure — not match it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This works:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Thanks for flagging this. I see the issue — here's what I'm doing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This doesn't:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"As I mentioned before..."&lt;/p&gt;

&lt;p&gt;"This was already explained in the last call."&lt;/p&gt;

&lt;p&gt;"That's not really a problem because..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even when you're factually correct, defensive language escalates. The test: &lt;strong&gt;would this message calm a frustrated person, or make them more frustrated?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Always End with a Next Step
&lt;/h2&gt;

&lt;p&gt;Every interaction should move the situation forward. Even if you don't have a solution yet, there must be a direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This works:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'll confirm this with the team and come back to you by Friday."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This doesn't:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An explanation that just... ends.&lt;/p&gt;

&lt;p&gt;"Let me know" (with no ownership on your side).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The test: &lt;strong&gt;is it obvious what happens next and who does it?&lt;/strong&gt; If not, the interaction is incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Here's a real scenario. A client writes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The priority has been increased to critical. Launch is planned for the 26th. Please share an update by tomorrow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A good response (30 minutes later):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Noted! Will update you tomorrow first thing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The follow-up (next day):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The issue was caused by the script relying on DOMContentLoaded, which had already fired when the script was injected. I've replaced this with an immediately executed function that sets the value directly. Please review and let me know if you face any other issues."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why this works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ownership&lt;/strong&gt; — PM responds fast, dev explains and fixes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt; — cause, impact, and resolution in three lines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calm tone&lt;/strong&gt; — no panic despite "critical" priority&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next step&lt;/strong&gt; — fix is delivered, review is requested&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now here's what a bad version looks like. Client writes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Any news on the ticket?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We are checking the best approach and will then provide an estimate. Please let me know what you think."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tone is fine. But the client had to chase. There's no timeline, no clear owner, and the ball gets passed back with "let me know what you think." That's an open loop — and open loops erode trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Example Worth Studying
&lt;/h2&gt;

&lt;p&gt;A client raises a billing concern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I see some items that seem repetitive from the December invoice. Could you clarify?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Good response:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Thanks for letting me know! I'm looking into the January invoice now and will double-check why the full audit appears again compared to December. I'll review the scope billed in both months and confirm whether this was a follow-up item or an error on our side. I'll come back to you by tomorrow."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is textbook. The concern is acknowledged. The investigation is scoped. The timeline is set. The client has nothing left to worry about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Truth
&lt;/h2&gt;

&lt;p&gt;You can do excellent work and still leave a client feeling uneasy — if your communication doesn't hold up.&lt;/p&gt;

&lt;p&gt;Hospitality isn't about being perfect. It's about consistently doing four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ownership is visible&lt;/strong&gt; — someone clearly holds the ball&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The message reduces complexity&lt;/strong&gt; — the reader feels more oriented, not less&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tone lowers tension&lt;/strong&gt; — pressure is absorbed, not reflected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The next step is always clear&lt;/strong&gt; — every interaction moves forward&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If one of these is missing, the experience degrades. Even if the code ships on time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This started as a guideline for PMs, QAs, and developers. Turns out it applies to nearly every professional interaction where someone is counting on you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally inspired by an internal hospitality guideline at Scandiweb.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>communication</category>
      <category>softskills</category>
      <category>projectmanagement</category>
    </item>
    <item>
      <title>How to Use React Query with React Router Loaders (Pre-fetch &amp; Cache Data)</title>
      <dc:creator>Mohamed Idris</dc:creator>
      <pubDate>Thu, 26 Feb 2026 12:11:05 +0000</pubDate>
      <link>https://forem.com/edriso/how-to-use-react-query-with-react-router-loaders-pre-fetch-cache-data-kag</link>
      <guid>https://forem.com/edriso/how-to-use-react-query-with-react-router-loaders-pre-fetch-cache-data-kag</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When you navigate to a page, there's usually a delay while data is being fetched. The user sees a loading spinner, and the content pops in after the request finishes. Not great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the data was already there when the page loads?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's exactly what combining &lt;strong&gt;React Query&lt;/strong&gt; with &lt;strong&gt;React Router loaders&lt;/strong&gt; gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea in Plain English
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loader&lt;/strong&gt; runs &lt;em&gt;before&lt;/em&gt; the component mounts (React Router calls it on navigation).&lt;/li&gt;
&lt;li&gt;Inside the loader, we ask React Query: &lt;em&gt;"Do you already have this data cached?"&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yes&lt;/strong&gt; → Use it instantly. No network request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No&lt;/strong&gt; → Fetch it now, wait for it, then cache it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When the component finally mounts, it calls &lt;code&gt;useQuery&lt;/code&gt; with the same query. Since the data is already cached, it renders &lt;strong&gt;immediately&lt;/strong&gt; — no loading state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key method is &lt;code&gt;queryClient.ensureQueryData(queryOptions)&lt;/code&gt;. Think of it as: &lt;em&gt;"Make sure this data exists — get it from cache or fetch it."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Example: A Simple Pokémon Page
&lt;/h2&gt;

&lt;p&gt;Let's build a page that shows Pokémon details. When you navigate to &lt;code&gt;/pokemon/pikachu&lt;/code&gt;, the data is already loaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Set Up the Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/Pokemon.jsx&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;useQuery&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;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;useLoaderData&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;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&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;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// A function that returns the query config (key + fetch function).&lt;/span&gt;
&lt;span class="c1"&gt;// We reuse this in BOTH the loader and the component.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pokemonQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&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;pokemon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create the Loader
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The loader receives queryClient from the router setup (see step 4).&lt;/span&gt;
&lt;span class="c1"&gt;// It runs BEFORE the component mounts.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryClient&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;return&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;params&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ensureQueryData checks the cache first:&lt;/span&gt;
    &lt;span class="c1"&gt;//   - cached? → returns it instantly&lt;/span&gt;
    &lt;span class="c1"&gt;//   - not cached? → fetches, caches, and returns it&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pokemonQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// We only return the param — the actual data lives in React Query's cache&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build the Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&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="c1"&gt;// Get the param that the loader returned&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;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLoaderData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// useQuery uses the SAME query config as the loader.&lt;/span&gt;
  &lt;span class="c1"&gt;// Since ensureQueryData already cached it, this renders instantly.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pokemon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pokemonQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sprites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;front_default&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Height: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Weight: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Wire It Up in the Router
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.jsx&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;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RouterProvider&lt;/span&gt;&lt;span class="p"&gt;,&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;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClientProvider&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;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pokemonLoader&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;./pages/Pokemon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pokemon/:name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Pass queryClient into the loader&lt;/span&gt;
    &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;pokemonLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryClient&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RouterProvider&lt;/span&gt; &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It All Flows
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks link to /pokemon/pikachu
        │
        ▼
Router calls loader BEFORE mounting the component
        │
        ▼
loader calls: await queryClient.ensureQueryData(pokemonQuery("pikachu"))
        │
        ├── Cache HIT?  → Returns cached data instantly (no fetch)
        │
        └── Cache MISS? → Fetches from API, caches result, then returns
        │
        ▼
Component mounts → useQuery(pokemonQuery("pikachu"))
        │
        ▼
Data is already in cache → Renders IMMEDIATELY (no loading spinner)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Not Just Use &lt;code&gt;useQuery&lt;/code&gt; Alone?
&lt;/h2&gt;

&lt;p&gt;You totally &lt;em&gt;can&lt;/em&gt; use &lt;code&gt;useQuery&lt;/code&gt; by itself. But here's the difference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;useQuery&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;Component mounts → starts fetching → shows loading → shows data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ensureQueryData&lt;/code&gt; in loader + &lt;code&gt;useQuery&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Data fetched before mount → component renders with data instantly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The loader approach gives a &lt;strong&gt;smoother, faster UX&lt;/strong&gt; — especially on page navigations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ensureQueryData&lt;/code&gt;&lt;/strong&gt; = &lt;em&gt;"If cached, use cache. If not, fetch and cache it."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;shared query config function&lt;/strong&gt; (like &lt;code&gt;pokemonQuery&lt;/code&gt;) and use it in both the loader and the component.&lt;/li&gt;
&lt;li&gt;The loader &lt;strong&gt;pre-fills the cache&lt;/strong&gt; so &lt;code&gt;useQuery&lt;/code&gt; in the component finds the data immediately.&lt;/li&gt;
&lt;li&gt;You return &lt;strong&gt;only the params&lt;/strong&gt; from the loader — not the data itself. The data lives in React Query's cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Your pages now load instantly on navigation, and React Query handles caching, background refetching, and stale data for free.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>react</category>
    </item>
  </channel>
</rss>
