<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Frontend Development - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Frontend Development - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 18 Jun 2026 20:59:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/frontend-development/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Apply Academic Theories to Human-Centered Web Design [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever abandoned an app right at the sign‑up page? Or felt uneasy navigating a website because the buttons were scattered randomly, the colors clashed, and the layout felt confusing and unneces ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-apply-academic-theories-to-human-centered-web-design-handbook/</link>
                <guid isPermaLink="false">69fe29e9f239332df4f7cd02</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UX ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ux design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Great John ]]>
                </dc:creator>
                <pubDate>Fri, 08 May 2026 18:22:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d7621fda-83a6-460e-aa38-bce970d4a655.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever abandoned an app right at the sign‑up page? Or felt uneasy navigating a website because the buttons were scattered randomly, the colors clashed, and the layout felt confusing and unnecessarily complex?</p>
<p>Maybe you were asked to complete twenty fields in one go. You carefully filled everything out, hit Submit — and only then were you told that your password didn't meet some hidden, unspoken requirement. A requirement that was never communicated upfront.</p>
<p>Instead of helpful guidance, you were met with a vague message: “Invalid input." Invalid how, you wonder?</p>
<p>Required fields weren’t marked. There was no real‑time validation. No helpful red outline showing which field was wrong. Just a generic prompt telling you to “go back and correct missing information,” as if you’re supposed to magically know what the system wants.</p>
<p>So you scroll.</p>
<p>You search.</p>
<p>You guess.</p>
<p>And you're now getting frustrated.</p>
<p>The reason you're frustrated is simple: no one enjoys repeating a task they thought they had already completed — especially when the mistakes could've been prevented with clear guidance along the way.</p>
<p>You manage to fill in the form and you tap the Submit button.</p>
<p>Nothing happens.</p>
<p>No loading spinner.</p>
<p>No subtle animation.</p>
<p>No confirmation message.</p>
<p>No success screen.</p>
<p>Just silence. For a brief moment, you’re left wondering: Did it go through? So you tap again. And maybe… one more time.</p>
<p>At this point, you become fed up and you either postpone the signup process to when you have the time, or you may not ever return.</p>
<p>Even if you haven’t experienced this exact scenario, you’ve almost certainly felt the same kind of friction: that moment when a digital interface makes you pause, hesitate, or wonder what you’re supposed to do next.</p>
<p>These frustrations often arise because frontend developers either overlook or are unaware of the essential design principles and theories that underpin a smooth, intuitive user experience.</p>
<p>As a frontend developer, your interface should minimise cognitive load, provide immediate clarity, and guide users effortlessly through every task.</p>
<p>In this handbook, I'll introduce the academic theories that should inform and elevate your frontend decisions.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-10-fittss-law">1.0 Fitts’s Law:</a></p>
<ul>
<li><p><a href="#heading-11-use-padding-wisely">1.1 Use padding wisely</a></p>
</li>
<li><p><a href="#heading-12-use-infinite-targets">1.2 Use infinite targets</a></p>
</li>
<li><p><a href="#heading-design-takeaway-from-fitts-law">Design Takeaway from Fitts Law:</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-20-hicks-law">2.0 Hick's Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-hicks-law">Design Takeaway from Hick's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-30-gestalt-principles">3.0 Gestalt Principles:</a></p>
<ul>
<li><p><a href="#heading-key-gestalt-principles">Key Gestalt Principles:</a></p>
</li>
<li><p><a href="#heading-31-proximity">3.1 Proximity</a></p>
</li>
<li><p><a href="#heading-32-similarity">3.2 Similarity</a></p>
</li>
<li><p><a href="#heading-33-continuity">3.3 Continuity</a></p>
</li>
<li><p><a href="#heading-34-closure">3.4 Closure</a></p>
</li>
<li><p><a href="#heading-35-figureground">3.5 Figure/Ground</a></p>
</li>
<li><p><a href="#heading-36-common-fate">3.6 Common Fate</a></p>
</li>
<li><p><a href="#heading-37-focal-point">3.7 Focal Point</a></p>
</li>
<li><p><a href="#heading-design-takeaways-from-the-gestalt-principles">Design Takeaways from the Gestalt Principles</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-40-von-restorff-effect-the-isolation-effect">4.0 Von Restorff Effect (The Isolation Effect):</a></p>
<ul>
<li><a href="#heading-design-takeway-from-von-restorff">Design takeway from Von Restorff</a></li>
</ul>
</li>
<li><p><a href="#heading-50-jakobs-law">5.0 Jakob’s Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-jakobs-law">Design Takeaway from Jakob's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-60-millers-law">6.0 Miller’s Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-millers-law">Design Takeaway from Miller's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-70-the-goal-gradient-hypothesis">7.0 The Goal-Gradient Hypothesis</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-goal-gradient-hypothesis">Design Takeaway from Goal-Gradient Hypothesis</a></li>
</ul>
</li>
<li><p><a href="#heading-80-zeigarnik-effect">8.0 Zeigarnik Effect</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-zeigarnik-effect">Design Takeaway from Zeigarnik Effect</a></li>
</ul>
</li>
<li><p><a href="#heading-90-teslas-law">9.0 Tesla’s Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-teslas-law">Design Takeaway from Tesla's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-100-peak-end-rule">10.0 Peak End Rule:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-peak-end-rule">Design takeaway from Peak End Rule</a></li>
</ul>
</li>
<li><p><a href="#heading-110-postels-law">11.0 Postel’s Law:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-postels-law">Design Takeaway from Postel's Law</a></li>
</ul>
</li>
<li><p><a href="#heading-120-doherty-threshold">12.0 Doherty Threshold:</a></p>
<ul>
<li><a href="#heading-design-takeaways-from-doherty-threshold">Design Takeaways from Doherty Threshold</a></li>
</ul>
</li>
<li><p><a href="#heading-130-serial-position-effect-primacy-and-recency">13.0 Serial Position Effect (Primacy and Recency):</a></p>
<ul>
<li><a href="#heading-design-takeaways-serial-position-effect">Design Takeaways Serial Position Effect</a></li>
</ul>
</li>
<li><p><a href="#heading-140-occams-razor">14.0 Occam’s Razor:</a></p>
<ul>
<li><a href="#heading-design-takeaway-from-occams-razor">Design Takeaway from Occam's Razor</a></li>
</ul>
</li>
<li><p><a href="#heading-150-parkinsons-law">15.0 Parkinson's Law</a></p>
<ul>
<li><a href="#heading-design-takeaway-for-parkinsons-law">Design Takeaway for Parkinson's law</a></li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<p>You might wonder what academic theories have to do with frontend development.</p>
<p>The answer is simple. Academic theories aren't abstract ideas. There are the result of rigorous scientific investigation — controlled experiments, validated models, and decades of research into how humans think, learn, perceive, and interact with information.</p>
<p>Because these theories are grounded in evidence rather than opinion, they offer reliable guidance for building interfaces that align with how the human brain actually processes information.</p>
<p>Applying them to frontend development means you're not designing by guesswork or personal preference. Instead, you're applying tested, scientific insights to create clearer, faster, more humane user experiences.</p>
<p>In other words, when you build with academic theory in mind, your frontend becomes more than just visually appealing — it becomes cognitively efficient, behaviourally aligned, and measurably easier for users to navigate.</p>
<p>You can use the following laws and principles to guide your development work. Let’s start by looking at Fitt’s law.</p>
<h2 id="heading-10-fittss-law"><strong>1.0 Fitts’s Law:</strong></h2>
<p>Fitts’s law is the brainchild of Paul Fitts. He was among the early psychologists who recognised that many human errors result from flawed design rather than simple human weakness.</p>
<p>During World War II, he studied airplane cockpit layouts and concluded that numerous incidents attributed to pilot error were actually caused by poor design decisions (Hall, 2023; Budiu, 2022).</p>
<p>Here's the formula:</p>
<p>$$T = a + b \cdot \log_2\left(1 + \frac{D}{W}\right)$$</p>
<p>T = Movement Time</p>
<p>D = Distance to the target</p>
<p>W = Width (size) of the target</p>
<p>a, b = Empirically determined constants</p>
<p>Based on his findings, Fitts postulated that the time required to acquire/reach a target is determined by the distance to the target and the size of the target.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/7d2699bd-4878-4ab0-8669-21616cb8faf1.png" alt="7d2699bd-4878-4ab0-8669-21616cb8faf1" style="display:block;margin:0 auto" width="691" height="244" loading="lazy">

<p><em>Fig 1.0: Illustration of Fitts Law.</em></p>
<p>From the above, between Target B and Target C, it will be faster to interact with Target C than Target B simply because of the distance (Target B is farther away). Interestingly, though Target A and Target C are at the same distance, Target C will still be faster to interact with and less error-prone because of its larger size.</p>
<p>In simple terms, Fitt’s Law tells us that the time required to move to a target depends on two main factors: the distance to the target and the size of the target. The farther away an element is, the longer it takes to reach. The smaller it is, the more precision it demands, which increases the interaction time and the likelihood of errors.</p>
<p>Conversely, closer and larger targets reduce cognitive load, motor effort, and frustration.</p>
<p>In a nutshell, Fitts’s main message to developers is to reduce the distance users must travel on the screen and to make important buttons large and visually dominant.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/bc293949-cadc-46b2-892d-bdfa1dfc6db3.jpg" alt="bc293949-cadc-46b2-892d-bdfa1dfc6db3" style="display:block;margin:0 auto" width="928" height="664" loading="lazy">

<p><em>Fig 1.1: Showing Call-to-Action buttons are the largest and most visually prominent elements on each screen.</em></p>
<p>From the image above, you can see that the Call-to-Action buttons on each of the screens are the most visually dominant button and largest in size. They're also placed within the natural region. This makes them faster/easier to interact with.</p>
<p>You should also place your Call-to-Action button within the natural zone. This is a zone on a mobile phone where it's easy to reach with the thumb (as most people use their thumbs to select things on a phone screen). Here's a diagram showing the "natural zone" on a typical smartphone. It's much faster for a user to interact within the "natural zone" than the "hard zone" (see figure).</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e98d8d91-b4e6-4b4a-96e5-2524a6e09028.png" alt="e98d8d91-b4e6-4b4a-96e5-2524a6e09028" style="display:block;margin:0 auto" width="1536" height="1024" loading="lazy">

<p><em>Fig 1.2: Showing three different zones for buttons placement (natural, stretching and hard region)</em></p>
<h3 id="heading-11-use-padding-wisely">1.1 Use Padding Wisely</h3>
<p>Fitts' law can be applied to your development by increasing padding wisely. You can also use padding to increase the interactive area. By doing this, you're increasing the size of the targets.</p>
<p>This is important, because imagine a menu that disappears the moment your cursor drifts a few inches away. You’re weren't trying to close it — you simply moved slightly, and suddenly the entire menu collapses. That tiny slip forces you to start the interaction all over again. It’s a small mistake, but it creates a disproportionately frustrating experience.</p>
<p>This happens because the interactive area is too narrow.</p>
<p>That’s why effective padding — or more broadly, generous interactive zones — is essential. By increasing the clickable or hoverable area around a menu, you are increasing the size of the targets, which makes the interaction more stable, more forgiving, and far less cognitively demanding.</p>
<p>This ensures users can move naturally without fear of accidentally “falling off” the target.</p>
<h3 id="heading-12-use-infinite-targets">1.2 Use Infinite Targets</h3>
<p>Another fundamental principle that emerges from Fitt’s Law is the idea of infinite targets. When an interface element is placed at the very edge or corner of a screen, it becomes effectively “infinite” because the cursor can't move beyond the screen boundary. The edge acts as a physical barrier, allowing the user to fling the mouse in that direction without precision or careful aiming.</p>
<p>As a result, corners and edges become the fastest, easiest, and most reliable places for users to access important controls.</p>
<p>This is why operating systems such as Apple’s macOS and Microsoft Windows position their most essential menus and buttons at these locations. The macOS Apple Menu sits in the top‑left corner, Windows historically placed the Start button in the bottom‑left corner, and both systems anchor taskbars, docks, and notification areas along screen edges.</p>
<p>These placements reduce cognitive load, minimise motor effort, and increase interaction speed because users do not need to slow down or correct their cursor movement. The screen itself “catches” the pointer.</p>
<p>In essence, infinite targets transform small interface elements into large, easy‑to‑hit zones simply by leveraging the geometry of the screen.</p>
<p>What this means for you: place your most important and frequently used actions where users can reach them with the least effort. Screen edges and corners act as natural stopping points, meaning users can't overshoot them.</p>
<h3 id="heading-design-takeaways-from-fitts-law">Design Takeaways from Fitts Law:</h3>
<p><strong>Place Primary Actions Where the Task Ends:</strong><br>Placing a submit button at the top‑right forces users to travel all the way back after completing a long form. This increases interaction cost and breaks flow. The best place for a submit button is at the bottom of the form — exactly where the user finishes the task. This aligns with natural reading and interaction patterns.</p>
<p><strong>Keep Related Actions Physically Close:</strong><br>Separating “Add to Cart” and “Check Out” across opposite sides of the screen forces unnecessary thumb movement. Group related actions to reduce effort and speed up decisions.</p>
<p><strong>Make Primary Targets Large and Visually Dominant:</strong><br>Your main CTAs (“Subscribe Now,” “Pay Now,” “Create Account,” “Sign Up”) should be the most recognisable elements on the screen. Large, high‑contrast targets reduce errors and improve speed.</p>
<p><strong>Place High‑Value Actions at Screen Edges and Corners:</strong><br>Edges and corners act as “infinite targets” because the cursor can’t overshoot them. This makes them the fastest, easiest, and most reliable places for critical controls.</p>
<p>A tiny icon in the middle of the screen is hard to hit. The same icon placed at an edge becomes effectively huge because the boundary “catches” the pointer. Also, actions like navigation, primary CTAs, or global controls should live where users can reach them with minimal effort. Avoid burying important actions in the centre of the screen.</p>
<p><strong>Increase Target Size With Generous Padding:</strong><br>Small interactive zones force users to aim with pixel‑level precision. Adding padding expands the clickable or hoverable area, making interactions easier, faster, and more forgiving.</p>
<p><strong>Prevent Accidental “Fall‑Off” With Larger Hit Areas:</strong><br>Menus that collapse the moment the cursor drifts slightly create frustration. A wider interactive zone keeps the menu open during natural mouse movement, reducing accidental resets.</p>
<p>Users don’t move perfectly. Interfaces should accommodate slight slips without punishing them. Larger targets reduce cognitive load and eliminate unnecessary frustration. so by increasing the effective size of buttons, menus, and controls, you create interactions that feel stable and predictable, and users can move confidently without fear of losing their place.</p>
<p><strong>To Sum Up:</strong> The farther away an element is, the longer it takes to reach. The smaller it is, the more precision it demands, which increases the interaction time and the likelihood of errors. Conversely, closer and larger targets reduce cognitive load, motor effort, and frustration.</p>
<h2 id="heading-20-hicks-law"><strong>2.0 Hick's Law</strong>:</h2>
<p>Hick’s Law is a psychological principle that describes the relationship between the number of choices presented to a user and the time it takes them to make a decision. It was formulated by William Edmund Hick in 1952 (Yablonski, 2022; Proctor &amp; Scheider, 2018).</p>
<p>The law states that as the number of options increases, the decision time increases logarithmically. In simple terms, more choices slow users down, while fewer choices speed up decision-making.</p>
<p>$$T = a + b \cdot \log_2(n + 1)$$</p>
<p>Where:</p>
<p>T = time to make a decision,</p>
<p>n = number of choices,</p>
<p>b= a constant that depends on the task and the individual</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/d2c45f4b-77e9-42dc-a9c3-165dfe5f7ce7.jpg" alt="d2c45f4b-77e9-42dc-a9c3-165dfe5f7ce7" style="display:block;margin:0 auto" width="1136" height="692" loading="lazy">

<p><em>Figure 2.0 illustrates the relationship between user experience, reaction time, and the number of actions.</em></p>
<p>This is how users feel, for example, when they encounter a form that asks for too much information upfront. The longer the form gets, the more frustrated they become.</p>
<p>Examples of this are overloading menus with too many items, presenting long, unorganised forms, giving too many calls-to-action on one screen, and building nested menus with excessive depth.</p>
<p>All of these create friction and can lead to cognitive overload.</p>
<h3 id="heading-design-takeaway-from-hicks-law">Design Takeaway from Hick's Law</h3>
<p><strong>Avoid Overloading Users With Too Many Actions:</strong><br>Too many buttons, menu items, or choices at once increases cognitive load and slows decision‑making. Users freeze when everything competes for attention.</p>
<p><strong>Keep Navigation Clean and Focused:</strong><br>Cluttered menus hurt both usability and SEO. Search engines struggle to track overly complex navigation structures, and users struggle to find what matters.</p>
<p><strong>Use Progressive Disclosure to Reduce Complexity:</strong><br>Hide advanced or rarely used options under “More” or expandable sections. Reveal complexity only when the user needs it.</p>
<p><strong>Break Complex Tasks Into Smaller, Manageable Steps:</strong><br>Progressive disclosure works beautifully for multi‑step forms and decision flows. Smaller steps reduce overwhelm and improve completion rates.</p>
<p><strong>Group Related Options Into Logical Categories:</strong><br>Organising actions into meaningful clusters helps users process information faster. For example, placing “Edit” and “Delete” together leverages natural mental grouping.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/mbYIfRxSkHs" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 2.0: Video description of Progressive Disclosure.</em></p>
<p>From the video above, instead of showing all the menu details at once, it is better to hide them initially. As you can see, the additional information only appears when the arrow down button is pressed. This approach prevents overwhelming the user and keeps the interface clean and focused.</p>
<p>You should also reduce decision anxiety, as too many choices create doubt and friction (as they say, the more you ask from a user, the less you get).</p>
<p>Beyond this, try to use recommended labels, show brief descriptions, provide visual previews, and use comparison tables wisely to show comparison between products especially when they have many characteristics. An example of a comparison table is shown below:</p>
<p><a href="https://drive.google.com/file/d/1sY-tb9W1QDnyrH9dd3NSYsteP9WoMT27/view?usp=drive_link"><img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/5e52527a-3faf-4da1-97c2-a2a09ca7af8b.jpg" alt="Use of comparison table to compare products" style="display:block;margin:0 auto" width="1038" height="972" loading="lazy"></a></p>
<p><em>Figure 2.1: A comparison table being used to simplify complex information.</em></p>
<p>Also, rather than showing advanced configuration options by default, display only the most commonly used settings. Advanced options can be hidden under an expandable section like “Advanced” or “More Settings. This makes your interface less cluttered and more visually organized.</p>
<p>And speaking of visual organization, this is the perfect moment to introduce Gestalt principles — the psychological rules that explain how users naturally group and interpret what they see.</p>
<p><strong>To Sum Up</strong>: As the number of options increases, the decision time increases logarithmically.</p>
<h2 id="heading-30-gestalt-principles"><strong>3.0 Gestalt Principles</strong>:</h2>
<p>In the 1920s, a group of German psychologists – Max Wertheimer, Kurt Koffka, and Wolfgang Köhlern – introduced what are now known as the Gestalt Principles. Their work sought to understand how humans perceive and interpret visual information (Bustamante, 2023).</p>
<p>The word “Gestalt” is German for “unified whole,” reflecting the core idea behind the theory: people naturally perceive objects as organised patterns and complete forms rather than as separate, disconnected parts.</p>
<p>These principles explain how the human mind structures visual elements to make sense of the world. Over time, they have become highly influential in fields such as design, user experience (UX), psychology, and data visualization, where understanding perception is critical.</p>
<h3 id="heading-key-gestalt-principles">Key Gestalt Principles:</h3>
<h3 id="heading-31-proximity">3.1 Proximity</h3>
<p>Elements that are placed close to each other are perceived as a group, while those spaced far apart are seen as separate. This is why labels are placed directly next to their corresponding input fields.</p>
<p>For example: In a blog feed, the "Title," "Author," and "Date" should have small margins between them (8px), while the space between one blog post card and the next should be much larger (40px). This tells the user's brain: "These three text strings belong to this specific post."</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/b68ab7f4-1b2e-47f1-8e8c-8ae8f32198c1.jpg" alt="b68ab7f4-1b2e-47f1-8e8c-8ae8f32198c1" style="display:block;margin:0 auto" width="1046" height="956" loading="lazy">

<p><em>Fig 3:0 Illustration of proximity (Gestalt Principle)</em></p>
<p>From the fig above, the spacing within the blog feed plays a powerful role in how effortlessly users interpret what they see. When elements sit close together, the brain instinctively treats them as belonging to the same unit. This is why placing the author credit just 8px beneath the title creates an immediate mental link. The viewer doesn’t need to pause or decode who wrote which article; proximity does the cognitive work automatically, forming a tight, intuitive grouping.</p>
<p>Equally important is the generous 40px gap between individual cards. This larger spacing introduces “visual breathing room.” Without it, a feed can quickly collapse into a dense wall of text, overwhelming the user and discouraging exploration. The wider margin establishes a clear boundary—a natural stop-and-start rhythm—that makes each card feel distinct and the entire layout more scannable.</p>
<p>Finally, subtle spacing differences can guide behaviour, not just perception. The slightly larger 12px margin above the read‑more link separates it from the passive information above it. This spacing cues the user that the link represents an action rather than another piece of descriptive text. It’s a small adjustment, but it shifts the element’s role from informational to interactive, helping users understand what they can <em>do</em> next.</p>
<p>Together, these spacing decisions transform a simple list of posts into a structured, intuitive, and behaviourally clear interface—one where the user never has to think about the layout, because the layout is already thinking for them.</p>
<p>Proximity controls meaning: move elements closer to show connection, separate them to show difference.</p>
<h3 id="heading-32-similarity">3.2 Similarity</h3>
<p>We naturally group elements that share similar visual characteristics, such as color, shape, size, or orientation.</p>
<p>For example, even if buttons are spread across a page, if they're all the same shade of blue, the user understands they perform similar functions.</p>
<p>If your primary "Submit" button is blue with rounded corners, every other primary action on your site should look exactly the same. If you suddenly use a square red button for a primary action, the user will be confused because the "similarity" is broken.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/151d9658-5a4b-4d1d-b885-02c73c53cdbd.jpg" alt="151d9658-5a4b-4d1d-b885-02c73c53cdbd" style="display:block;margin:0 auto" width="1618" height="2170" loading="lazy">

<p><em>Fig 3:1 : illustration of similarity (Gestalt principle)</em></p>
<p>As you can see from above, the layout clearly demonstrates how the Gestalt Principle of Similarity works by showing two different visual situations: one where everything matches, and one where a single element breaks the pattern.</p>
<p>All three product cards share the same visual characteristics:</p>
<ul>
<li><p>Same card shape</p>
</li>
<li><p>Same border and shadow</p>
</li>
<li><p>Same image size and placement</p>
</li>
<li><p>Same blue “Add to Cart” button</p>
</li>
<li><p>Same font style and spacing</p>
</li>
</ul>
<p>Because these elements look alike, your brain automatically groups them as one category — “products that belong together.”<br>You don’t have to think about it; the similarity creates instant visual unity.</p>
<p>This is the Gestalt Principle of Similarity in action.</p>
<p>In the second row, everything is still similar except one button:</p>
<ul>
<li><p>The middle product’s button is orange, not blue</p>
</li>
<li><p>It has square corners, not rounded</p>
</li>
<li><p>The text is italic, not regular</p>
</li>
<li><p>The label changes to “Quick Buy”</p>
</li>
</ul>
<p>Because this button breaks the shared pattern, your brain immediately notices it and treats it as different or special.</p>
<p>Developers can use broken similarity to intentionally highlight featured items, promotions, or urgent actions.</p>
<p>When similarity is broken, the different element stands out and draws attention.</p>
<h3 id="heading-33-continuity">3.3 Continuity</h3>
<p>The human eye prefers to follow a continuous path or curve rather than jagged or broken lines. We perceive items aligned on a line or curve as being related. This is often used in navigation menus or horizontal carousels to guide the user's gaze.</p>
<p>For example, you might have a horizontal carousel where the last visible card is slightly "cut off" at the edge of the screen. This visual break creates a path that encourages the user to keep scrolling as their eyes follow the line of cards.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/e44556e8-4174-4073-9c05-63b5df9a1278.jpg" alt="e44556e8-4174-4073-9c05-63b5df9a1278" style="display:block;margin:0 auto" width="1650" height="214" loading="lazy">

<p><em>Fig 3:2: illustration of continuity (Gestalt principle)</em></p>
<p>As you can see, all four form fields — <em>First Name</em>, <em>Last Name</em>, <em>Email Address</em>, and <em>Phone Number</em> — are perfectly aligned along one continuous horizontal path. Because the human eye naturally prefers to follow an unbroken line, your gaze moves smoothly from left to right across the fields without effort.</p>
<p>The final field is slightly cut off at the edge, which creates a subtle visual cue that the line continues beyond the visible area. This encourages the user to keep scrolling or swiping, because their eyes are already following the direction of the form.<br>when elements are arranged along a straight path, curve, or flow, the brain automatically treats them as connected and expects the pattern to continue.</p>
<p>Another example is Instagram Stories, which are arranged in a smooth horizontal line at the top of the app. Instagram reinforces this by slightly revealing the next story circle at the edge of the screen. That tiny “peek” acts as a continuation cue — your eyes expect the line to keep going, so your finger follows.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/bb019a6d-33c9-4509-b1c7-191b5a453c46.jpg" alt="bb019a6d-33c9-4509-b1c7-191b5a453c46" style="display:block;margin:0 auto" width="1320" height="520" loading="lazy">

<p><em>Fig 3:3: illustration of continuity (Gestalt principle)</em></p>
<p>As you can see from above, all the circular story icons are arranged in a straight horizontal line, and your visual system instinctively follows that line from left to right without effort.</p>
<p>The slight visibility of the next story at the edge of the screen strengthens this effect, signaling that the sequence continues beyond what's currently shown. Also, because the icons share the same size, spacing, and shape, there are no visual interruptions, allowing your eyes to glide across them in one continuous motion.</p>
<p>This seamless flow is exactly what continuity describes: the tendency of the human eye to follow the direction of a line or pattern, assuming it continues even when part of it is out of view.</p>
<p>Continuity is the tendency of the human eye to follow the direction of a line or pattern, assuming it continues even when part of it is out of view.</p>
<h3 id="heading-34-closure">3.4 Closure</h3>
<p>Closure refers to the mind’s ability to perceive a complete, unified form even when parts of that form are missing. Rather than requiring every boundary, line, or shape to be explicitly drawn, the brain instinctively fills in the gaps. When used intentionally, closure allows interfaces to feel cleaner, more elegant, and more cognitively efficient.</p>
<p>When we look at a complex arrangement of visual elements, we tend to look for a single, recognisable pattern. If an image is missing parts, our brains fill in the gaps to "close" the shape.</p>
<p>One of the most celebrated examples of closure in visual identity design is the panda symbol used by the World Wide Fund for Nature (WWF). This logo demonstrates how strategic omission can produce a memorable, emotionally resonant, and universally recognisable mark.</p>
<p>At first glance, the panda illustration appears simple, composed of a few bold black shapes arranged against a white background.</p>
<p>Yet a closer look reveals that the panda is not fully drawn. There are no outlines defining the body, no complete contours around the head, and no explicit boundaries separating limbs from background. Instead, the designer uses a series of carefully placed shapes (ears, eye patches, nose, and partial limbs) to imply the rest of the animal. The viewer’s mind fills in the missing information, completing the silhouette effortlessly.</p>
<p>This is closure at its most effective: the brain constructs a whole from fragments, creating a sense of completeness without visual overload.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/0c40effd-e4d2-4669-af1d-56faac976b4a.jpg" alt="0c40effd-e4d2-4669-af1d-56faac976b4a" style="display:block;margin:0 auto" width="522" height="784" loading="lazy">

<p><em>Fig 3:4: illustration of closure (Gestalt principle)</em></p>
<p>For example, a "hamburger menu" (three lines) isn't a literal drawer, but our brains "close" the shape to understand it represents a menu.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/b67253cd-2a2c-4565-9818-f5586dad6d36.jpg" alt="b67253cd-2a2c-4565-9818-f5586dad6d36" style="display:block;margin:0 auto" width="1244" height="236" loading="lazy">

<p><em>Fig 3:5: illustration of closure (Gestalt principle)</em></p>
<p>An example of closure in practice can be seen in step indicators commonly used in checkout flows. These components often rely on partial shapes, implied boundaries, and incomplete outlines to guide the user through a sequence of actions.</p>
<p>For instance, upcoming steps may be represented by dashed circles. Although the circles aren't fully drawn, the viewer immediately recognises them as complete shapes. The brain resolves the missing segments, allowing the interface to communicate progression without heavy borders or fully rendered icons. This subtle use of closure reduces visual clutter while preserving clarity.</p>
<p>Closure refers to the mind’s ability to perceive a complete, unified form even when parts of that form are missing.</p>
<h3 id="heading-35-figureground">3.5 Figure/Ground</h3>
<p>This principle describes the mind's tendency to separate an object (the figure) from its surrounding area (the ground or background). In web design, using a "modal" or "pop-up" relies on this: by blurring the background, you force the user to see the pop-up as the focal figure.</p>
<p>When a user clicks "Login" on a modal/lightbox, the background site often dims (the "Ground") while the login box stays bright and centered (the "Figure"). This immediate depth change tells the user exactly where their attention belongs.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/UCdjymjASOU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.5.0 Video description of Figure/Ground (Gestalt Principle)</em></p>
<p>From the video above, you can see that when the Quick View button is clicked, the selected figure stands out while the background darkens. This contrast guides the user’s attention and helps them focus on the figure. Developers can use this technique to direct users’ attention to what matters most or to what they want users to notice.</p>
<p>This principle describes the mind's tendency to separate an object (the figure) from its surrounding area (the ground or background).</p>
<h3 id="heading-36-common-fate">3.6 Common Fate</h3>
<p>Elements that move in the same direction are perceived as more related than elements that are stationary or move in different directions. Think of a dropdown menu: when all sub-items slide down together, they are clearly part of the same "unit."</p>
<p>For example, when you click a FAQ header and five sub-items slide down at the exact same speed and direction, the "Common Fate" tells the user that all those items belong to that specific category. If they flew in from different directions, the relationship would be lost.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/T10xmlne6E4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.6.1 Video description of common fate (Gestalt Principle)</em></p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/5FncGLRy0bM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 3.6.2 Video description of common fate (Gestalt Principle)</em></p>
<p>From the video shown above, the e‑commerce animation example demonstrates these principles clearly by using two distinct motion patterns: a group of regular products that move upward together, and a pair of special‑category items that enter dramatically from the left. Through these contrasting movements, the interface communicates category differences without relying on text labels or explicit instructions.</p>
<p>Therefore, developers can use this motion‑based differentiation as a design strategy to guide users’ perception—allowing the interface to signal hierarchy, category structure, and product importance purely through animated behaviour rather than through static visual labels.</p>
<p>Elements that move in the same direction are perceived as more related than elements that are stationary or move in different directions.</p>
<h3 id="heading-37-focal-point">3.7 Focal Point</h3>
<p>Whatever stands out visually will capture and hold the viewer’s attention first. This is essentially the principle of emphasis. A bright "Sign Up" button in a sea of gray text acts as the focal point, directing the user's primary action.</p>
<p>For example, an alert banner or a pricing table should stand out from its surroundings. Beyond this, in a three-tier pricing table (Basic, Pro, Enterprise), the "Pro" column is often slightly larger or a different color. This creates a focal point that draws the eye to the "recommended" option immediately.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/711c9ee9-b546-4041-bf86-30c80154bd47.jpg" alt="711c9ee9-b546-4041-bf86-30c80154bd47" style="display:block;margin:0 auto" width="1784" height="646" loading="lazy">

<p><em>Fig 3:7: illustration of closure (Gestalt principle)</em></p>
<p>In visual interface design, the Gestalt principle of Focal Point plays a crucial role in directing user attention toward the most important element on a screen.</p>
<p>A focal point is created when one element breaks the established pattern of surrounding elements, making it stand out immediately.</p>
<p>In e‑commerce interfaces, this principle is often applied to highlight primary actions such as purchasing, subscribing, or upgrading. The “Buy Now” button provides a clear and practical example of how focal points function within a layout.</p>
<p>From the example above, the first two buttons share the same visual characteristics: neutral colours, and regular weight text. This repetition establishes a visual pattern that the user quickly becomes familiar with.</p>
<p>But the “Buy Now” button intentionally disrupts this pattern. It uses a bright colour, which contrasts sharply with the muted tones of the other buttons. This colour difference alone is enough to draw the eye, as humans are naturally sensitive to changes in hue and saturation within a uniform environment.</p>
<p>The Focal Point may sound like it's similar to the principle of Similarity, but the two operate in completely opposite ways within perceptual psychology.</p>
<p>Similarity explains how the mind naturally groups elements that share visual characteristics –&nbsp;such as colour, shape, or size – into coherent units. Once this grouping is established, the interface gains structure and predictability.</p>
<p>Focal Point, on the other hand, works by intentionally breaking that structure. Instead of reinforcing uniformity, it introduces a deliberate contrast – through colour, scale, brightness, or motion –&nbsp;to draw the viewer’s attention to one specific element.</p>
<p>In other words, Similarity creates the background pattern, while Focal Point identifies the one element that must stand out against that pattern.</p>
<p>Whatever stands out visually will capture and hold the viewer’s attention first.</p>
<h3 id="heading-design-takeaways-from-the-gestalt-principles">Design Takeaways from the Gestalt Principles</h3>
<p><strong>Use Spacing as Your Primary Grouping Tool:</strong><br>Elements that belong together should sit closer to each other than to anything else. Spacing communicates structure faster than borders or boxes. Use tight internal spacing (6–12px) for related items and wide external spacing (24–48px) to separate groups.</p>
<p><strong>Build a Strict, Consistent Visual System — and Stick to It:</strong><br>Define clear rules for button types, text styles, icon sizes, and alignment patterns. Consistent left‑aligned text blocks, predictable carousel lines, and stable flow patterns reduce cognitive load and make interfaces feel trustworthy.</p>
<p><strong>Guide the Brain With Spacing, Alignment, Consistency, Contrast, and Motion:</strong><br>The human brain is always trying to group, follow, and prioritise what it sees. Your job is to guide that instinct through intentional layout decisions, not fight against it.</p>
<h2 id="heading-40-von-restorff-effect-the-isolation-effect"><strong>4.0 Von Restorff Effect (The Isolation Effect)</strong>:</h2>
<p>This is the brainchild of Hedwig von Restorff, posited in 1933. In principle it states: An item that stands out is more noticable and more likely to be remembered than other items (Hunt, 1995).</p>
<p>So unique or visually distinct elements grab attention and are more memorable – in other words, distinctiveness dictates memory. When a user interacts with an interface, their brain naturally seeks patterns to minimize cognitive effort.</p>
<p>While consistency is generally a virtue in design, a perfectly uniform layout can lead to "banner blindness" or habituation, where the user stops noticing details.</p>
<p>By strategically breaking a pattern through changes in color, size, shape, or spacing, the developer can "isolate" an element, triggering a biological response that flags the item as high-priority.</p>
<p>Note that although the Focal Point principle may initially seem similar to the Von Restorff Effect, they describe two different psychological processes.</p>
<p>Focal Point is a Gestalt visual principle that explains how one element becomes the centre of attention within a composition because it carries the strongest visual contrast –&nbsp;through size, colour, brightness, position, or motion. Its purpose is to guide the viewer’s eye toward the most important element in the layout.</p>
<p>The Von Restorff Effect comes from cognitive psychology, not Gestalt theory. It states that an item that is noticeably different from a group of similar items is not only more attention‑grabbing but also more memorable.</p>
<p>So Focal Point is about where the eye goes first, while the Von Restorff Effect is about what the brain remembers later.</p>
<h3 id="heading-design-takeaways-from-von-restorff">Design takeaways from Von Restorff</h3>
<p><strong>Use Isolation to Make CTAs Impossible to Miss:</strong><br>On a page filled with neutral text and standard links, a single high‑contrast button (like a bold “Primary Blue” or “Emergency Red”) instantly becomes the standout element. This leverages the Von Restorff Effect to pull the user’s eye toward the most important action.</p>
<p><strong>Create a Visual “Hitch” in the Scan Path:</strong><br>A distinct CTA interrupts the user’s natural left‑to‑right, top‑to‑bottom scanning rhythm. This makes actions like “Buy Now” or “Sign Up” the first thing they notice and the last thing they forget.</p>
<p><strong>Make Critical Actions Visually Distinct:</strong><br>Because users naturally notice the one element that breaks a pattern, your most important actions should use deliberate contrast — color, size, shape, weight, or motion. Isolate key information instead of letting it blend into surrounding UI noise.</p>
<p><strong>Avoid Over‑Differentiation — or Nothing Stands Out:</strong> If every button is loud, animated, or uniquely styled, the interface becomes chaotic. The Von Restorff Effect only works when there is a clear, stable pattern — and you break it once, intentionally.</p>
<p><strong>To Sum Up:</strong> An item that stands out is more noticable and more likely to be remembered than other items.</p>
<h2 id="heading-50-jakobs-law"><strong>5.0 Jakob’s Law</strong></h2>
<p>Jakob’s Law states that users spend most of their time on other sites, so they expect your interface to behave like the ones they already know.</p>
<p>Familiar patterns — hamburger menus, top navigation, search icons, and clickable top‑left logos — reduce cognitive load because users don’t have to interpret anything new.</p>
<p>But while Jakob’s Law is foundational to UX, I think it can also unintentionally suppress innovation.</p>
<p>When developers over‑prioritise familiarity, they fall into a standardisation trap: endlessly optimising conventional patterns instead of exploring fundamentally better ones.</p>
<p>The Pie Menu is a perfect illustration of this. According to Fitts’s Law, the time required to reach a target depends on its distance and size. Linear menus place the last item much farther from the cursor than the first, creating uneven interaction costs.</p>
<p>Radial menus position every option at an equal distance from the centre, and their wedge‑shaped targets effectively grow larger as the pointer moves outward.</p>
<p>Mathematically, pie/radial menu are faster to interact with and more efficient — yet they remain rare in mainstream web design because they violate users’ expectations. In other words, Jakob’s Law keeps us locked into a familiar but suboptimal pattern simply because “that’s how it’s always been done.”</p>
<p>But the challenge is not choosing between familiarity and innovation, but balancing them.</p>
<p>This is where the Aesthetic–Usability Effect becomes powerful. Research shows that users perceive attractive interfaces as easier to use, and they are more forgiving of minor usability friction when the design is visually pleasing.</p>
<p>A beautifully crafted Pie Menu, for example, can encourage users to invest the small amount of learning required to use it. By applying aesthetic delight strategically, developers can introduce innovative patterns without overwhelming users.</p>
<p>The principle that emerges is simple: Be conventional where it matters, and innovative where it delights.</p>
<h3 id="heading-design-takeaway-from-jakobs-law">Design Takeaway from Jakob's Law</h3>
<p><strong>Keep Trust‑Critical Elements Predictable:</strong><br>Navigation, search, authentication, and other high‑stakes interactions must follow established conventions. Users rely on these patterns for speed, confidence, and safety — this is where Jakob’s Law should be respected without exception.</p>
<p><strong>Experiment Only in Low‑Risk, High‑Creativity Areas:</strong><br>In creative or productivity‑focused zones — like editing tools in a photo app — you can safely introduce new interaction models such as radial menus, gesture wheels, or context‑aware tool selectors. These areas invite exploration and benefit from efficiency‑driven innovation.</p>
<p><strong>To Sum Up:</strong> Be conventional where it matters, and innovative where it delights.</p>
<h2 id="heading-60-millers-law"><strong>6.0 Miller’s Law</strong></h2>
<p>Miller’s Law originates from George A. Miller’s classic paper “The Magical Number Seven, Plus or Minus Two.” It states that the average person can hold only about 7 (±2) chunks of information in working memory at any given moment (Miller, 1956).</p>
<p>Crucially, Miller emphasised that the brain doesn’t store isolated items — it groups them into meaningful units called chunks. Because working memory is so limited, developers must structure information in ways that respect this cognitive boundary.</p>
<p>This principle has direct implications for interface design. Long, unbroken strings of information overwhelm users, whereas chunked formats are far easier to process.</p>
<p>For example, instead of displaying a phone number as 1234567890, formatting it as 123‑456‑7890 transforms ten digits into three manageable chunks. The same logic applies to navigation: aim for five to nine primary menu items, and if you need more, group them into categories. Users remember the category as a single chunk rather than each individual link.</p>
<p>Miller’s Law also explains why long forms are so intimidating. When a user sees 30 fields on one page, their brain interprets it as a single, massive task — far beyond the 7±2 limit.</p>
<p>A progressive stepper solves this by breaking the form into smaller stages of 5–7 fields each. This reduces cognitive load, creates a sense of progress, and significantly lowers abandonment rates.</p>
<p>The same principle applies to product listings or search results. Expecting users to compare 50 items at once is unrealistic. Instead, provide strong filtering tools so users can narrow the set to a manageable size — ideally within the range their working memory can meaningfully evaluate.</p>
<p>In essence, Miller’s Law reminds developers that humans don’t process information in bulk. They process it in structured, meaningful chunks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6998def93dc17c4862075045/3add2891-c6f8-4df1-a044-6cea6c5f1945.jpg" alt="Image of progressive stepper" style="display:block;margin:0 auto" width="818" height="960" loading="lazy">

<p><em>Fig 6.0: Illustrating progressive stepper</em></p>
<p>In the example above, the interface uses both a progress bar and a stepper to guide the user through multiple stages of a task. After completing the first page and selecting “Continue,” the user moves to the next step, and the progress bar updates accordingly. This creates a clear sense of forward movement and accomplishment.</p>
<p>By breaking the process into smaller segments, the interface prevents cognitive overload. If all the information were presented on a single page, users might feel overwhelmed, unsure where to begin, or discouraged by the sheer volume of work.</p>
<p>A step‑by‑step flow transforms a large task into a sequence of manageable actions, increasing the likelihood of completion.</p>
<h3 id="heading-design-takeaway-from-millers-law">Design Takeaway from Miller's Law</h3>
<p><strong>Respect the 7±2 Working‑Memory Limit:</strong><br>Users can only hold about seven chunks of information at once. Long, unbroken content overwhelms them, while chunked information is instantly easier to process.</p>
<p><strong>Chunk Information Into Meaningful Units:</strong><br>The brain doesn’t store isolated items — it groups them. Format data (like phone numbers), menus, and settings into clear, memorable chunks instead of long, flat lists.</p>
<p><strong>Keep Navigation Within 5–9 Primary Items:</strong><br>If you need more than nine options, group them into categories. Users remember the category as a single chunk, not each individual link.</p>
<p><strong>Break Long Forms Into Smaller Steps:</strong><br>A 30‑field form feels like one giant task. A progressive stepper with 5–7 fields per step keeps users below the cognitive overload threshold and dramatically reduces abandonment.</p>
<p><strong>Reduce Comparison Load With Strong Filters:</strong><br>Expecting users to compare 50 products at once is unrealistic. Provide filtering tools that shrink the decision set to something the working memory can actually handle.</p>
<p><strong>Design for Chunked Thinking, Not Bulk Processing:</strong><br>Humans don’t process information in bulk — they process structured, meaningful groups. Interfaces that respect this limitation feel lighter, faster, and more intuitive.</p>
<p><strong>To Sum Up:</strong> A step‑by‑step flow transforms a large task into a sequence of manageable actions, increasing the likelihood of completion.</p>
<h2 id="heading-70-the-goal-gradient-hypothesis">7.0 The Goal-Gradient Hypothesis</h2>
<p>This is the perfect moment to introduce the Goal‑Gradient Hypothesis, originally proposed by behaviorist Clark Hull in 1932 (Yablonski, 2022). The hypothesis states that people become more motivated as they get closer to achieving a goal. In other words, users naturally accelerate their engagement when they sense they are nearing completion.</p>
<p>This principle is incredibly powerful in UX design, especially for progress tracking, gamification, and reward systems.</p>
<p>The takeaway is straightforward: Because users are more motivated near the finish line, progress indicators should be prominent and meaningful.</p>
<p>Percentages, progress bars, and step counters reinforce momentum. Micro‑achievements — such as badges, checkmarks, or subtle confetti — amplify motivation by celebrating small wins.</p>
<p>Tasks should be broken into measurable milestones so users can see themselves advancing.</p>
<p>This is why e‑learning platforms display messages like “You’ve completed 8 of 10 lessons — almost there!” and why fitness apps highlight progress with prompts such as “3 km done, 2 km to go.” These cues leverage the goal‑gradient effect to keep users engaged, energized, and eager to finish.</p>
<p>By combining progressive steppers with clear progress feedback, developers create interfaces that feel lighter, more encouraging, and far more motivating — ultimately improving completion rates and overall user satisfaction.</p>
<p>But what happens when a goal isn't completed? Why do we sometimes feel uncomfortable leaving things unfinished? That discomfort is explained by another psychological principle called the Zeigarnik Effect — the tendency for people to remember and feel tension about incomplete tasks. We will look at this next.</p>
<h3 id="heading-design-takeaway-from-goal-gradient-hypothesis">Design Takeaway from Goal-Gradient Hypothesis</h3>
<p><strong>Make Progress Visible to Boost Motivation:</strong><br>According to the Goal‑Gradient Hypothesis, users naturally speed up as they sense they’re nearing completion. Prominent progress bars, percentages, and step counters tap into this instinct and keep momentum high.</p>
<p><strong>Celebrate Micro‑Achievements to Reinforce Engagement:</strong><br>Badges, checkmarks, subtle confetti, and “step completed” cues reward small wins. These micro‑rewards amplify motivation and make long tasks feel lighter and more achievable.</p>
<p><strong>Break Tasks Into Measurable Milestones:</strong><br>Users stay motivated when they can see themselves advancing. Divide complex flows into clear steps so progress feels tangible rather than overwhelming.</p>
<p><strong>Use Progress Feedback to Drive Completion:</strong><br>Messages like “8 of 10 lessons completed — almost there” or “3 km done, 2 km to go” leverage the goal‑gradient effect to energise users and pull them toward the finish line.</p>
<p><strong>Combine Steppers With Clear Feedback for Maximum Impact:</strong><br>Progressive steppers paired with strong visual feedback create interfaces that feel encouraging, structured, and motivating — dramatically improving completion rates.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/-mDuFB3W2bg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p><em>Video 8.0 : Video illustrating goal gradient</em></p>
<p><strong>To Sum Up:</strong> People become more motivated as they get closer to achieving a goal.</p>
<h2 id="heading-80-zeigarnik-effect"><strong>8.0 Zeigarnik Effect</strong></h2>
<p>The Zeigarnik Effect is a psychological principle stating that people remember unfinished or interrupted tasks better than completed ones (Cherry, 2024).</p>
<p>Memory begins with sensory input, which is processed into short-term memory. Unfinished tasks persist in our thoughts, leading to active recall. This ongoing engagement can turn them into long-term memories, enhancing recall until resolved. This increases engagement, encourages task completion, improves retention, and drives conversions.</p>
<p>So because people remember unfinished tasks better than completed ones (Zeigarnik Effect), developers use progress indicators to make users aware that something is incomplete and motivate them to finish it.</p>
<p>In your designs, you can break long forms into multi-step processes to encourage completion and display profile completion percentages (for example, 70% complete) to push users toward 100%.</p>
<p>This is the main reason why e-commerce platforms send abandoned cart reminders to bring users back to complete their purchases. It's also why apps use streak systems to encourage daily engagement and habit formation and learning platforms show course completion bars to motivate users to finish modules.</p>
<h3 id="heading-design-takeaway-from-zeigarnik-effect">Design Takeaway from Zeigarnik Effect</h3>
<p><strong>Unfinished Tasks Stay Active in Memory — Use That to Drive Completion:</strong><br>Because incomplete tasks linger in working memory (Zeigarnik Effect), users naturally keep thinking about what they haven’t finished. This tension boosts recall, engagement, and the likelihood of returning to complete the task.</p>
<p><strong>Make Incompleteness Visible With Progress Indicators:</strong><br>Progress bars, percentages, and step counters remind users that something is still unfinished. This gentle psychological pressure motivates them to continue until the task is complete.</p>
<p><strong>Break Long Flows Into Multi‑Step Processes:</strong><br>A massive form feels overwhelming, but a stepper with smaller chunks keeps users moving. Showing “70% complete” nudges them toward finishing the last stretch.</p>
<p><strong>Use Reminders to Re‑activate Unfinished Intent:</strong><br>Abandoned cart emails, streak reminders, and “continue your lesson” prompts work because the unfinished task is already active in the user’s mind. The reminder simply pulls them back into the loop.</p>
<p><strong>Celebrate Completion to Close the Cognitive Loop:</strong><br>Checkmarks, confirmations, and completion badges give users closure. This resolves the mental tension created by the unfinished task and reinforces positive behaviour.</p>
<p><strong>To Sum Up:</strong> Unfinished tasks persist in our thoughts, leading to active recall.</p>
<h2 id="heading-90-teslers-law"><strong>9.0 Tesler’s Law</strong>:</h2>
<p>This law was proposed by Lawrence Tesler. He was a computer scientist known for his work on human-computer interaction, and he contributed significantly to making software more user-friendly, including work on cut, copy, and paste functionality.</p>
<p>This law is otherwise known as the Law of Conservation of Complexity. The core Idea here is every process has a certain amount of “inherent complexity" that can't be removed. You can only decide who handles it: the user or the system.</p>
<p>Some examples of these inherent complexities might be:</p>
<ul>
<li><p>translating user actions into correct operations behind the scenes,</p>
</li>
<li><p>handling unreliable or slow network connections,</p>
</li>
<li><p>connecting with third-party APIs, services, or legacy systems,</p>
</li>
<li><p>sorting large datasets quickly,</p>
</li>
<li><p>performing complex search operations</p>
</li>
<li><p>managing version changes and compatibility issues,</p>
</li>
<li><p>managing state, interactions, and animations without confusing the user.</p>
</li>
</ul>
<p>All of these can be inherently complex, but it's the job of the developer to deal with the complexity.</p>
<p>As a developer, you should always try as much as possible to push complexity to the system. For example, instead of making a user type their full address manually, use an Auto-complete API (Google’s Places and Map is best for this). The complexity of finding and validating the address still exists, but the software handles the work for them.</p>
<p>Here's a practical example: let’s say you're designing a student platform that requires users to enter their university name. A practical approach would be to store an array of all universities in the UK in your codebase (This is the hard part Tesla hinted at).</p>
<p>As the user types, they don't need to enter the full name, and their full university name is shown (relating to what they have typed). For instance, if they intend to type “University of Sheffield,” simply typing “Sheff” should prompt the system to display the full university name, which they can then select.</p>
<p>In Dart, you can use a package like fuzzysearch to implement this kind of intelligent matching.</p>
<p>The advantage of this approach is greater than it first appears. It improves data consistency because users often enter the same information in different ways. For example, some users might type “Uni of Sheff,” others “Sheffield University,” and others “Uni of Sheffield,” while all are referring to “University of Sheffield.”</p>
<p>This is how messy data is created, and it creates more work for data analysts. Little wonder that data analysts spend up to 70% of their time cleaning data.</p>
<p>If developers invested more time in structuring how data is collected to ensure consistency, there would be far less work downstream for analysts. This same logic should be applied in how we collect date, time, and other information.</p>
<p>So apart from people's names and email addresses, you should try to standardize the data your app collects as much as possible. Use date and time pickers, stepper controls, input masks, checkboxes, dropdown menu and radio buttons, toggle switches. and so on.</p>
<p>The essence of removing complexity from the user is not only about improving usability, but also about ensuring that the data collected is standardised, structured, and consistent.</p>
<h3 id="heading-design-takeaway-from-teslers-law">Design Takeaway from Tesler's Law</h3>
<p><strong>Push Complexity to the System, Not the User:</strong><br>Every process contains unavoidable complexity. Your job is to handle it behind the scenes so the user experiences the simplest possible interaction.</p>
<p><strong>Automate Tasks Users Shouldn’t Have to Think About:</strong><br>Use tools like autocomplete, fuzzy search, intelligent defaults, and validation APIs to remove manual effort. The complexity still exists — but the system absorbs it instead of the user.</p>
<p><strong>Standardise Inputs to Prevent Messy Data:</strong><br>Users enter the same information in wildly different ways. Use pickers, dropdowns, input masks, radio buttons, and toggles to enforce consistent, structured data collection.</p>
<p><strong>Handle Inherent Technical Complexity Internally:</strong><br>Network issues, API quirks, large dataset sorting, search optimisation, state management, and animation logic are all developer responsibilities. Users should never feel this complexity.</p>
<p><strong>To Sum Up:</strong> Every process contains unavoidable complexity. Your job as a developer is to handle it behind the scenes so the user experiences the simplest possible interaction.</p>
<h2 id="heading-100-peak-end-rule"><strong>10.0 Peak End Rule</strong>:</h2>
<p>In 1993, Daniel Kahneman, Barbara Fredrickson, Charles Schreiber, and Donald Redelmeier invited volunteers into a lab for what sounded like a simple experiment. The task was straightforward: place a hand into a container of painfully cold water (Kahneman et al., 1993)</p>
<p>In the first round, participants kept their hand in 14°C water for 60 seconds. It was uncomfortable, sharp, and unpleasant but after one minute, it was over.</p>
<p>In the second round, they again endured 60 seconds in 14°C water. But this time, they were asked to keep their hand in for an extra 30 seconds. The temperature was raised slightly to 15°C. Still cold. Still unpleasant. Just slightly less intense.</p>
<p>Objectively, the second experience was worse. It lasted 90 seconds instead of 60. More total pain. More suffering.</p>
<p>Later, the researchers asked a simple question:</p>
<p>If you had to repeat one of the trials, which would you choose?” Surprisingly, most participants chose the longer one.</p>
<p>Why would anyone choose more pain?</p>
<p>The researchers realised something profound: people don’t remember experiences by calculating total discomfort. Instead, the mind summarizes the experience using just two key moments — the most intense point (the peak) and the final moment (the end).</p>
<p>In both trials, the peak pain was the same: 14°C. But the longer trial ended slightly better, at 15°C. That small improvement at the end reshaped how the entire episode was remembered. The participants’ “experiencing self” suffered more during the longer trial. But their “remembering self” preferred it because it ended on a less painful note.</p>
<p>From this, the researchers introduced what became known as the Peak–End Rule: we judge experiences largely by their most intense moment and how they finish, not by how long they last.</p>
<p>Since people largely judge an experience by how it ends, developers should focus on designing satisfying confirmation screens and smooth exit interactions. You should concentrate less on making every single moment perfect and instead prioritise optimising the peak and final moments.</p>
<p>A negative ending can overshadow an otherwise good experience, so carefully avoid frustrating final steps such as unexpected fees or confusing confirmations.</p>
<p>Emotional intensity strongly shapes memory, which is why many apps incorporate celebration animations, rewards, or success messages at key moments to leave a lasting positive impression.</p>
<h3 id="heading-design-takeaway-from-peak-end-rule">Design takeaway from Peak End Rule</h3>
<p><strong>People Judge Experiences by the Peak and the Ending — Not the Total Duration:</strong><br>Users don’t remember every moment. They remember the most intense point and how the experience ends. A slightly better ending can completely reshape how the entire interaction is remembered.</p>
<p><strong>Prioritise Strong, Positive Endings in Your UX Flows:</strong><br>A smooth final step, a clear confirmation, or a satisfying success screen leaves a disproportionately strong impression. A bad ending can overshadow an otherwise great experience.</p>
<p><strong>Design for Emotional Peaks at Key Moments:</strong><br>Celebration animations, rewards, checkmarks, and success messages create memorable emotional spikes. These peaks anchor the experience in the user’s memory.</p>
<p><strong>Don’t Try to Perfect Every Moment — Perfect the Right Moments:</strong><br>Optimise the peak and the end of the journey. These two moments define how users recall the entire interaction.</p>
<p><strong>Avoid Negative Surprises at the Finish Line:</strong><br>Unexpected fees, confusing confirmations, or friction at the last step can ruin the memory of the whole process. Protect the ending carefully.</p>
<p><strong>To Sum Up:</strong> We judge experiences largely by their most intense moment and how they finish, not by how long they last.</p>
<h2 id="heading-110-postels-law"><strong>11.0 Postel’s Law</strong>:</h2>
<p>Jon Postel’s famous principle – “Be conservative in what you send, be liberal in what you accept” –&nbsp; is a philosophy of kindness in software design. At its core, the principle argues that systems should be generous with what they accept from users, yet disciplined and predictable in what they output.</p>
<p>When developers follow this approach, users feel supported and understood. When they don’t, users feel punished for being human.</p>
<p>A user’s input is rarely perfect. People type quickly, make mistakes, follow their own habits, or rely on formats familiar to them. A robust system embraces this reality. It accepts messy, human input and quietly transforms it into clean, standardized data.</p>
<p>Real people don't think in strict formats. They write dates the way they learned in school, type phone numbers the way they say them aloud, and enter names and addresses in whatever structure feels natural to them.</p>
<p>A rigid system will reject anything that doesn’t match its narrow expectations, but a robust system, by contrast, adapts to the user.</p>
<p>Consider dates. A brittle interface might demand MM/DD/YYYY and reject everything else. A more humane system accepts a wide range of formats — “1 May 2024,” “2024‑05‑01,” “05/01/24,” or “May 1st, 2024” — and quietly converts them into a standard internal representation. This is where the complex handling described by Tesla's Law comes into play (Shifting complexity to the system, rather than the user).</p>
<p>Phone numbers follow the same pattern. People might enter (555) 123 4567, 555‑123‑4567, 5551234567, or +1 555 123 4567. A fragile system throws errors. A robust one parses all of them using libraries like libphonenumber and moves on.</p>
<p>Addresses are equally varied. “221B Baker St,” “221‑B Baker Street,” and “221 Baker St., Apt B” all refer to the same place. A forgiving system normalizes these instead of rejecting them.</p>
<p>Even names can be surprisingly complex. Hyphens, apostrophes, multiple words, and titles are all part of real human identity. Rejecting “O’Connor,” “Jean‑Luc,” or “Dr. Sarah Lee” is not just technically incorrect — it's disrespectful to the user.</p>
<p>Search bars offer another clear example. A strict search bar demands perfect spelling and exact phrasing. A robust one handles typos (“restuarant”), partial words (“resta”), synonyms (“food places”), and natural language (“where can I eat nearby”). It meets the user where they are instead of forcing them to think like a machine.</p>
<p>Currency should be normalized to a clear format such as GBP 5.00, no matter whether the user typed “£5,” “5 pounds,” or “5 GBP.”</p>
<p>Even file uploads benefit from standardization: whether the user uploads .jpeg, .jpg, .JPG, or .JPEG, the system should store everything as .jpg.</p>
<p>Error messages follow the same principle. Vague feedback like “Invalid password” leaves users confused and frustrated.</p>
<p>A clear, conservative message — “Incorrect password. Please try again.” — respects the user’s time. And instead of hiding password requirements, the system should state them upfront: minimum eight characters, at least one uppercase letter, at least one number.</p>
<p>Predictability reduces friction.</p>
<p>Because users inevitably make mistakes or enter data in unexpected ways, developers should design input fields that are tolerant rather than brittle. This means accepting flexible formats, offering autocorrect or intelligent parsing, and using forgiving validation rules that interpret the user’s intent instead of rejecting their effort.</p>
<p>Clear instructions, tooltips, and visible requirements should appear before submission so users understand what the system expects without trial and error.</p>
<p>When errors do occur, the interface should handle them gently—never crashing, and never forcing the user to start over.</p>
<p>Even simple variations, such as phone numbers typed with spaces, dashes, or parentheses, should be accepted and normalized behind the scenes.</p>
<p>By embracing flexibility on the input side and clarity on the output side, developers create systems that feel humane, resilient, and respectful of the way real people actually behave.</p>
<h3 id="heading-design-takeaway-from-postels-law">Design Takeaway from Postel's Law</h3>
<p><strong>Accept Messy Human Input, Output Clean Structured Data:</strong><br>Users type dates, names, phone numbers, and addresses in unpredictable ways. A humane system accepts this variability and quietly normalises it into a consistent internal format.</p>
<p>Rigid interfaces punish users for being human. Robust interfaces interpret intent — handling typos, partial matches, synonyms, and natural language without complaint.</p>
<p>Also accept variations in spacing, punctuation, casing, and structure. Let users type naturally — the system should handle the complexity, not them.</p>
<p><strong>Be Flexible With Input, Be Strict With Output:</strong><br>This is the heart of Postel’s Law. Let users express information naturally, but ensure your system stores and displays it in a predictable, standardised way.</p>
<p><strong>Use Intelligent Parsing and Autocorrection to Reduce Errors:</strong><br>Libraries like libphonenumber, fuzzy search, and natural‑language parsers allow systems to accept a wide range of formats while still producing clean, reliable data.</p>
<p><strong>Normalise Everything Behind the Scenes:</strong><br>Dates, phone numbers, currency, file extensions, and addresses should all be standardised internally. This prevents messy data and reduces downstream cleanup work.</p>
<p><strong>Provide Clear, Predictable Feedback:</strong><br>Error messages should be specific and helpful. Requirements should be visible upfront. Users should never be surprised, confused, or forced to start over.</p>
<p><strong>Combine Postel’s Law With Tesler’s Law:</strong><br>Shift complexity to the system. Intelligent handling of messy input reduces cognitive load, improves usability, and ensures consistent, high‑quality data.</p>
<p><strong>To Sum Up:</strong> A rigid system will reject anything that doesn’t match its narrow expectations, but a robust system, by contrast, adapts to the user.</p>
<h2 id="heading-120-doherty-threshold"><strong>12.0 Doherty Threshold</strong>:</h2>
<p>The Doherty Threshold is a principle in human–computer interaction which proposes that systems should respond quickly enough to keep users actively engaged (Mod 2024).</p>
<p>When response times stay below a certain limit, users remain focused and productive. But once performance already meets this optimal responsiveness level, making the system even faster or adding extra capability doesn't significantly enhance satisfaction or efficiency.</p>
<p>The idea was introduced by Walter J. Doherty in 1976 in his paper “A Comparison of Programming Systems and Doherty Threshold.” His research showed that maintaining rapid system feedback fast enough to sustain continuous interaction has a stronger impact on productivity than simply increasing system power or features beyond that point.</p>
<p>Doherty proposes that this shouldn't be greater than 400ms Rule: If the system responds within this window, the user feels in total control. If the response takes longer, the user's attention begins to wander, and their "train of thought" is broken.</p>
<p>The challenge, of course, is that not every operation can realistically complete within 400ms. Some tasks require heavy computation, large network calls, or complex rendering. This is where the concept of perceived performance becomes essential.</p>
<p>Even when the system can't finish the work quickly, it can feel fast by responding instantly at the UI level. Developers can achieve this illusion of speed through a combination of thoughtful design patterns and disciplined engineering practices.</p>
<p>On the technical side, performance begins with reducing unnecessary work. Keeping the number of HTML elements low helps the browser render faster. Rendering only the visible portion of long lists prevents the Document Oject Model (DOM) from becoming bloated. Splitting scripts and deferring non‑critical code ensures that essential interactions load first.</p>
<p>Using CSS transforms and opacity changes avoids expensive layout recalculations. Lazy‑loading images, videos, and scripts ensures that the interface becomes interactive long before all assets are downloaded.</p>
<p>These optimizations don’t just improve raw speed — they create the foundation for interfaces that feel responsive.</p>
<h3 id="heading-design-takeaways-from-doherty-threshold">Design Takeaways from Doherty Threshold</h3>
<p><strong>Instant Feedback</strong>: When a user clicks a button, provide a visual change (like a button press animation or a spinner) immediately, even if the background task takes longer.</p>
<p><strong>Skeleton Screens</strong>: Use placeholder blocks that mimic the layout of the page while data loads. This makes the app feel like it is responding instantly.</p>
<p><strong>Progressive Loading</strong>: Load text and basic structures first, then "pop in" high-resolution images later.</p>
<p><strong>Optimistic UI</strong>: When a user hits "Save," don't wait for the server. Update the UI instantly (Doherty) and handle the "messy" data formatting on the backend (Postel).</p>
<p><strong>Live Inline Validation</strong>: Show a green checkmark or a helpful error message as the user types. This keeps them below the 400ms "thought-break" limit.</p>
<p><strong>Debouncing</strong>: In search bars, start showing results after a few keystrokes so the user feels the app is "predicting" their needs.</p>
<p><strong>To Sum Up:</strong> When response times stay below a certain limit, users remain focused and productive. But once performance already meets this optimal responsiveness level, making the system even faster or adding extra capability doesn't significantly enhance satisfaction or efficiency.</p>
<h2 id="heading-130-serial-position-effect-primacy-and-recency"><strong>13.0 Serial Position Effect (Primacy and Recency)</strong>:</h2>
<p>Murdock’s study investigated how the position of a word in a list affects recall, known as the serial position effect. He presented 103 psychology students with lists of 10 to 40 words, one at a time, at either 1 or 2 seconds per word (McLeod, 2025).</p>
<p>Participants were divided into six groups, each experiencing a different combination of list length and presentation rate, and were asked to recall as many words as possible in any order.</p>
<p>The results showed that participants were most likely to remember words at the beginning of the list (primacy effect) and at the end of the list (recency effect), while words in the middle were recalled less often. The recency effect persisted even in longer lists, and the middle section of the recall curve formed a flat asymptote.</p>
<p>Murdock explained this using the multi-store model of memory: early words were rehearsed and transferred to long-term memory, last words remained in short-term memory, and middle words were neither sufficiently rehearsed nor retained, leading to poorer recall.</p>
<p>The experiment demonstrated that memory performance varies systematically with the position of information in a sequence.</p>
<p>This is the reason why the most important information or actions should never be buried in the middle.</p>
<p>As a developer, you should put your most critical navigation links (like "Home" or "Dashboard") at the far left or the top of a list. In a pricing table, put the most popular or recommended plan on the Place "Final Actions" (like "Log Out," "Cart," or "Support") at the end of a menu or the far right of a navigation bar.</p>
<p>In a long onboarding flow, put the most exciting benefit of the app on the very last slide so the user enters the app feeling motivated.</p>
<p>Avoid placing highly important buttons in the middle of a row. If you have a row of 7 buttons, the user is statistically likely to overlook the 4th one.</p>
<h3 id="heading-design-takeaways-serial-position-effect">Design Takeaways <strong>Serial Position Effect</strong></h3>
<p><strong>Place Critical Items at the Beginning or End — Never the Middle:</strong><br>Users reliably remember the first and last items in any sequence (primacy and recency). Anything placed in the middle is statistically more likely to be forgotten or ignored. Also, actions such as “Log Out,” “Cart,” “Support,” or “Checkout” should sit at the far right or bottom — the natural recency position.</p>
<p><strong>Put Essential Navigation Links at the Far Left or Top:</strong><br>Links like “Home,” “Dashboard,” or “Overview” should appear at the start of a menu, where recall and recognition are strongest.</p>
<p><strong>To Sum Up:</strong> The results showed that participants were most likely to remember words at the beginning of the list (primacy effect) and at the end of the list (recency effect), while words in the middle were recalled less often.</p>
<h2 id="heading-140-occams-razor"><strong>14.0 Occam’s Razor</strong>:</h2>
<p>Although first articulated in the 14th century by the Franciscan friar William of Ockham, Occam’s Razor remains one of the most indispensable principles in a developer’s toolkit. In fact, skipping this law while discussing other theories and principles would be like skipping the glue that holds the entire framework together.</p>
<p>At its core, Occam’s Razor states that “among competing explanations, the simplest one is usually the best.”</p>
<p>For example, if two user interfaces achieve the same goal, the one with fewer visual elements is typically superior because it requires less processing power.</p>
<p>The fundamental takeaway for modern developers regarding Occam’s Razor is that complexity is a tax on the user’s cognitive resources.</p>
<p>In an era of information density, the developer's primary role is no longer to provide "more" features – rather, it's to curate the most direct path to a solution.</p>
<p>In practice, Occam’s Razor becomes a reminder to keep things as simple as possible. This “less is more” mindset shapes everything from navigation to forms.</p>
<p>A good rule for navigation is the Rule of Five: aim for three to five main menu items instead of a long, overwhelming list. This keeps choices clear and prevents users from freezing up when they see too many options.</p>
<p>The same idea applies to data entry. When you ask only for the information that truly matters, you respect the user’s time and reduce the chance of “form fatigue,” which is one of the biggest reasons people abandon sign‑ups or checkout flows.</p>
<p>Simplicity isn’t just elegant — it’s practical, humane, and far more effective.</p>
<h3 id="heading-design-takeaway-from-occams-razor">Design Takeaway from Occam's Razor</h3>
<p><strong>Choose the Simplest Effective Solution:</strong><br>When two designs achieve the same goal, the one with fewer elements is almost always better. Simplicity reduces cognitive load and speeds up user decision‑making.</p>
<p><strong>Simplicity Is Not Just Aesthetic — It’s Humane:</strong><br>Clear, minimal interfaces respect the user’s time, reduce friction, and make the product feel effortless. Simplicity is both a design strategy and an act of empathy.</p>
<p><strong>To Sum Up:</strong> Simplicity isn’t just elegant — it’s practical, humane, and far more effective.</p>
<h2 id="heading-150-parkinsons-law">15.0 Parkinson's Law</h2>
<p>Occam’s Razor teaches us to prefer the simplest solution that works. But why do we so often end up with complex systems in the first place? That tendency is explained by another principle: Parkinson’s Law.</p>
<p>Parkinson’s Law states that "work expands to fill the time available for its completion". In design, this means projects often become overly complex or take longer than necessary if given too much time, resulting in inefficient, over-designed, or cluttered interfaces.</p>
<p>In design, this manifests as Feature Creep. If you give yourself three months to build an app, you will spend three months adding "nice-to-have" animations, extra settings toggles, and niche edge cases that nobody asked for and in reality, what you have added isn’t that important.</p>
<p>You just succeeded in adding layers of complexity that might ends up violating some of the laws we spoke about. Occam’s Razor reminds us that the simplest solution is often the most effective.</p>
<p>By being aware of Parkinson’s Law and the tendency for work to expand, developers can manage their time intentionally and focus only on what truly matters.</p>
<h3 id="heading-design-takeaway-for-parkinsons-law">Design Takeaway for Parkinson's law</h3>
<p><strong>Set Clear Constraints to Keep Designs Focused:</strong><br>Intentional time limits and scope boundaries prevent over‑designing. Constraints force clarity, prioritisation, and simplicity.</p>
<p><strong>Build Only What Truly Matters for the User:</strong><br>Parkinson’s Law reminds you to resist the urge to fill time with unnecessary features. Focus on the core experience, not the edge cases nobody asked for.</p>
<p><strong>Use Occam’s Razor to Counterbalance Parkinson’s Law:</strong><br>As work expands, complexity grows. Occam’s Razor pulls you back to the simplest effective solution. Together, the two principles prevent bloated, over‑engineered products.</p>
<p><strong>To Sum Up:</strong> Work expands to fill the time available for its completion</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Human-centered design is deeply influenced by a set of psychological principles that explain how users perceive, process, and interact with digital systems.</p>
<p>Among these, Fitts’s Law establishes that the time required to acquire a target depends on its size and distance. In practice, this means that larger and closer elements are easier and faster to interact with.</p>
<p>To apply this in practice, developers should make primary call-to-action elements prominent, large, and easily reachable –&nbsp;especially in mobile interfaces where thumb accessibility is critical.</p>
<p>Closely related to decision-making is Hick’s Law, which states that the more choices a user is presented with, the longer it takes to make a decision. Excessive options can overwhelm users and lead to decision fatigue.</p>
<p>To address this, developers should simplify interfaces, minimise unnecessary options, and guide users through processes step-by-step rather than presenting everything at once.</p>
<p>Another important cognitive principle discussed is Miller’s Law, which suggests that the average person can hold approximately seven (plus or minus two) items in working memory at a time. This limitation highlights the need to present information in manageable chunks.</p>
<p>By breaking content into smaller groups and avoiding information overload, developers can improve comprehension and usability.</p>
<p>User expectations are strongly shaped by Jakob’s Law, which says that people spend most of their time on other websites and therefore expect similar patterns across digital products.</p>
<p>Instead of reinventing basic interactions, developers should follow familiar conventions such as placing the logo in the top‑left, the shopping cart in the top‑right, and keeping scrolling behaviour predictable.</p>
<p>But innovation is still possible where it truly adds value. As we discussed with the Aesthetic‑Usability Effect, users are far more tolerant of new or unusual design patterns when the interface is visually appealing and thoughtfully crafted.</p>
<p>The Gestalt Principles provided additional insight into how users visually organise information. The principle of proximity suggests that objects placed close together are perceived as related, so grouping related elements improves clarity. Similarity indicates that elements with consistent colours, shapes, or styles are seen as belonging together, reinforcing visual hierarchy and function. Closure explains that users can perceive incomplete shapes as complete, allowing for minimalistic designs where the brain fills in missing details. Continuity highlights that users naturally follow smooth visual paths, meaning layouts should guide the eye logically through alignment and structure.</p>
<p>We also looked at The Von Restorff Effect which emphasizes that elements which stand out are more likely to be remembered. By using contrast in colour, size, or design, important features such as buttons or alerts can capture user attention.</p>
<p>Managing complexity was addressed by Tesler’s Law, which asserts that every system has inherent complexity that cannot be eliminated but only managed.</p>
<p>Developers must therefore shift complexity away from the user by simplifying interfaces while handling intricate processes behind the scenes.</p>
<p>The Zeigarnik Effect reveals that people remember unfinished tasks better than completed ones, creating a sense of mental tension. This can be leveraged by incorporating progress indicators, checklists, and reminders that encourage users to complete tasks.</p>
<p>Similarly, the Peak-End Rule suggests that users judge an experience based on its most intense moment and its conclusion. Developers should create memorable highlights and ensure a smooth, satisfying ending to user journeys.</p>
<p>We also discussed the Goal-Gradient Effect, which explains that users become more motivated as they approach the completion of a task. By showing progress –such as indicating that a process is “80% complete” –&nbsp;and breaking tasks into stages, developers can encourage users to finish what they have started.</p>
<p>In terms of system interaction, Postel’s Law advises developers to be flexible in accepting user input while maintaining strict standards for output. This means allowing different input formats while ensuring consistent and reliable system responses.</p>
<p>Performance is equally important, as highlighted by the Doherty Threshold, which shows that productivity increases when system response times stay under 400 milliseconds. Fast systems keep users engaged and create a sense of ease.</p>
<p>This means that developers should focus on building interfaces that feel instant, even when real processing takes longer, by combining smart engineering practices with thoughtful design patterns that maintain the illusion of speed.</p>
<p>Memory and attention are further explained by the Serial Position Effect, where users tend to remember the first and last items in a sequence more than those in the middle. Developers should position key information or actions at the beginning or end of lists.</p>
<p>Simplicity is reinforced by Occam’s Razor, which argues that the simplest solution is often the most effective. Eliminating unnecessary features reduces friction and enhances usability, and we further discussed about Parkinson’s Law, which suggests that tasks expand to fill the time available, indicating the importance of setting constraints such as deadlines or timers to encourage timely action.</p>
<p>These principles collectively highlight the importance of simplicity, clarity, performance, and user psychology in design. By applying them thoughtfully, developers can create intuitive, efficient, and engaging user experiences that align with both human behaviour and user expectations.</p>
<h2 id="heading-references">References</h2>
<p>Budiu, R. (2022). <em>Fitts’s Law and Its Applications in UX</em>. [online] Nielsen Norman Group. Available at: <a href="https://www.nngroup.com/articles/fitts-law/">https://www.nngroup.com/articles/fitts-law/</a>.</p>
<p>Bustamante, N. (2023). <em>Gestalt Psychology? Definition, Principles, &amp; Examples - Simply Psychology</em>. [online] <a href="http://www.simplypsychology.org">www.simplypsychology.org</a>. Available at: <a href="https://www.simplypsychology.org/what-is-gestalt-psychology.html">https://www.simplypsychology.org/what-is-gestalt-psychology.html</a>.</p>
<p>Cherry, K. (2024). <em>The Zeigarnik Effect Is Why You Keep Thinking of Unfinished Work</em>. [online] Verywell Mind. Available at: <a href="https://www.verywellmind.com/zeigarnik-effect-memory-overview-4175150">https://www.verywellmind.com/zeigarnik-effect-memory-overview-4175150</a>.</p>
<p>DO, A.M., RUPERT, A.V. and WOLFORD, G. (2008). Evaluations of pleasurable experiences: The peak-end rule. <em>Psychonomic Bulletin &amp; Review</em>, 15(1), pp.96–98. doi:<a href="https://doi.org/10.3758/pbr.15.1.96">https://doi.org/10.3758/pbr.15.1.96</a>.</p>
<p>GUPTA, S., GUPTA, S., MAHENDRA, A. and GUPTA, S. (2006). Inverse Halo Nevus. <em>Dermatologic Surgery</em>, 32(6), pp.871–872. doi:<a href="https://doi.org/10.1097/00042728-200606000-00025">https://doi.org/10.1097/00042728-200606000-00025</a>.</p>
<p>‌Hall, D. (2023). <em>Pilot Error, Chapanis and The Shape of Things to Come</em>. [online] UX Magazine. Available at: <a href="https://uxmag.com/articles/pilot-error-chapanis-and-the-shape-of-things-to-come">https://uxmag.com/articles/pilot-error-chapanis-and-the-shape-of-things-to-come</a>.</p>
<p>Hunt, R.R. (1995). The subtlety of distinctiveness: What von Restorff really did. <em>Psychonomic Bulletin &amp; Review</em>, 2(1), pp.105–112. doi:<a href="https://doi.org/10.3758/bf03214414">https://doi.org/10.3758/bf03214414</a>.</p>
<p>Kahneman, D., Fredrickson, B.L., Schreiber, C.A. and Redelmeier, D.A. (1993). When More Pain Is Preferred to Less: Adding a Better End. <em>Psychological Science</em>, 4(6), pp.401–405. doi:<a href="https://doi.org/10.1111/j.1467-9280.1993.tb00589.x">https://doi.org/10.1111/j.1467-9280.1993.tb00589.x</a>.</p>
<p>Mod, D. (2024). <em>Doherty Threshold: UX Law of Swift Interactions</em>. [online] Articles on everything UX: Research, Testing &amp; Design. Available at: <a href="https://blog.uxtweak.com/doherty-threshold/">https://blog.uxtweak.com/doherty-threshold/</a>.</p>
<p>Miller, G.A. (1956). The magical number seven, plus or minus two: Some limits on our capacity for processing information. <em>Psychological Review</em>, [online] 101(2), pp.343–352. doi:<a href="https://doi.org/10.1037/0033-295x.101.2.343">https://doi.org/10.1037/0033-295x.101.2.343</a>.</p>
<p>Proctor, R.W. and Schneider, D.W. (2018). Hick’s law for choice reaction time: A review. <em>Quarterly Journal of Experimental Psychology</em>, [online] 71(6), pp.1281–1299. doi:<a href="https://doi.org/10.1080/17470218.2017.1322622">https://doi.org/10.1080/17470218.2017.1322622</a>.</p>
<p>Yablonski, J. (2022). <em>Hick’s Law</em>. [online] Laws of UX. Available at: <a href="https://lawsofux.com/hicks-law/">https://lawsofux.com/hicks-law/</a>.</p>
<p>Yablonski, J. (2022). <em>Goal-Gradient Effect</em>. [online] Laws of UX. Available at: <a href="https://lawsofux.com/goal-gradient-effect/">https://lawsofux.com/goal-gradient-effect/</a>.</p>
<p>‌</p>
<p>‌</p>
<p>‌</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Navigate Microservices as a Frontend Engineer ]]>
                </title>
                <description>
                    <![CDATA[ Most frontend engineers don't choose microservices. They inherit them. One day you're fetching data from a single API, and the next you're stitching together responses from five services, each with it ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-navigate-microservices-as-a-frontend-engineer/</link>
                <guid isPermaLink="false">69f8de9b46610fd6060f5251</guid>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abisoye Alli-Balogun ]]>
                </dc:creator>
                <pubDate>Mon, 04 May 2026 17:59:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6a10811b-1150-490a-8f29-28797fd39861.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most frontend engineers don't choose microservices. They inherit them. One day you're fetching data from a single API, and the next you're stitching together responses from five services, each with its own contract, its own failure modes, and its own idea of what a "user" looks like.</p>
<p>The backend team talks about bounded contexts, eventual consistency, and service meshes. You're thinking about loading states, stale data, and why the checkout page breaks when the inventory service is slow.</p>
<p>This article is for frontend engineers working in microservice environments. You'll learn how to consume multiple service APIs without creating a tangled mess, how to handle partial failures gracefully in the UI, how to manage distributed state across services, and how to work effectively with backend teams on API contracts <strong>because half the battle is communication, not code</strong>.</p>
<p>The goal is not to turn you into a backend engineer, it's to give you the mental models and patterns that make frontend development in a microservice world less painful.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>To get the most out of this article, you should be familiar with:</p>
<ul>
<li><p>React or a similar component framework (the examples use React and TypeScript)</p>
</li>
<li><p>Basic understanding of REST APIs and HTTP</p>
</li>
<li><p>Experience fetching data in frontend applications (fetch, Axios, or React Query)</p>
</li>
<li><p>General awareness of what microservices are (you don't need to have built one)</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-the-frontends-microservice-problem">The Frontend's Microservice Problem</a></p>
</li>
<li><p><a href="#heading-pattern-1-the-backend-for-frontend-bff">Pattern 1: The Backend-for-Frontend (BFF)</a></p>
</li>
<li><p><a href="#heading-pattern-2-handling-partial-failures-in-the-ui">Pattern 2: Handling Partial Failures in the UI</a></p>
</li>
<li><p><a href="#heading-pattern-3-managing-distributed-state">Pattern 3: Managing Distributed State</a></p>
</li>
<li><p><a href="#heading-pattern-4-taming-multiple-api-contracts">Pattern 4: Taming Multiple API Contracts</a></p>
</li>
<li><p><a href="#heading-pattern-5-timeout-budgets-for-page-assembly">Pattern 5: Timeout Budgets for Page Assembly</a></p>
</li>
<li><p><a href="#heading-pattern-6-error-boundaries-per-service">Pattern 6: Error Boundaries Per Service</a></p>
</li>
<li><p><a href="#heading-working-with-backend-teams-on-contracts">Working With Backend Teams on Contracts</a></p>
</li>
<li><p><a href="#heading-when-to-push-back">When to Push Back</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-frontends-microservice-problem">The Frontend's Microservice Problem</h2>
<p>In a monolithic architecture, the frontend talks to one API. That API owns the database, handles the business logic, and returns exactly the shape of data the UI needs. Life is simple.</p>
<p>In a microservice architecture, that single API fractures into many:</p>
<pre><code class="language-text">Monolith:
  Browser → API → Database

Microservices:
  Browser → API Gateway → User Service
                        → Order Service
                        → Inventory Service
                        → Payment Service
                        → Notification Service
</code></pre>
<p>Each of those services is owned by a different team, deployed independently, and may use different data formats or conventions. As a frontend engineer, you now have several new problems:</p>
<ol>
<li><p><strong>Multiple contracts:</strong> Each service has its own API shape. A "product" in the inventory service has different fields than a "product" in the catalog service.</p>
</li>
<li><p><strong>Partial failures:</strong> The order service might respond in 50 ms while the recommendation service times out. Your UI needs to handle both.</p>
</li>
<li><p><strong>Data consistency:</strong> A user updates their address, but the order service still shows the old one because it hasn't synced yet.</p>
</li>
<li><p><strong>Increased latency:</strong> Assembling a single page might require three or four API calls instead of one.</p>
</li>
</ol>
<p>These aren't backend problems that happen to affect the frontend. They're fundamentally frontend problems that require frontend solutions.</p>
<h2 id="heading-pattern-1-the-backend-for-frontend-bff">Pattern 1: The Backend-for-Frontend (BFF)</h2>
<p>The most impactful pattern for frontend teams in a microservice world is the Backend-for-Frontend. A BFF is a thin API layer that sits between the browser and the microservices. It's owned by the frontend team and exists to serve the frontend's specific needs.</p>
<pre><code class="language-text">Without BFF:
  Browser → User Service    (call 1)
  Browser → Order Service   (call 2)
  Browser → Inventory Service (call 3)
  3 round trips, 3 contracts to manage

With BFF:
  Browser → BFF → User Service
                → Order Service
                → Inventory Service
  1 round trip, 1 contract to manage
</code></pre>
<p>The BFF aggregates calls, transforms responses into the shapes your components need, and handles cross-service concerns like authentication token forwarding.</p>
<pre><code class="language-typescript">// BFF endpoint: GET /api/order-summary/:orderId
// Aggregates data from three services into one frontend-friendly response
import express from "express";

const router = express.Router();

router.get("/api/order-summary/:orderId", async (req, res) =&gt; {
  const { orderId } = req.params;
  const token = req.headers.authorization;

  try {
    const [order, customer, shipment] = await Promise.allSettled([
      fetch(`\({ORDER_SERVICE}/orders/\){orderId}`, {
        headers: { Authorization: token },
      }).then((r) =&gt; r.json()),
      fetch(`\({USER_SERVICE}/users/\){req.userId}`, { // userId set by auth middleware
        headers: { Authorization: token },
      }).then((r) =&gt; r.json()),
      fetch(`\({SHIPPING_SERVICE}/shipments?orderId=\){orderId}`, {
        headers: { Authorization: token },
      }).then((r) =&gt; r.json()),
    ]);

    res.json({
      order: order.status === "fulfilled" ? order.value : null,
      customer: customer.status === "fulfilled" ? customer.value : null,
      shipment: shipment.status === "fulfilled" ? shipment.value : null,
      errors: [order, customer, shipment]
        .filter((r) =&gt; r.status === "rejected")
        .map((r) =&gt; r.reason.message),
    });
  } catch (error) {
    res.status(500).json({ error: "Failed to assemble order summary" });
  }
});
</code></pre>
<p>Notice the use of <code>Promise.allSettled</code> instead of <code>Promise.all</code>. This is critical in a microservice environment. <code>Promise.all</code> fails fast: if any one service is down, the entire request fails. <code>Promise.allSettled</code> lets you return partial data, which leads directly to the next pattern.</p>
<h3 id="heading-when-to-use-a-bff">When to Use a BFF</h3>
<p>A BFF is worth the investment when:</p>
<ul>
<li><p>Your frontend aggregates data from three or more services per page</p>
</li>
<li><p>Different clients (web, mobile, admin) need different data shapes from the same services</p>
</li>
<li><p>You want the frontend team to control response shapes without waiting on backend teams</p>
</li>
</ul>
<p>A BFF isn't necessary when:</p>
<ul>
<li><p>You have an API gateway that already handles aggregation (for example, Apollo Federation for GraphQL)</p>
</li>
<li><p>You only consume one or two services</p>
</li>
<li><p>Your backend teams already provide frontend-optimized endpoints</p>
</li>
</ul>
<h2 id="heading-pattern-2-handling-partial-failures-in-the-ui">Pattern 2: Handling Partial Failures in the UI</h2>
<p>In a monolith, a request either succeeds or fails. In a microservice world, it can partially succeed. The order data loads fine, but the recommendation service is down. The product details are available, but the review service is slow.</p>
<p>Your UI needs to handle this gracefully. The key principle: <strong>never let a non-critical service failure break a critical user flow.</strong></p>
<pre><code class="language-typescript">// Types for partial data loading
interface ServiceResult&lt;T&gt; {
  data: T | null;
  status: "loaded" | "error" | "loading";
  error?: string;
}

interface OrderPageData {
  order: ServiceResult&lt;Order&gt;;
  recommendations: ServiceResult&lt;Product[]&gt;;
  reviews: ServiceResult&lt;Review[]&gt;;
}
</code></pre>
<p>Build your components to render independently based on what data is available:</p>
<pre><code class="language-typescript">function OrderPage({ orderId }: { orderId: string }) {
  const { order, recommendations, reviews } = useOrderPageData(orderId);

  // Critical: order must load or the page makes no sense
  if (order.status === "loading") return &lt;OrderSkeleton /&gt;;
  if (order.status === "error") return &lt;ErrorPage message={order.error} /&gt;;

  return (
    &lt;div&gt;
      {/* Critical section: always rendered */}
      &lt;OrderDetails order={order.data} /&gt;

      {/* Non-critical: degrades gracefully */}
      &lt;section aria-label="Recommendations"&gt;
        {recommendations.status === "loaded" ? (
          &lt;RecommendationCarousel products={recommendations.data} /&gt;
        ) : recommendations.status === "error" ? (
          &lt;EmptyState message="Recommendations Unavailable" /&gt;
        ) : (
          &lt;CarouselSkeleton /&gt;
        )}
      &lt;/section&gt;

      {/* Non-critical: degrades gracefully */}
      &lt;section aria-label="Customer reviews"&gt;
        {reviews.status === "loaded" ? (
          &lt;ReviewList reviews={reviews.data} /&gt;
        ) : reviews.status === "error" ? (
          &lt;EmptyState message="Reviews unavailable right now" /&gt;
        ) : (
          &lt;ReviewSkeleton /&gt;
        )}
      &lt;/section&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-classifying-critical-vs-non-critical-data">Classifying Critical vs. Non-Critical Data</h3>
<p>Not all data on a page is equally important. Before building any page that pulls from multiple services, classify each data source:</p>
<table>
<thead>
<tr>
<th>Data Source</th>
<th>Critical?</th>
<th>Failure Strategy</th>
</tr>
</thead>
<tbody><tr>
<td>Order details</td>
<td>Yes</td>
<td>Show error page, block the entire view</td>
</tr>
<tr>
<td>Customer info</td>
<td>Yes</td>
<td>Show error page</td>
</tr>
<tr>
<td>Recommendations</td>
<td>No</td>
<td>Hide the section, show empty state</td>
</tr>
<tr>
<td>Reviews</td>
<td>No</td>
<td>Show "reviews unavailable" message</td>
</tr>
<tr>
<td>Recently viewed</td>
<td>No</td>
<td>Hide silently</td>
</tr>
</tbody></table>
<p>This classification should be a conscious decision made with your product team, not something you discover when a service goes down in production.</p>
<h2 id="heading-pattern-3-managing-distributed-state">Pattern 3: Managing Distributed State</h2>
<p>In a monolithic world, the server is the single source of truth. In a microservice world, truth is distributed. The user service knows the user's current address. The order service has a snapshot of the address at the time of the order. These might not match.</p>
<h3 id="heading-stale-data-and-cache-boundaries">Stale Data and Cache Boundaries</h3>
<p>When your frontend caches data from multiple services, you need to think about cache boundaries. Data from different services goes stale at different rates.</p>
<pre><code class="language-typescript">// Configure cache times based on how frequently the underlying data changes
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000, // Default: 30 seconds
    },
  },
});

// Product catalog: changes infrequently
function useProduct(productId: string) {
  return useQuery({
    queryKey: ["product", productId],
    queryFn: () =&gt; fetchProduct(productId),
    staleTime: 5 * 60_000, // 5 minutes: catalog updates are rare
  });
}

// Inventory levels: changes constantly
function useStockLevel(productId: string) {
  return useQuery({
    queryKey: ["stock", productId],
    queryFn: () =&gt; fetchStockLevel(productId),
    staleTime: 10_000, // 10 seconds: stock changes with every purchase
    refetchInterval: 30_000, // Poll every 30 seconds on active pages
  });
}

// User's own order: should reflect latest state
function useOrder(orderId: string) {
  return useQuery({
    queryKey: ["order", orderId],
    queryFn: () =&gt; fetchOrder(orderId),
    staleTime: 0, // Always refetch: user expects to see their latest action
  });
}
</code></pre>
<p>The mistake is treating all cached data the same. Product information from the catalog service can be cached for minutes. Stock levels from the inventory service need to be refreshed much more frequently. A user's own order data should always be fresh because they just performed an action and expect to see the result.</p>
<h3 id="heading-cross-service-invalidation">Cross-Service Invalidation</h3>
<p>The trickiest part of distributed state is knowing when to invalidate. When a user places an order, you need to:</p>
<ol>
<li><p>Invalidate the order list (order service)</p>
</li>
<li><p>Invalidate the stock level (inventory service)</p>
</li>
<li><p>Invalidate the user's loyalty points (user service)</p>
</li>
</ol>
<pre><code class="language-typescript">// After a successful order placement, invalidate across service boundaries
async function placeOrder(cart: Cart): Promise&lt;Order&gt; {
  const order = await api.post("/api/orders", { items: cart.items });

  // Invalidate data from multiple services that this action affected
  queryClient.invalidateQueries({ queryKey: ["orders"] });
  queryClient.invalidateQueries({ queryKey: ["stock"] });
  queryClient.invalidateQueries({ queryKey: ["loyalty-points"] });

  // Optimistically update the cart (owned by the frontend)
  queryClient.setQueryData(["cart"], { items: [] });

  return order;
}
</code></pre>
<p>This is manual and error-prone. Every time a new service cares about order events, you need to remember to add an invalidation here.</p>
<p>For more robust alternatives, you can use server-sent events or WebSocket connections to let the backend push invalidation signals to the frontend, or adopt a pub/sub pattern within your client-side state layer where cache keys subscribe to domain events.</p>
<p>These approaches are beyond this article's scope, but worth exploring once your invalidation table grows past a dozen entries.</p>
<p>In the meantime, documenting these cross-service dependencies in a table helps:</p>
<table>
<thead>
<tr>
<th>User Action</th>
<th>Services Affected</th>
<th>Cache Keys to Invalidate</th>
</tr>
</thead>
<tbody><tr>
<td>Place order</td>
<td>Order, Inventory, User</td>
<td><code>orders</code>, <code>stock</code>, <code>loyalty-points</code>, <code>cart</code></td>
</tr>
<tr>
<td>Update address</td>
<td>User, Shipping</td>
<td><code>user-profile</code>, <code>shipping-estimates</code></td>
</tr>
<tr>
<td>Write review</td>
<td>Reviews, Product</td>
<td><code>reviews</code>, <code>product</code> (rating changes)</td>
</tr>
</tbody></table>
<h2 id="heading-pattern-4-taming-multiple-api-contracts">Pattern 4: Taming Multiple API Contracts</h2>
<p>In a microservice world, each service defines its own API contract. The user service returns <code>firstName</code> and <code>lastName</code>. The order service returns <code>customerName</code> as a single string. The notification service expects <code>fullName</code>. Same concept, three different field names.</p>
<h3 id="heading-the-adapter-layer">The Adapter Layer</h3>
<p>Create an adapter layer that translates each service's response into a consistent domain model that your components use:</p>
<pre><code class="language-typescript">// Domain models: what the frontend actually works with
interface User {
  id: string;
  fullName: string;
  email: string;
  address: Address;
}

// Adapter for the User Service
function adaptUserServiceResponse(raw: UserServiceResponse): User {
  return {
    id: raw.userId,
    fullName: `\({raw.firstName} \){raw.lastName}`,
    email: raw.emailAddress,
    address: {
      line1: raw.address.street,
      city: raw.address.city,
      postcode: raw.address.zipCode,
      country: raw.address.countryCode,
    },
  };
}

// Adapter for the Order Service (which embeds a different user shape)
function adaptOrderCustomer(raw: OrderServiceCustomer): User {
  return {
    id: raw.customerId,
    fullName: raw.customerName,
    email: raw.email,
    address: {
      line1: raw.shippingAddress.addressLine1,
      city: raw.shippingAddress.city,
      postcode: raw.shippingAddress.postalCode,
      country: raw.shippingAddress.country,
    },
  };
}
</code></pre>
<p>Your components only work with the <code>User</code> type. They never see the raw service responses. When a service changes its API, you update one adapter, not every component that displays a user's name.</p>
<h3 id="heading-where-to-put-the-adapter-layer">Where to Put the Adapter Layer</h3>
<p>If you have a BFF, the adapters live there. The browser never sees the raw service response. If you're calling services directly from the frontend, place the adapters in your data-fetching layer, between the HTTP call and the cache:</p>
<pre><code class="language-typescript">// The adapter runs before data enters the cache
function useUser(userId: string) {
  return useQuery({
    queryKey: ["user", userId],
    queryFn: async () =&gt; {
      const raw = await fetch(`/api/users/${userId}`).then((r) =&gt; r.json());
      return adaptUserServiceResponse(raw);
    },
  });
}
</code></pre>
<h2 id="heading-pattern-5-timeout-budgets-for-page-assembly">Pattern 5: Timeout Budgets for Page Assembly</h2>
<p>When a page depends on multiple services, you need a timeout strategy. Without one, your page load time is determined by the slowest service, and in a microservice world, there's always a slow service.</p>
<p>A timeout budget allocates a maximum time for assembling all the data a page needs. If a non-critical service doesn't respond within its budget, you render without it.</p>
<p>In practice, this utility lives in a shared service layer (for example, <code>lib/api.ts</code>) rather than inline with each page's assembly logic. Here's the implementation:</p>
<pre><code class="language-typescript">// lib/api.ts: shared timeout utility
async function fetchWithTimeout&lt;T&gt;(
  url: string,
  options: RequestInit,
  timeoutMs: number
): Promise&lt;T | null&gt; {
  const controller = new AbortController();
  const timeout = setTimeout(() =&gt; controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    return response.json();
  } catch (error) {
    if (error instanceof DOMException &amp;&amp; error.name === "AbortError") {
      console.warn(`Request to \({url} timed out after \){timeoutMs}ms`);
    }
    return null;
  } finally {
    clearTimeout(timeout);
  }
}

// Page assembly with tiered timeouts
async function assembleProductPage(productId: string): Promise&lt;ProductPageData&gt; {
  // Critical data: longer timeout, page fails without it
  const product = await fetchWithTimeout&lt;Product&gt;(
    `/api/products/${productId}`,
    {},
    3000 // 3 second budget for critical data
  );

  if (!product) {
    throw new Error("Product not found");
  }

  // Non-critical data: shorter timeout, page renders without it
  const [reviews, recommendations, relatedProducts] = await Promise.all([
    fetchWithTimeout&lt;Review[]&gt;(
      `/api/reviews?productId=${productId}`,
      {},
      1500 // 1.5 second budget
    ),
    fetchWithTimeout&lt;Product[]&gt;(
      `/api/recommendations?productId=${productId}`,
      {},
      1000 // 1 second budget: nice to have
    ),
    fetchWithTimeout&lt;Product[]&gt;(
      `/api/products/${productId}/related`,
      {},
      1000
    ),
  ]);

  return {
    product,
    reviews: reviews ?? [],
    recommendations: recommendations ?? [],
    relatedProducts: relatedProducts ?? [],
  };
}
</code></pre>
<p>Notice the different budgets. Critical data (the product itself) gets 3 seconds. Non-critical data (reviews, recommendations) gets 1–1.5 seconds. If recommendations are slow, you show the product without them. The user doesn't wait for a service they may not even look at.</p>
<h2 id="heading-pattern-6-error-boundaries-per-service">Pattern 6: Error Boundaries Per Service</h2>
<p>React error boundaries are especially powerful in a microservice frontend. Instead of one error boundary at the page level, place boundaries around sections that map to different backend services.</p>
<p>If you haven't used error boundaries before, here's a minimal implementation. Error boundaries must be class components, React doesn't support them as function components yet (see the <a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary">React docs</a> for more detail):</p>
<pre><code class="language-typescript">class ErrorBoundary extends React.Component&lt;
  { fallback: React.ReactNode; children: React.ReactNode },
  { hasError: boolean } &gt; {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: React.ErrorInfo) {
    console.error("ErrorBoundary caught:", error, info);
  }

  render() {
    if (this.state.hasError) return this.props.fallback;
    return this.props.children;
  }
}
</code></pre>
<p>With that in place, scope your boundaries to individual service sections:</p>
<pre><code class="language-typescript">function ProductPage({ productId }: { productId: string }) {
  return (
    &lt;div&gt;
      {/* If the product service fails, show a full-page error */}
      &lt;ErrorBoundary fallback={&lt;ProductErrorPage /&gt;}&gt;
        &lt;Suspense fallback={&lt;ProductSkeleton /&gt;}&gt;
          &lt;ProductDetails productId={productId} /&gt;
        &lt;/Suspense&gt;
      &lt;/ErrorBoundary&gt;

      {/* If the review service fails, just hide reviews */}
      &lt;ErrorBoundary fallback={&lt;EmptyState message="Reviews unavailable" /&gt;}&gt;
        &lt;Suspense fallback={&lt;ReviewSkeleton /&gt;}&gt;
          &lt;ProductReviews productId={productId} /&gt;
        &lt;/Suspense&gt;
      &lt;/ErrorBoundary&gt;

      {/* If recommendations fail, hide silently */}
      &lt;ErrorBoundary fallback={null}&gt;
        &lt;Suspense fallback={&lt;CarouselSkeleton /&gt;}&gt;
          &lt;Recommendations productId={productId} /&gt;
        &lt;/Suspense&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Each boundary catches errors from its own data source independently. The review service crashing doesn't affect the product details. The recommendation service timing out doesn't show an error at all – the section simply doesn't render.</p>
<p>This maps directly to your critical/non-critical classification. Critical services get error boundaries with visible error UI. Non-critical services get boundaries that degrade silently or show a minimal empty state.</p>
<h2 id="heading-working-with-backend-teams-on-contracts">Working With Backend Teams on Contracts</h2>
<p>The technical patterns above solve symptoms. The root cause of most frontend pain in microservice environments is poor communication between frontend and backend teams about API contracts.</p>
<h3 id="heading-contract-conversations-to-have-early">Contract Conversations to Have Early</h3>
<h4 id="heading-1-what-fields-will-the-frontend-actually-use">1. What fields will the frontend actually use?</h4>
<p>Backend services often expose their entire data model. The frontend uses three fields. If the backend team knows which fields you depend on, they can maintain those fields more carefully and deprecate the ones nobody uses.</p>
<h4 id="heading-2-what-is-the-expected-latency-budget-for-this-endpoint">2. What is the expected latency budget for this endpoint?</h4>
<p>If the product page has a 2-second total budget and the recommendation service averages 1.8 seconds, you have a problem before you write any frontend code. Surface this early.</p>
<h4 id="heading-3-what-happens-when-this-service-is-degraded">3. What happens when this service is degraded?</h4>
<p>Ask each backend team: "If your service responds with 500 errors for an hour, what should the frontend show?" This question often reveals that nobody has thought about it, which is exactly why you need to ask.</p>
<h4 id="heading-4-how-will-you-communicate-breaking-changes">4. How will you communicate breaking changes?</h4>
<p>Agree on a process. Whether it is OpenAPI spec diffs in pull requests, a Slack channel for API changes, or versioned endpoints, pick something and hold each other to it.</p>
<h3 id="heading-api-contracts-as-shared-artifacts">API Contracts as Shared Artifacts</h3>
<p>Push for machine-readable contracts. OpenAPI specs, GraphQL schemas, or Protocol Buffer definitions serve as a shared source of truth between frontend and backend teams. They enable:</p>
<ul>
<li><p><strong>Automated type generation:</strong> Tools like <code>openapi-typescript</code> generate TypeScript types from OpenAPI specs. When the backend changes a field, your build fails immediately, not in production.</p>
</li>
<li><p><strong>Contract testing:</strong> Tools like <code>Pact</code> let you define the expected request/response pairs from the frontend's perspective. The backend runs these tests in their CI pipeline. If their changes break the frontend's expectations, the pipeline fails.</p>
</li>
<li><p><strong>Mock servers:</strong> Generated mocks from the spec let you build the frontend before the backend is ready. When the real service ships, your code already works.</p>
</li>
</ul>
<pre><code class="language-typescript">// Generated types from OpenAPI spec, always in sync with the backend
import type { components } from "./generated/inventory-api";

type Product = components["schemas"]["Product"];
type StockLevel = components["schemas"]["StockLevel"];

// If the backend renames "available" to "inStock",
// this code fails at compile time, not in production
function formatStockMessage(stock: StockLevel): string {
  if (stock.available &gt; 10) return "In Stock";
  if (stock.available &gt; 0) return `Only ${stock.available} left`;
  return "Out of Stock";
}
</code></pre>
<h3 id="heading-testing-against-multiple-services">Testing Against Multiple Services</h3>
<p>Contract testing catches backend-side breaking changes, but you also need to test your frontend's behavior when services respond in unexpected ways. <a href="https://mswjs.io/">Mock Service Worker (MSW)</a> lets you spin up per-service mock handlers in your test environment:</p>
<pre><code class="language-typescript">import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";

// Mock each service independently
const server = setupServer(
  http.get("/api/products/:id", () =&gt;
    HttpResponse.json({ productId: "abc-123", name: "Widget", price: 49.99 })
  ),
  http.get("/api/reviews", () =&gt;
    HttpResponse.json([{ rating: 5, body: "Great product" }])
  )
);

// Test: what happens when the review service is down?
test("renders product page when reviews service fails", async () =&gt; {
  server.use(
    http.get("/api/reviews", () =&gt; HttpResponse.error())
  );

  render(&lt;ProductPage productId="abc-123" /&gt;);

  expect(await screen.findByText("Widget")).toBeInTheDocument();
  expect(await screen.findByText("Reviews unavailable")).toBeInTheDocument();
});
</code></pre>
<p>This lets you simulate the partial failure scenarios from Pattern 2 in your test suite. Test your adapter layer (Pattern 4) with unit tests against raw service response fixtures, and use MSW for integration tests that verify the full page assembles correctly when individual services are slow, down, or return unexpected shapes.</p>
<h2 id="heading-when-to-push-back">When to Push Back</h2>
<p>Not every microservice problem has a frontend solution. Sometimes the right answer is to push back on the architecture.</p>
<p><strong>Push back when the frontend is making more than 5 API calls for a single page.</strong> This is a signal that either the services are too granular or there is a missing aggregation layer. The fix is a BFF or a composite API, not more <code>Promise.all</code> calls in the browser.</p>
<p><strong>Push back when two services return conflicting data about the same entity.</strong> If the user service says the user's name is "Jane" and the order service says it is "Janet," this is a data consistency problem that the frontend can't solve. It needs to be fixed at the source, either through event-driven syncing between services or by establishing one service as the authoritative source for that field.</p>
<p><strong>Push back when backend teams make breaking changes without notice.</strong> If your production app breaks because a service renamed a field in a minor version bump, that's a process failure. Advocate for versioned APIs, deprecation notices, and contract testing.</p>
<p>You're not just a consumer of APIs. You're a stakeholder in how those APIs are designed. The earlier you participate in API design conversations, the fewer surprises you deal with in production.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The patterns in this article give you a structured starting point, but the underlying principle is consistent across all of them:</p>
<p>Key takeaways:</p>
<ol>
<li><p><strong>Own the aggregation layer:</strong> A BFF gives the frontend team control over response shapes and lets you handle partial failures at the server level instead of the browser.</p>
</li>
<li><p><strong>Classify every data source as critical or non-critical:</strong> This single decision determines your error handling, timeout budgets, and loading strategies for every section of every page.</p>
</li>
<li><p><strong>Normalize at the boundary:</strong> Adapter layers between raw service responses and your components protect you from upstream API changes and give you a consistent domain model.</p>
</li>
<li><p><strong>Invest in contracts:</strong> Machine-readable API contracts, generated types, and contract testing catch breaking changes at build time instead of in production.</p>
</li>
<li><p><strong>Push back when needed:</strong> Not every microservice problem has a frontend solution. If the architecture creates an unreasonable burden on the UI layer, say so early.</p>
</li>
</ol>
<p>Microservices are a backend architecture decision, but their consequences are felt most acutely in the frontend. The patterns in this article won't make that complexity disappear, but they will give you a structured way to manage it.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Split PDF Files in the Browser Using JavaScript (Step-by-Step) ]]>
                </title>
                <description>
                    <![CDATA[ Working with PDFs is part of everyday development. Sometimes you don’t need the entire document. You just need a few pages — maybe a specific section, a report summary, or selected invoice pages. Most ]]>
                </description>
                <link>https://www.freecodecamp.org/news/split-pdf-files-using-javascript/</link>
                <guid isPermaLink="false">69ef7279330a1ad7f7ec9a85</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ pdf ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Bhavin Sheth ]]>
                </dc:creator>
                <pubDate>Mon, 27 Apr 2026 14:28:09 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/ff8ed4f5-a0f1-44cd-8703-a6dcd95e6b0f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Working with PDFs is part of everyday development.</p>
<p>Sometimes you don’t need the entire document. You just need a few pages — maybe a specific section, a report summary, or selected invoice pages.</p>
<p>Most tools require uploading files or installing software. But modern browsers are powerful enough to handle this locally.</p>
<p>In this tutorial, you’ll learn how to build a browser-based PDF splitter using JavaScript, where everything runs directly in the user’s browser.</p>
<p>By the end, you’ll understand how to extract specific pages from a PDF, create a new document from those pages, and download the result instantly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/1a0d3489-5034-4aed-add4-68bada614c8e.png" alt="split pdf files,extract pages" style="display:block;margin:0 auto" width="1456" height="267" loading="lazy">

<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-how-pdf-splitting-works-in-the-browser">How PDF Splitting Works in the Browser</a></p>
</li>
<li><p><a href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a href="#heading-what-library-are-we-using">What Library Are We Using?</a></p>
</li>
<li><p><a href="#heading-creating-the-upload-interface">Creating the Upload Interface</a></p>
</li>
<li><p><a href="#heading-reading-the-pdf-file">Reading the PDF File</a></p>
</li>
<li><p><a href="#heading-selecting-pages-to-extract-or-split">Selecting Pages to Extract or Split</a></p>
</li>
<li><p><a href="#heading-splitting-the-pdf-using-javascript">Splitting the PDF Using JavaScript</a></p>
</li>
<li><p><a href="#generating-and-downloading-the-pdf">Generating and Downloading the PDF</a></p>
</li>
<li><p><a href="#demo-how-the-pdf-split-tool-works">Demo: How the PDF Split Tool Works</a></p>
</li>
<li><p><a href="#important-notes-from-real-world-use">Important Notes from Real-World Use</a></p>
</li>
<li><p><a href="#common-mistakes-to-avoid">Common Mistakes to Avoid</a></p>
</li>
<li><p><a href="#conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-pdf-splitting-works-in-the-browser">How PDF Splitting Works in the Browser</h2>
<p>Splitting a PDF means taking a single document and extracting specific pages into a new file.</p>
<p>Traditionally, this kind of processing is handled on a server. But with modern JavaScript libraries like pdf-lib, we can do everything directly in the browser.</p>
<p>The process is straightforward. A user uploads a PDF file, the browser reads it, and we can display a preview of its pages to help users understand what they’re working with. Based on the selected split mode or page input, we then extract only the required pages and copy them into a new PDF document.</p>
<p>All of this happens locally in the browser, which makes the process faster and ensures that user files never leave their device.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>We’ll keep this project simple.</p>
<p>You only need:</p>
<ul>
<li><p>an HTML file</p>
</li>
<li><p>JavaScript</p>
</li>
<li><p>a PDF processing library</p>
</li>
</ul>
<p>No backend or server is required.</p>
<h2 id="heading-what-library-are-we-using">What Library Are We Using?</h2>
<p>We’ll use <strong>pdf-lib</strong>, a lightweight JavaScript library for working with PDFs.</p>
<p>Add it using a CDN:</p>
<pre><code class="language-html">&lt;script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"&gt;&lt;/script&gt;
</code></pre>
<p>This library allows us to:</p>
<ul>
<li><p>load PDFs</p>
</li>
<li><p>copy pages</p>
</li>
<li><p>create new documents</p>
</li>
</ul>
<h2 id="heading-creating-the-upload-interface">Creating the Upload Interface</h2>
<p>Start with a simple file input:</p>
<pre><code class="language-html">&lt;input type="file" id="upload" accept="application/pdf"&gt;
&lt;input type="text" id="pages" placeholder="Enter pages (e.g. 1-3,5)"&gt;
&lt;button onclick="splitPDF()"&gt;Split PDF&lt;/button&gt;

&lt;a id="download" style="display:none;"&gt;Download Split PDF&lt;/a&gt;
</code></pre>
<p>This interface allows users to upload a PDF file, specify which pages they want to extract, and trigger the splitting process with a single click. Once the process is complete, the download link becomes visible so they can save the new PDF.</p>
<h2 id="heading-reading-the-pdf-file">Reading the PDF File</h2>
<p>Now let’s read the uploaded file:</p>
<pre><code class="language-javascript">const fileInput = document.getElementById("upload");

if (!fileInput.files.length) {
  alert("Please upload a PDF file");
  return;
}

const file = fileInput.files[0];
const arrayBuffer = await file.arrayBuffer();
</code></pre>
<p>This converts the file into a format the library can use.</p>
<h2 id="heading-selecting-pages-to-extract-or-split">Selecting Pages to Extract or Split</h2>
<p>Users can control how the PDF is split in multiple ways.</p>
<p>They can manually enter page ranges like <code>1-3,5</code>, which allows precise selection of pages. For example, entering <code>1-3</code> extracts pages 1 to 3, while <code>5</code> selects only page 5.</p>
<p>In addition to manual input, the tool also provides predefined options such as splitting all pages, extracting only even or odd pages, or splitting the document into fixed-size ranges. These options make it easier for users who don’t want to type page ranges manually.</p>
<p>To support manual input, we use a simple parser that converts the user’s input into valid page indexes:</p>
<pre><code class="language-javascript">function parsePages(input, totalPages) {
  const pages = [];

  input.split(',').forEach(part =&gt; {
    if (part.includes('-')) {
      const [start, end] = part.split('-').map(Number);
      for (let i = start; i &lt;= end; i++) {
        if (i &lt;= totalPages) pages.push(i - 1);
      }
    } else {
      const num = parseInt(part);
      if (num &lt;= totalPages) pages.push(num - 1);
    }
  });

  return pages;
}
</code></pre>
<p>This approach gives flexibility, allowing both simple and advanced ways to select pages depending on the user’s needs.</p>
<h2 id="heading-splitting-the-pdf-using-javascript">Splitting the PDF Using JavaScript</h2>
<p>Now comes the main logic:</p>
<pre><code class="language-javascript">async function splitPDF() {
  const fileInput = document.getElementById("upload");
  const pageInput = document.getElementById("pages").value;

  if (!fileInput.files.length || !pageInput.trim()) {
    alert("Please upload a PDF and enter page numbers");
    return;
  }

  const file = fileInput.files[0];
  const arrayBuffer = await file.arrayBuffer();

  const { PDFDocument } = PDFLib;

  const originalPdf = await PDFDocument.load(arrayBuffer);
  const totalPages = originalPdf.getPageCount();

  const selectedPages = parsePages(pageInput, totalPages);

  const newPdf = await PDFDocument.create();

  const copiedPages = await newPdf.copyPages(originalPdf, selectedPages);

  copiedPages.forEach(page =&gt; newPdf.addPage(page));

  const pdfBytes = await newPdf.save();

  const blob = new Blob([pdfBytes], { type: "application/pdf" });

  const link = document.getElementById("download");
  link.href = URL.createObjectURL(blob);
  link.download = "split.pdf";
  link.style.display = "inline";
  link.innerText = "Download Split PDF";
}
</code></pre>
<p>This:</p>
<ul>
<li><p>loads the original file</p>
</li>
<li><p>extracts selected pages</p>
</li>
<li><p>creates a new PDF</p>
</li>
<li><p>prepares it for download</p>
</li>
</ul>
<h2 id="heading-generating-and-downloading-the-pdf">Generating and Downloading the PDF</h2>
<p>Once the PDF is created:</p>
<pre><code class="language-javascript">link.href = URL.createObjectURL(blob);
link.download = "split.pdf";
</code></pre>
<p>The browser handles the download instantly — no server needed.</p>
<h2 id="heading-demo-how-the-pdf-split-tool-works">Demo: How the PDF Split Tool Works</h2>
<p>Here’s how the full flow looks in practice using the tool:</p>
<h3 id="heading-step-1-upload-your-pdf">Step 1: Upload Your PDF</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/59361d9e-1f64-428e-8098-49b0976bd3ae.png" alt="PDF splitter tool interface showing drag and drop upload area with select PDF button" style="display:block;margin:0 auto" width="1724" height="757" loading="lazy">

<p>Start by dragging and dropping your PDF file into the upload area, or click the button to select a file from your device. Once uploaded, the tool instantly processes the document and prepares it for splitting.</p>
<h3 id="heading-step-2-preview-pages">Step 2: Preview Pages</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/b6cae555-6b9d-4ea9-afe5-877803574ceb.png" alt="PDF splitter preview showing multiple pages as thumbnails for visual selection" style="display:block;margin:0 auto" width="1408" height="430" loading="lazy">

<p>After uploading, all pages of the PDF are displayed as thumbnails. This gives you a clear visual overview of the document so you can decide how you want to split it.</p>
<h3 id="heading-step-3-choose-split-mode-and-options">Step 3: Choose Split Mode and Options</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/e7ee8c00-8247-41ce-9459-4de7f5e8b1ef.png" alt="PDF splitter settings with options for page range, all pages, fixed range, and odd or even page splitting" style="display:block;margin:0 auto" width="1753" height="523" loading="lazy">

<p>Next, choose how you want to split the PDF. You can select options like splitting by page range, extracting all pages, splitting odd or even pages, or dividing the document into fixed-size sections. This flexibility makes it easy to handle different use cases without manually selecting every page.</p>
<h3 id="heading-step-4-split-the-pdf">Step 4: Split the PDF</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/185b297f-f16e-4269-a885-6dd48903db23.png" alt="PDF splitter interface showing split PDF button and start over option" style="display:block;margin:0 auto" width="1644" height="195" loading="lazy">

<p>Once your settings are ready, click the split button. The browser processes the file locally and generates the new PDFs based on your selected mode.</p>
<h3 id="heading-step-5-download-the-results">Step 5: Download the Results</h3>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/c3fdf474-9052-4661-9c82-c34515a4423c.png" alt="PDF splitter result showing multiple generated files with download buttons and download all option" style="display:block;margin:0 auto" width="967" height="636" loading="lazy">

<p>After processing, the split files are displayed with download options. You can download individual files or download all of them at once. Everything happens instantly in the browser without uploading your files anywhere.</p>
<h2 id="heading-important-notes-from-real-world-use">Important Notes from Real-World Use</h2>
<p>When working with PDF splitting, input validation is important.</p>
<p>Users may enter invalid ranges or page numbers that don’t exist. Always validate and limit input to available pages.</p>
<p>Handling large PDFs can also affect performance. Instead of processing everything at once, you can handle operations step by step to keep the browser responsive.</p>
<p>Another key consideration is privacy. Since all processing happens in the browser, files never leave the user’s device. This makes the tool safer for sensitive documents.</p>
<p>In real-world applications, it’s important to clearly communicate that files are not uploaded or stored anywhere.</p>
<h2 id="heading-common-mistakes-to-avoid">Common Mistakes to Avoid</h2>
<p>One common issue is not validating user input. If users enter incorrect page ranges, the tool may fail or produce unexpected results.</p>
<p>Another mistake is forgetting that page indexes start at zero internally. If you don’t adjust for this, you may extract the wrong pages.</p>
<p>Also, skipping edge cases like empty input or large files can make the tool unreliable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you built a browser-based PDF splitter using JavaScript.</p>
<p>You learned how to read PDF files, extract specific pages, and generate a new document entirely in the browser.</p>
<p>This approach removes the need for a backend and keeps everything fast and private.</p>
<p>If you’d like to see a complete working version of this idea, you can try it here: <a href="https://allinonetools.net/split-pdf/">Split PDF</a></p>
<p>Once you understand this pattern, you can extend it further to build more advanced PDF tools like merging, compression, or editing.</p>
<p>And that’s where things start getting really interesting.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Barcode Generator Using JavaScript (Step-by-Step) ]]>
                </title>
                <description>
                    <![CDATA[ If you’ve ever worked on something like an inventory system, billing dashboard, or even a small internal tool, chances are you’ve needed to generate barcodes at some point. Most developers either rely ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-barcode-generator/</link>
                <guid isPermaLink="false">69cfdf9b21e7d63506a6957e</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Bhavin Sheth ]]>
                </dc:creator>
                <pubDate>Fri, 03 Apr 2026 15:41:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/684644dd-4128-415f-94ec-cf45b2a80cad.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’ve ever worked on something like an inventory system, billing dashboard, or even a small internal tool, chances are you’ve needed to generate barcodes at some point.</p>
<p>Most developers either rely on external tools or assume this requires backend processing. That’s usually where things get slower, more complex, and harder to maintain.</p>
<p>But modern browsers have quietly become powerful enough to handle this entirely on their own.</p>
<p>In this tutorial, you’ll build a barcode generator that runs completely in the browser. It won’t upload data anywhere, and it won’t require any server logic. Everything happens instantly on the client side.</p>
<p>Along the way, you’ll also learn how barcode formats work, how to validate inputs properly, and how to create a real-time preview experience that feels responsive and practical.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-how-barcode-generation-works">How Barcode Generation Works</a></p>
</li>
<li><p><a href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a href="#heading-what-library-are-we-using">What Library Are We Using?</a></p>
</li>
<li><p><a href="#heading-creating-the-html-structure">Creating the HTML Structure</a></p>
</li>
<li><p><a href="#heading-adding-javascript-for-barcode-generation">Adding JavaScript for Barcode Generation</a></p>
</li>
<li><p><a href="#heading-how-the-barcode-is-generated">How the Barcode Is Generated</a></p>
</li>
<li><p><a href="#heading-types-of-barcodes-you-can-generate">Types of Barcodes You Can Generate</a></p>
</li>
<li><p><a href="#heading-adding-real-time-preview">Adding Real-Time Preview</a></p>
</li>
<li><p><a href="#heading-how-to-validate-input-properly">How to Validate Input Properly</a></p>
</li>
<li><p><a href="#heading-how-to-download-the-barcode">How to Download the Barcode</a></p>
</li>
<li><p><a href="#heading-important-notes-from-real-world-use">Important Notes from Real-World Use</a></p>
</li>
<li><p><a href="#heading-common-mistakes-to-avoid">Common Mistakes to Avoid</a></p>
</li>
<li><p><a href="#heading-demo-how-the-barcode-generator-works">Demo: How the Barcode Generator Works</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-how-barcode-generation-works">How Barcode Generation Works</h2>
<p>A barcode is simply a visual encoding of data. Instead of displaying text directly, it represents that data using a pattern of lines and spaces.</p>
<p>Different barcode formats use different encoding rules. Some support only numbers, while others allow full text input. When you generate a barcode in the browser, you’re essentially converting user input into a structured visual pattern.</p>
<p>The key idea here is that we don’t draw these lines manually. A library takes care of encoding the data and rendering it as an SVG element, which the browser can display instantly.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>We’ll keep this project intentionally simple so the focus stays on understanding how it works.</p>
<p>All you need is a basic HTML file, a small JavaScript file, and a barcode library. There’s no backend involved, and nothing gets stored or uploaded.</p>
<p>This makes the tool fast, private, and easy to integrate into other projects.</p>
<h2 id="heading-what-library-are-we-using">What Library Are We Using?</h2>
<p>In this project, we use the <strong>JsBarcode</strong> library.</p>
<p>It’s a lightweight JavaScript library that can generate barcodes directly inside the browser using SVG. It supports multiple formats and works without any external dependencies.</p>
<p>You can include it using a CDN:</p>
<pre><code class="language-html">&lt;script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"&gt;&lt;/script&gt;
</code></pre>
<h2 id="heading-creating-the-html-structure">Creating the HTML Structure</h2>
<p>The interface is simple but practical. It includes an input field where users can enter data, a dropdown to choose the barcode format, and a preview area where the barcode is rendered.</p>
<pre><code class="language-html">&lt;input type="text" id="text" placeholder="Enter text or number"&gt;

&lt;select id="format"&gt;
  &lt;option value="CODE128"&gt;Code128&lt;/option&gt;
  &lt;option value="EAN13"&gt;EAN13&lt;/option&gt;
&lt;/select&gt;

&lt;button onclick="generateBarcode()"&gt;Generate&lt;/button&gt;

&lt;svg id="barcode"&gt;&lt;/svg&gt;
</code></pre>
<p>This structure is enough to handle input, display output, and connect everything through JavaScript.</p>
<h2 id="heading-adding-javascript-for-barcode-generation">Adding JavaScript for Barcode Generation</h2>
<p>Now we'll connect the user input to barcode generation.</p>
<pre><code class="language-javascript">function generateBarcode() {
  const text = document.getElementById("text").value;
  const format = document.getElementById("format").value;

  if (!text) {
    alert("Please enter a value");
    return;
  }

  JsBarcode("#barcode", text, {
    format: format,
    width: 2,
    height: 100,
    displayValue: true
  });
}
</code></pre>
<p>This function reads the input, checks if it exists, and then generates the barcode using the selected format.</p>
<h2 id="heading-how-the-barcode-is-generated">How the Barcode Is Generated</h2>
<p>When you call the JsBarcode function, the library handles everything behind the scenes.</p>
<p>It encodes the input into a barcode standard, converts that into a pattern of lines, and renders it as an SVG element. Because SVG is vector-based, the barcode remains sharp even when resized.</p>
<p>All of this happens instantly in the browser, which is why the experience feels fast.</p>
<h2 id="heading-types-of-barcodes-you-can-generate">Types of Barcodes You Can Generate</h2>
<p>Different barcode formats are used in different industries, and understanding them helps you build more practical tools.</p>
<ol>
<li><p><strong>Code128</strong> is the most flexible format. It supports letters, numbers, and special characters, which makes it ideal for general-purpose use.</p>
</li>
<li><p><strong>EAN-13</strong> is commonly used in retail products. It works only with 13-digit numbers, so it requires strict validation.</p>
</li>
<li><p><strong>UPC</strong> is similar to EAN and is widely used in billing systems, especially in the US. It also expects numeric input with a fixed length.</p>
</li>
<li><p><strong>Code39</strong> is simpler and supports uppercase letters and numbers, but it’s less compact compared to Code128.</p>
</li>
<li><p><strong>ITF-14</strong> is mostly used in logistics and packaging. It’s designed for numeric data and is common in shipping environments.</p>
</li>
</ol>
<p>In most cases, starting with Code128 is the safest option unless you have a specific requirement.</p>
<h2 id="heading-adding-real-time-preview">Adding Real-Time Preview</h2>
<p>One of the biggest improvements you can make to a tool like this is real-time feedback.</p>
<p>Instead of requiring users to click a button every time, you can generate the barcode as they type.</p>
<pre><code class="language-javascript">document.getElementById("text").addEventListener("input", generateBarcode);
document.getElementById("format").addEventListener("change", generateBarcode);
</code></pre>
<p>This small change makes the tool feel much more responsive.</p>
<p>As soon as the user types or changes the format, the barcode updates automatically. This is the same kind of interaction you see in polished production tools.</p>
<h2 id="heading-how-to-validate-input-properly">How to Validate Input Properly</h2>
<p>Validation is where many simple tools break.</p>
<p>Since different barcode formats have different rules, if you don’t validate input correctly, the barcode may fail silently or produce incorrect output.</p>
<p>Here’s a simple example:</p>
<pre><code class="language-javascript">function isValidInput(text, format) {
  if (format === "EAN13") {
    return /^\d{13}$/.test(text);
  }

  if (format === "UPC") {
    return /^\d{12}$/.test(text);
  }

  return text.length &gt; 0;
}
</code></pre>
<p>Then use it inside your generator:</p>
<pre><code class="language-javascript">if (!isValidInput(text, format)) {
  alert("Invalid input for selected format");
  return;
}
</code></pre>
<p>This ensures users get immediate feedback instead of confusion.</p>
<h2 id="heading-how-to-download-the-barcode">How to Download the Barcode</h2>
<p>Once the barcode is generated, you can allow users to download it.</p>
<pre><code class="language-javascript">function downloadBarcode() {
  const svg = document.getElementById("barcode");
  const serializer = new XMLSerializer();
  const source = serializer.serializeToString(svg);

  const blob = new Blob([source], { type: "image/svg+xml" });
  const url = URL.createObjectURL(blob);

  const link = document.createElement("a");
  link.href = url;
  link.download = "barcode.svg";
  link.click();
}
</code></pre>
<p>This converts the SVG into a file that can be downloaded directly from the browser.</p>
<h2 id="heading-important-notes-from-real-world-use">Important Notes from Real-World Use</h2>
<p>When building tools like this in production, small details matter.</p>
<p>Large input values can sometimes affect readability, so it’s important to test how dense the barcode becomes. Choosing the right format also makes a difference depending on whether you need flexibility or strict standards.</p>
<p>Another important detail is rendering quality. Using SVG instead of raster formats ensures that the barcode remains sharp even when printed.</p>
<h2 id="heading-common-mistakes-to-avoid">Common Mistakes to Avoid</h2>
<p>One common issue is skipping validation. This leads to broken or unreadable barcodes, especially with strict formats like EAN or UPC.</p>
<p>Another mistake is relying too much on button-based interactions. Real-time updates create a much better user experience.</p>
<p>Finally, developers sometimes forget to include the library correctly, which leads to silent failures. Always verify that your CDN is loaded.</p>
<h2 id="heading-demo-how-the-barcode-generator-works">Demo: How the Barcode Generator Works</h2>
<p>To better understand how everything comes together, here’s a quick walkthrough of how the tool works in the browser.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/3a90ba9d-0b4d-4cc6-8060-de238571e67a.png" alt="Barcode generator interface showing barcode type selection options like Code128, EAN-13, UPC and input field for entering barcode data" style="display:block;margin:0 auto" width="944" height="326" loading="lazy">

<h3 id="heading-step-1-select-a-barcode-type">Step 1: Select a Barcode Type</h3>
<p>Start by choosing the barcode format. In most cases, Code128 is a good default since it supports both text and numbers.</p>
<h3 id="heading-step-2-enter-your-data">Step 2: Enter Your Data</h3>
<p>Next, enter the value you want to encode. This could be a product ID, URL, or any text depending on the selected format.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/5e7d1655-92f8-4b38-ac92-52b8fd700aab.png" alt="Barcode customization panel with options to change bar color, background color, width, height, and display settings" style="display:block;margin:0 auto" width="916" height="327" loading="lazy">

<h3 id="heading-step-3-customize-the-design">Step 3: Customize the Design</h3>
<p>You can adjust things like bar width, height, and colors. These settings help control how the barcode looks and how readable it is in different use cases.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/8b46e3fb-bd4d-41c9-84dc-c91e36656680.png" alt="Generated barcode preview displayed in the browser based on user input" style="display:block;margin:0 auto" width="433" height="217" loading="lazy">

<h3 id="heading-step-4-generate-and-preview">Step 4: Generate and Preview</h3>
<p>As you type or change settings, the barcode updates instantly. This real-time preview makes it easier to experiment and see results immediately.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979d22f93bc273cc33971b1/24b16194-d38d-4e2c-be1a-cbcef646ef7f.png" alt="Download options for generated barcode in PNG, JPG, and SVG formats" style="display:block;margin:0 auto" width="440" height="145" loading="lazy">

<h3 id="heading-step-5-download-the-barcode">Step 5: Download the Barcode</h3>
<p>Once you're satisfied with the result, you can download the barcode in formats like PNG, JPG, or SVG.</p>
<p>This entire process happens in the browser, without uploading any data to a server.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you built a browser-based barcode generator using JavaScript.</p>
<p>More importantly, you learned how to think about building tools that run entirely on the client side. This approach reduces complexity, improves performance, and gives users a faster experience.</p>
<p>Once you understand this pattern, you can apply it to many other tools like QR generators, image converters, and file processors.</p>
<p>And that’s where things start to get interesting.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Design a Type-Safe, Lazy, and Secure Plugin Architecture in React ]]>
                </title>
                <description>
                    <![CDATA[ Modern web applications increasingly need to evolve faster than a single team can maintain a monolithic codebase. Product teams often want to add features independently, experiment with new capabiliti ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-design-a-type-safe-lazy-and-secure-plugin-architecture-in-react/</link>
                <guid isPermaLink="false">69caa5bc9fffa747404dbd51</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ plugin ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jessica Patel ]]>
                </dc:creator>
                <pubDate>Mon, 30 Mar 2026 15:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/5cdc3448-3bf8-456e-b316-33c6bcb98690.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Modern web applications increasingly need to evolve faster than a single team can maintain a monolithic codebase. Product teams often want to add features independently, experiment with new capabilities, or deploy domain-specific functionality without modifying the core application every time. This is where a plugin architecture becomes valuable.</p>
<p>A plugin architecture allows an application to load external modules that extend its functionality at runtime. Instead of embedding every feature directly in the core application, the system exposes a controlled interface (the host API) that plugins use to integrate with the platform. These plugins can register UI components, contribute functionality, or interact with application services while remaining isolated from the core codebase.</p>
<p>This architectural pattern is widely used across software ecosystems. Platforms such as IDEs, content management systems, and browser extensions rely on plugins to allow third-party developers to extend their functionality without compromising stability.</p>
<p>In a web application context, a similar approach allows large frontend systems to evolve modularly, enabling multiple teams to ship features independently.</p>
<p>In this tutorial, you'll learn how to design a type-safe, lazy-loaded, and secure plugin architecture in React — complete with lifecycle management, independent bundling, hot-loading, and real TypeScript examples.</p>
<p>By the end, you'll have everything you need to transform your React application into a modular platform capable of hosting independent extensions without sacrificing maintainability, performance, or security.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-a-common-pain-point-scaling-frontend-platforms">A Common Pain Point: Scaling Frontend Platforms</a></p>
</li>
<li><p><a href="#heading-what-this-article-will-cover">What This Article Will Cover</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-why-a-plugin-architecture">Why a Plugin Architecture?</a></p>
</li>
<li><p><a href="#heading-core-concepts-of-a-react-plugin-architecture">Core Concepts of a React Plugin Architecture</a></p>
</li>
<li><p><a href="#heading-high-level-architecture-of-a-react-plugin-system">High-Level Architecture of a React Plugin System</a></p>
</li>
<li><p><a href="#heading-real-typescript-example-a-chat-plugin">Real TypeScript Example: A Chat Plugin</a></p>
<ul>
<li><p><a href="#heading-chat-plugin-implementation">Chat Plugin Implementation</a></p>
</li>
<li><p><a href="#heading-host-application-usage">Host Application Usage</a></p>
</li>
<li><p><a href="#heading-1-how-to-define-the-host-api">1. How to Define the Host API</a></p>
</li>
<li><p><a href="#heading-2-how-to-define-the-plugin-lifecycle">2. How to Define the Plugin Lifecycle</a></p>
</li>
<li><p><a href="#heading-3-how-to-bundle-plugins-separately">3. How to Bundle Plugins Separately</a></p>
</li>
<li><p><a href="#heading-4-how-to-lazy-load-plugins">4. How to Lazy-Load Plugins</a></p>
</li>
<li><p><a href="#heading-5-security-permission-model">5. Security &amp; Permission Model</a></p>
</li>
<li><p><a href="#heading-6-plugin-hot-loading">6. Plugin Hot-loading</a></p>
</li>
<li><p><a href="#heading-7-ci-deployment-considerations">7. CI &amp; Deployment Considerations</a></p>
</li>
<li><p><a href="#heading-putting-it-all-together">Putting It All Together</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a href="#heading-when-not-to-use-a-plugin-architecture">When NOT to Use a Plugin Architecture</a></p>
<ul>
<li><p><a href="#heading-small-or-single-team-applications">Small or Single-Team Applications</a></p>
</li>
<li><p><a href="#heading-tightly-coupled-features">Tightly Coupled Features</a></p>
</li>
<li><p><a href="#heading-performance-critical-systems">Performance-Critical Systems</a></p>
</li>
<li><p><a href="#heading-limited-security-controls">Limited Security Controls</a></p>
</li>
<li><p><a href="#heading-early-stage-products">Early-Stage Products</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-future-enhancements">Future Enhancements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-a-common-pain-point-scaling-frontend-platforms">A Common Pain Point: Scaling Frontend Platforms</h2>
<p>Consider a large internal admin dashboard used by multiple teams across an organization. Each team wants to add its own functionality, like analytics dashboards, workflow management tools, user administration panels, and domain-specific reporting modules.</p>
<p>If all these features are implemented directly in the main React application, several problems quickly emerge. Merge conflicts in the core repository become frequent, unrelated features grow tightly coupled, and release cycles slow down because every change requires redeploying the entire application. Worse, adding new features carries a constant risk of breaking existing functionality.</p>
<p>A plugin architecture solves this problem by allowing each feature to be developed as an independent plugin. The host application provides a stable platform and a controlled API, while teams can ship their own plugins without modifying the core system.</p>
<h2 id="heading-what-this-article-will-cover">What This Article Will Cover</h2>
<p>This guide walks you through how to design a type-safe, lazy-loaded, and secure plugin architecture in React using TypeScript. You'll learn how to design a host API that plugins can safely interact with, how to define a plugin lifecycle for initialization, mounting, updates, and cleanup, and how to bundle plugins independently so they can be developed and deployed separately.</p>
<p>You'll also learn how to lazy-load plugins at runtime to improve performance, how to implement a security model that prevents plugins from accessing sensitive application state, and how to enable hot-loading during development while enforcing safety through CI/CD pipelines.</p>
<p>By the end of this article, you'll understand how to build a flexible plugin system that allows your React application to grow into a modular platform capable of hosting independent extensions without sacrificing maintainability, performance, or security.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before following along with this guide, you should be familiar with several core technologies and concepts used throughout the examples.</p>
<p><strong>React Fundamentals</strong><br>A basic understanding of React components, hooks, and JSX is required. The examples assume familiarity with functional components, <code>useState</code>, and <code>useEffect</code>.</p>
<p><strong>TypeScript Basics</strong><br>Since the plugin architecture relies heavily on type contracts between the host application and plugins, you should understand TypeScript interfaces, generics, and module exports.</p>
<p><strong>Modern JavaScript Modules</strong><br>Knowledge of ES modules (<code>import</code> / <code>export</code>) and dynamic imports will help when working with lazy-loaded plugins.</p>
<p><strong>React Tooling (Vite or Webpack)</strong><br>The examples reference modern frontend build tools such as Vite. Familiarity with how bundlers compile React applications and manage dependencies will help when configuring plugin builds.</p>
<p><strong>Basic Web Security Concepts</strong><br>Some sections discuss sandboxing and restricted APIs. A general understanding of browser security concepts such as iframes, same-origin policies, and API boundaries is helpful but not strictly required.</p>
<h2 id="heading-why-a-plugin-architecture">Why a Plugin Architecture?</h2>
<p>Imagine you're building an internal admin platform where multiple teams need to ship independent features as plugins without risking the core application. A plugin architecture allows each team to contribute functionality safely, while the host maintains type safety, security, and performance.</p>
<p>This guide targets React/TypeScript engineers who want to design a plugin system capable of hosting third-party extensions without compromising maintainability.</p>
<p>The benefits of this approach are significant. Extensibility means developers or third parties can add features without touching core code. Isolation allows plugins to be sandboxed so they can't affect unrelated parts of the application. Lazy loading ensures only the features a user actually needs are fetched, keeping the application fast. TypeScript enforces a strict contract between plugins and the host, catching errors at compile time rather than at runtime. Finally, controlled APIs and permission boundaries prevent malicious or poorly written plugins from interfering with the rest of the system.</p>
<p>A well-architected plugin system balances all of these qualities – flexibility, safety, and maintainability – without forcing unnecessary trade-offs between them.</p>
<h2 id="heading-core-concepts-of-a-react-plugin-architecture">Core Concepts of a React Plugin Architecture</h2>
<p>Before diving into code, it helps to understand the key building blocks that make up a React plugin system.</p>
<p>At a high level, a plugin architecture in React revolves around five concerns.</p>
<ol>
<li><p>The <strong>Host API</strong> is the interface the core application exposes to plugins.</p>
</li>
<li><p>The <strong>Plugin Lifecycle</strong> defines methods for initialization, mounting, updating, and cleanup.</p>
</li>
<li><p><strong>Bundling</strong> means compiling each plugin separately to avoid coupling it to the host.</p>
</li>
<li><p>The <strong>Security Model</strong> covers permissions and sandboxing to prevent misuse.</p>
</li>
<li><p>Finally, <strong>Hot-loading and CI</strong> streamline the development and deployment experience.</p>
</li>
</ol>
<p>We'll explore each of these concepts in detail in the sections that follow. First, let's look at how they fit together visually.</p>
<h2 id="heading-high-level-architecture-of-a-react-plugin-system">High-Level Architecture of a React Plugin System</h2>
<p>The following diagram illustrates how the host application interacts with independently bundled plugins. The host exposes a controlled API, loads plugins dynamically, and manages their lifecycle while maintaining security boundaries.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6979762ba2442d262dacf388/5b7ef89a-62ae-4ea0-97f5-34a2423650d3.png" alt="React Plugin Architecture" style="display:block;margin:0 auto" width="680" height="545" loading="lazy">

<p>The core application serves as the runtime environment for all plugins, housing the plugin loader, lifecycle manager, and the host API.</p>
<p>The plugin loader dynamically imports plugin bundles at runtime using <code>import()</code>, while the host API ensures plugins interact with the application through a controlled interface rather than accessing internal state directly.</p>
<p>Each plugin is compiled as a separate bundle and registers itself with the host during initialization. A dedicated security layer enforces all of these boundaries, ensuring plugins cannot directly manipulate internal state or sensitive resources.</p>
<p>Together, these pieces ensure that plugins remain independent, lazy-loadable, and secure, while the host application retains full control over lifecycle management and platform stability.</p>
<h2 id="heading-real-typescript-example-a-chat-plugin">Real TypeScript Example: A Chat Plugin</h2>
<p>Now that you have a mental model of the architecture, let's look at a minimal working example before diving into each concept individually. This example demonstrates how a plugin registers itself with the host application and exposes a UI component through the host API.</p>
<p>The following plugin implements a simple chat feature that registers a React component with the host platform.</p>
<h3 id="heading-chat-plugin-implementation">Chat Plugin Implementation</h3>
<pre><code class="language-typescript">// plugins/chat-plugin/src/plugin.ts

import { Plugin, HostAPI } from '../../src/plugins';

const ChatPlugin: Plugin = {
  name: 'ChatPlugin',
  version: '1.0.0',
  init(host: HostAPI) {
    host.registerComponent('Chat', () =&gt; (
      &lt;div&gt;Welcome to the Chat Plugin!&lt;/div&gt;
    ));
    host.log('ChatPlugin initialized');
  },
};

export default ChatPlugin;
</code></pre>
<h3 id="heading-host-application-usage">Host Application Usage</h3>
<p>The host application loads the plugin and renders the component it registered.</p>
<pre><code class="language-typescript">const Chat = hostAPI.getComponent('Chat');

return (
  &lt;div&gt;
    {Chat ? &lt;Chat /&gt; : 'Loading Chat Plugin...'}
  &lt;/div&gt;
);
</code></pre>
<p>In this example, the plugin doesn't directly modify the host application. Instead, it interacts through the Host API, registering a component that the host can render dynamically. The sections below break down exactly how each piece of this system is built.</p>
<h3 id="heading-1-how-to-define-the-host-api">1. How to Define the Host API</h3>
<p>The <strong>host API</strong> is the contract between the core app and its plugins. It defines what functionality plugins can access. Before plugins can do anything useful, the host must expose a controlled interface, establishing the contract between the core application and its extensions.</p>
<p><strong>Example: TypeScript Host API</strong></p>
<pre><code class="language-typescript">// src/plugins/host.ts

export interface HostAPI {
  // Using ComponentType instead of FC&lt;any&gt; reinforces type-safety while allowing class/function components
  registerComponent: (name: string, component: React.ComponentType&lt;any&gt;) =&gt; void;
  getComponent: (name: string) =&gt; React.ComponentType&lt;any&gt; | undefined;
  log: (message: string) =&gt; void;
}

// Note: We still use `any` for props here for extensibility; plugins can define stricter props locally if needed.

export const hostAPI: HostAPI = { 
    registerComponent(name, component) { 
        console.log(Registered component: ${name}); 
        componentRegistry[name] = component; 
    }, 
    getComponent(name) { 
        return componentRegistry[name]; 
    }, 
    log(message) { 
        console.log([PLUGIN LOG]: ${message}); 
    }, 
};

const componentRegistry: Record&lt;string, React.ComponentType&lt;any&gt;&gt; = {};
</code></pre>
<p>This API allows plugins to register UI components and log messages, without giving them unrestricted access to the application state.</p>
<h3 id="heading-2-how-to-define-the-plugin-lifecycle">2. How to Define the Plugin Lifecycle</h3>
<p>A plugin lifecycle ensures consistent behavior across all extensions. Once the host API exists, plugins need a structured way to initialize, render, and clean up resources.</p>
<p><strong>Lifecycle Interface</strong></p>
<pre><code class="language-typescript">// src/plugins/plugin.ts

import { HostAPI } from './host';

export interface Plugin {
  name: string;
  version: string;
  init: (host: HostAPI) =&gt; void;
  mount?: () =&gt; void;
  update?: () =&gt; void;
  unmount?: () =&gt; void;
}

// Typically, the host calls mount/update/unmount based on route changes, feature flags, or user interactions.
</code></pre>
<p>The <code>init</code> method is called when the plugin is first loaded and receives the host API as its argument. <code>mount</code> is called when the plugin's UI is displayed, while <code>update</code> is an optional hook triggered when props or state change.</p>
<p>When a plugin is removed, <code>unmount</code> is called to clean up any resources the plugin was holding, preventing memory leaks and side effects in the host application.</p>
<h3 id="heading-3-how-to-bundle-plugins-separately">3. How to Bundle Plugins Separately</h3>
<p>Each plugin should be packaged as an independent module so that it can be developed, versioned, and deployed without tightly coupling it to the host application.</p>
<p>Modern build tools such as Vite or Webpack make it possible to compile plugins into standalone bundles that the host can load dynamically at runtime.</p>
<p><strong>Example Vite Configuration for a Plugin</strong></p>
<pre><code class="language-typescript">// vite.config.ts

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    lib: {
      entry: 'src/plugin.ts',
      name: 'MyPlugin',
      fileName: 'my-plugin',
      formats: ['es'],
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
    },
  },
});
</code></pre>
<p>The <code>external</code> option ensures the plugin uses the host's React, preventing duplicate React versions in memory.</p>
<h3 id="heading-4-how-to-lazy-load-plugins">4. How to Lazy-Load Plugins</h3>
<p>Even when plugins are bundled independently, loading all of them during application startup would significantly increase initial load time. Instead, plugins should be loaded on demand using dynamic imports so that functionality is only fetched when the user actually needs it.</p>
<pre><code class="language-typescript">// src/plugins/loader.ts

export async function loadPlugin(url: string): Promise { 

    // Using /* @vite-ignore */ because the URL is dynamic and cannot be         statically analyzed by Vite.
    // Tradeoff: plugin cannot be pre-bundled; ensure URLs are trusted to avoid security risks.

    const module = await import(/ @vite-ignore */ url); 
    return module.default as Plugin; 
}
</code></pre>
<p><strong>Usage in React:</strong></p>
<pre><code class="language-typescript">const [plugin, setPlugin] = React.useState&lt;Plugin | null&gt;(null);

React.useEffect(() =&gt; {
  loadPlugin('/plugins/my-plugin.js').then((p) =&gt; {
    p.init(hostAPI);
    setPlugin(p);
  });
}, []);
</code></pre>
<p>This pattern allows applications to scale without preloading all plugins, improving initial load time.</p>
<h3 id="heading-5-security-amp-permission-model">5. Security &amp; Permission Model</h3>
<p>Because plugins run code that originates outside the core application, security boundaries are essential. Even though plugins interact through the host API, the platform must still restrict what capabilities they can access in order to prevent misuse or accidental interference with application state.</p>
<p><strong>Example: Restricted API</strong></p>
<pre><code class="language-typescript">export interface SecureHostAPI {
  log: (message: string) =&gt; void;
  registerComponent: (name: string, component: React.ComponentType&lt;any&gt;) =&gt; void;
  fetchData?: (endpoint: string) =&gt; Promise&lt;any&gt;; // Only if allowed
}
</code></pre>
<p>You can enhance security further using <strong>iframe sandboxing</strong> or <strong>Web Workers</strong> for heavier isolation.</p>
<pre><code class="language-typescript">// Example of a sandboxed iframe plugin

&lt;iframe
  src="/plugins/my-plugin.html"
  sandbox="allow-scripts"
  style={{ width: '100%', height: '400px', border: 'none' }}
/&gt;

// Advanced isolation notes:
// - You can define different SecureHostAPI shapes for internal vs. third-party plugins,
//   exposing more capabilities to trusted plugins while restricting untrusted ones.
// - For stronger isolation, use message passing (postMessage) with iframes or Web Workers
//   so plugins cannot access the DOM or host state directly.
</code></pre>
<p>This approach prevents DOM and network access outside the API.</p>
<h3 id="heading-6-plugin-hot-loading">6. Plugin Hot-loading</h3>
<p>Hot-loading is essential for developer productivity. Tools like Vite's HMR let you see plugin updates immediately, speeding up iteration and reducing friction.</p>
<p><strong>React Example with HMR:</strong></p>
<pre><code class="language-typescript">if (import.meta.hot) {
  import.meta.hot.accept('/plugins/my-plugin.js', (newModule) =&gt; {
    const updatedPlugin = newModule.default as Plugin;
    updatedPlugin.init(hostAPI);
    setPlugin(updatedPlugin);
  });
}
</code></pre>
<p>With hot-loading, developers can update plugins without restarting the host app.</p>
<h3 id="heading-7-ci-amp-deployment-considerations">7. CI &amp; Deployment Considerations</h3>
<p>To deploy safely, plugins must be verified and tested. CI/CD pipelines enforce type safety, bundling, and security checks automatically. For a production-grade plugin system, continuous integration pipelines should:</p>
<ol>
<li><p>Lint and type-check each plugin using TypeScript.</p>
</li>
<li><p>Run automated tests to ensure plugin compliance.</p>
</li>
<li><p>Bundle plugins independently with versioned outputs.</p>
</li>
<li><p>Deploy plugins to a secure CDN or internal repository.</p>
</li>
<li><p>Verify signatures or hashes to prevent tampering.</p>
</li>
</ol>
<p><strong>GitHub Actions Example for Plugin CI:</strong></p>
<pre><code class="language-plaintext">name: Build Plugin

on:
  push:
    paths:
      - 'plugins/**'

jobs:
  build:
    runs-on: ubuntu-latest
    steps: 
      - uses: actions/checkout@v3 
      - uses: actions/setup-node@v3 
      with:
        node-version: 20
      - run: npm install 
      - run: npm run build --workspace plugins/my-plugin 
      - run: npm run test --workspace plugins/my-plugin
      # Optional: sign plugin artifacts or generate a checksum to verify integrity before loading in the host
</code></pre>
<p>This ensures every plugin is type-safe, tested, and ready for deployment.</p>
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>At this point, you have walked through each architectural layer independently. Here's how all the pieces map to a real project structure:</p>
<pre><code class="language-plaintext">src/
├── plugins/ 
│ ├── host.ts ← Host API definition 
│ ├── plugin.ts ← Plugin lifecycle interface 
│ └── loader.ts ← Dynamic plugin loader 
plugins/ 
└── chat-plugin/ 
    └── src/ 
        └── plugin.ts ← Chat plugin implementation
</code></pre>
<p>Each file has a single, clear responsibility. <code>host.ts</code> owns the contract, <code>plugin.ts</code> owns the lifecycle shape, <code>loader.ts</code> handles runtime importing, and the plugin itself lives entirely outside the core <code>src/</code> tree – deployable and versioned independently.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<p>At this point, you have a host API, a well-defined plugin lifecycle, isolated bundles, lazy-loading, and a security model. These foundations ensure plugins are robust, type-safe, and maintainable — ready to be extended with versioning, testing, and CI/CD pipelines.</p>
<ol>
<li><p><strong>Type safety:</strong> Always define TypeScript interfaces for host APIs and plugin contracts.</p>
</li>
<li><p><strong>Lazy loading:</strong> Only load plugins when required.</p>
</li>
<li><p><strong>Security:</strong> Expose a minimal API and avoid giving plugins unrestricted access.</p>
</li>
<li><p><strong>Isolated state:</strong> Keep plugin state isolated to prevent accidental interference.</p>
</li>
<li><p><strong>Versioning:</strong> Maintain plugin versions to ensure compatibility with the host.</p>
</li>
<li><p><strong>Testing:</strong> Unit-test plugins against host API mocks.</p>
</li>
<li><p><strong>CI/CD:</strong> Automate linting, testing, and bundling for plugins.</p>
</li>
</ol>
<h2 id="heading-when-not-to-use-a-plugin-architecture">When NOT to Use a Plugin Architecture</h2>
<p>In some cases, introducing a plugin system can add unnecessary complexity without delivering meaningful benefits.</p>
<h3 id="heading-small-or-single-team-applications">Small or Single-Team Applications</h3>
<p>If a project is maintained by a small team and the feature set is relatively stable, a plugin architecture may be excessive. A simpler modular structure within the main codebase is usually easier to maintain and reason about.</p>
<h3 id="heading-tightly-coupled-features">Tightly Coupled Features</h3>
<p>Plugin systems work best when features can operate independently. If new functionality requires deep access to application state or tightly integrated workflows, forcing it into a plugin model may introduce unnecessary abstractions and complexity rather than solving a real problem.</p>
<h3 id="heading-performance-critical-systems">Performance-Critical Systems</h3>
<p>Although lazy-loading can mitigate performance issues, plugin architectures still introduce additional runtime complexity. Applications with strict performance constraints may benefit from a more tightly optimized architecture rather than dynamic plugin loading.</p>
<h3 id="heading-limited-security-controls">Limited Security Controls</h3>
<p>Allowing external code to run inside an application always introduces security risks. If the platform can't enforce strong API boundaries, sandboxing, or validation of plugins, it may be safer to avoid a plugin architecture altogether.</p>
<h3 id="heading-early-stage-products">Early-Stage Products</h3>
<p>In early product development, requirements often change rapidly. Designing a plugin system too early can slow development because engineers must maintain abstraction layers before the product's core architecture has stabilized. It's usually better to wait until the platform's boundaries are well understood before introducing this level of extensibility.</p>
<h2 id="heading-future-enhancements">Future Enhancements</h2>
<p>As the platform matures, there are several directions worth exploring.</p>
<p>Dynamic permissions would allow plugins to explicitly request capabilities, with the host deciding whether to grant them. This makes the security model more granular and auditable.</p>
<p>A plugin marketplace could serve as a central registry of verified plugins, making discovery and distribution easier for teams.</p>
<p>For use cases that require stronger isolation, Web Workers or iframes offer more robust sandboxing than API boundaries alone.</p>
<p>An event bus is another useful addition, allowing plugins to communicate with each other through a shared message system rather than direct API calls, which keeps inter-plugin dependencies loose and manageable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Designing a plugin architecture in React is ultimately about treating your application as a platform rather than a single codebase. By defining clear contracts between the host application and its extensions, you enable teams to ship features independently while preserving stability, security, and performance.</p>
<p>If you are building a system that multiple teams (or even third-party developers) need to extend, start by establishing a minimal host API and plugin contract. Focus on strong TypeScript interfaces, clear lifecycle boundaries, and strict API access rules. These foundations ensure that plugins remain predictable and safe as the ecosystem grows.</p>
<p>As your platform evolves, you can gradually introduce more advanced capabilities such as plugin versioning, capability-based permissions, sandboxed execution environments, or an internal plugin marketplace.</p>
<p>Observability and monitoring also become increasingly important as the number of plugins grows, allowing you to detect compatibility issues or performance regressions early.</p>
<p>The key takeaway is to start simple but intentional. A small, well-defined plugin interface combined with lazy loading and secure API boundaries is often enough to support the first generation of extensions. From there, your architecture can expand naturally into a full ecosystem where features are delivered as modular, independently deployable plugins.</p>
<p>When implemented thoughtfully, a React plugin architecture transforms a single application into a scalable, extensible platform capable of supporting long-term growth and collaboration across teams.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Override API Responses and Headers in Chrome DevTools: A Step-by-Step Guide ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever faced a situation as a frontend developer where you needed to show a demo to your product manager, and something was broken in the API response? Or, a production bug where you were block ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-override-api-responses-and-headers-in-chrome-devtools/</link>
                <guid isPermaLink="false">69c5a33110e664c5da34c6d3</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tapas Adhikary ]]>
                </dc:creator>
                <pubDate>Thu, 26 Mar 2026 21:20:49 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/a2fee8e9-cb71-4065-af8e-ef0357e6ca2c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever faced a situation as a frontend developer where you needed to show a demo to your product manager, and something was broken in the API response? Or, a production bug where you were blocked by waiting on your backend team to provide you with a fix to implement further on the frontend?</p>
<p>To make things even worse, how about a CORS error that completely prevented you from showing the page?</p>
<p>It happens, right? Going back to the backend team and getting a quick fix for these issues would be ideal, but may not be realistic in most cases. It depends on the availability of the backend developers, the priority items they're working on, communication protocols between teams, and even interpersonal relationships.</p>
<p>Now, the question is, can you afford to wait? Wait for things to work in your favour within the given deadline so that you can show that demo or deliver the work to your customer? The answer is NO. You may not have that luxury.</p>
<p>Today, in this article, you'll learn a couple of mind-blowing tips and tricks that will save you from these situations. You'll understand how to set up your Chrome browser so that you can continue with your frontend development even when the backend API returns an incorrect response or a CORS error.</p>
<p>This is a step-by-step guide that'll help make you comfortable with all the required configurations and get you set up to use it on your web projects. This guide is also available as a video tutorial as part of the <a href="https://www.youtube.com/playlist?list=PLIJrr73KDmRwT8Msc4H3_CP5Tf8MqqqVZ">Thinking in Debugging</a> series. You can check it out if you’d like:</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/ndrPcDNmwFk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>

<p>Let's get started with the problems and their solutions.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-problem-1-backend-response-is-wrong">Problem 1: The Backend Response Is Wrong</a></p>
</li>
<li><p><a href="#heading-problem-2-validating-a-ui-scenario-without-backend-changes">Problem 2: Validating a UI Scenario Without Backend Changes</a></p>
</li>
<li><p><a href="#heading-problem-3-handling-cors-errors">Problem 3: Handling CORS Errors</a></p>
</li>
<li><p><a href="#heading-additional-tips">Additional Tips</a></p>
<ul>
<li><p><a href="#heading-applying-overrides-globally">Applying Overrides Globally</a></p>
</li>
<li><p><a href="#heading-disabling-or-removing-overrides">Disabling or Removing Overrides</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-learn-more-from-the-thinking-in-debugging-mindset">Learn More From the Thinking in Debugging Mindset</a></p>
</li>
<li><p><a href="#heading-before-we-end">Before We End…</a></p>
</li>
</ol>
<h2 id="heading-problem-1-the-backend-response-is-wrong">Problem 1: The Backend Response Is Wrong</h2>
<p>Here is a classic case of a wrong API response, but you still need to continue with your frontend work.</p>
<p>Take a look at the image below. Do you see that something is off? Yeah, on the first card, the spelling of <code>Banana</code> seems to be misspelled as <code>Banananana</code>. This user interface is constructed using the data we have received as an API response.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/2bef7ac2-98c0-419a-b783-ee64900f8121.png" alt="Incorrect spelling of banana on first card" style="display:block;margin:0 auto" width="2076" height="1546" loading="lazy">

<p>We can go to the backend team and request that they fix it as soon as possible. But it may not happen until the next sprint starts, which might be 15 days from now.</p>
<p>So, what can we do to continue with our work and all the validations on the frontend side? We can use the <code>Content Overriding</code> feature of the Chrome browser to mitigate this situation.</p>
<h3 id="heading-how-to-use-content-overriding">How to Use <code>Content Overriding</code></h3>
<p>First, open up the DevTools of your Chrome browser by pressing the F12 (on Mac, Cmd+F12) key. Then move to the network tab and inspect the API request that returns the incorrect response.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/f9689133-c7cd-400e-b0cc-5275fe1ca7ec.png" alt="Network Request" style="display:block;margin:0 auto" width="1870" height="808" loading="lazy">

<p>Next, right-click on the API request and select the <code>Override content</code> option from the context menu.</p>
<p>You may wonder what content means here, and what I am overriding? You're overriding the API response so that it can reflect on the UI locally.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/31d4e340-d9fa-4dec-81cc-08fbf644aa58.png" alt="Override Content Option" style="display:block;margin:0 auto" width="814" height="1090" loading="lazy">

<p>This will bring up a UI at the top where you can select a folder to store override files. It's important to understand that all the content overrides are locally stored on your machine's hard disk. This means you can use these persisted overrides again and again until someone fixes the issue permanently at the backend.</p>
<p>Now click on the <code>Select folder</code> button.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/7d238b9d-146c-4592-9008-26c7c189a0de.png" alt="Select a Folder" style="display:block;margin:0 auto" width="1890" height="1530" loading="lazy">

<p>This will open up the folder explorer for you. Create a new folder and select it, or select an existing folder where you want to save the overrides. In my case, I've named the folder as <code>debug_devtools</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/7bc13c7b-0ffa-442a-899e-98a805fcddac.png" alt="Select a Folder" style="display:block;margin:0 auto" width="1564" height="1062" loading="lazy">

<p>Now, Chrome DevTools will ask for confirmation that you're allowing DevTools to edit files on your local system. Just click on the <code>Allow</code> button.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/07ff30f6-8bef-40c3-aee9-bcd55545c67f.png" alt="Allow" style="display:block;margin:0 auto" width="1504" height="682" loading="lazy">

<p>That's all for the setup. Now, you'll find the same response in the editable mode under the <code>Sources</code> tab of DevTools. Let's take a deeper look at the image below:</p>
<ol>
<li><p>The Local overrides are listed under the <code>Sources &gt; Oveerides</code> tab of the DevTools.</p>
</li>
<li><p>On the left side, the <code>Enable Local Overrides</code> checkbox is selected, and the overrides are listed below. You can find the same folder you created before, and under that, you'll see another folder called <code>localhost:3001</code> and a file called <code>edibles</code> under it. The localhost:3001 folder name is related to the API endpoint namespace we're connecting to. The edibles file name under it goes with the request name.</p>
</li>
<li><p>On the right side, you can see the content (that is, the response to the edibles request) in editable mode.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/fc918250-c1ab-4b56-8505-3032e698b8cc.png" alt="fc918250-c1ab-4b56-8505-3032e698b8cc" style="display:block;margin:0 auto" width="1818" height="1410" loading="lazy">

<p>You can even cross-check now by traversing to the file system's <code>debug_devtools</code> folder. You should find the same folders and files as you saw in the DevTools.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/81fd4e82-ae7d-4d07-88d8-5a757edbc3ef.png" alt="Response in folder" style="display:block;margin:0 auto" width="1460" height="662" loading="lazy">

<p>You can open up the <code>edibles</code> file. The file content should match exactly the response you saw before.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/4ba75464-0a29-4f11-9bc6-be0463e10dd5.png" alt="edibles content" style="display:block;margin:0 auto" width="1686" height="1148" loading="lazy">

<p>Now, it's time to override. Coming to the Sources tab's editable response panel, you can fix the spelling. Save your changes using Ctrl + S (or Cmd + S).</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/c33701f0-ddb1-478c-8fe6-d68ddf8dd638.png" alt="edit text" style="display:block;margin:0 auto" width="1634" height="700" loading="lazy">

<p>Now, hard refresh your browser. You should be able to see your change reflected on the UI.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/1414be9d-adf4-41a6-8cbc-d115a49bb8a6.png" alt="Banana Fixed" style="display:block;margin:0 auto" width="2982" height="1590" loading="lazy">

<p>Awesome!! You can now share this overridden response (the <code>edibles</code> file) with other developers to point to from their Chrome DevTools to get the same local fix until the backend fixes it.</p>
<h2 id="heading-problem-2-validating-a-ui-scenario-without-backend-changes">Problem 2: Validating a UI Scenario Without Backend Changes</h2>
<p>Imagine you need to validate that certain items are low in stock on an item listing page. If the stock quantity of an item hits 50 or below, you want to show a <code>Low Stock</code> label for that item.</p>
<p>Now, what if the API response doesn't return a quantity of 50 or below? Content overriding can come to the rescue once again!</p>
<p>You can edit the response to set the quantity value to 50 or below and follow the same process as before to reflect the change on the UI. Look at the image below:</p>
<ol>
<li><p>We have edited the quantity on the right-side panel.</p>
</li>
<li><p>Once saved and refreshed, we not only see the updated count on the UI, but it also runs the underlying JavaScript logic to show the <code>Low Stock</code> label automatically. This is a superpower.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/77b8578e-ecc9-4397-b980-e24cc0ab099e.png" alt="Stock" style="display:block;margin:0 auto" width="2660" height="684" loading="lazy">

<h2 id="heading-problem-3-handling-cors-errors">Problem 3: Handling CORS Errors</h2>
<p><a href="https://www.youtube.com/shorts/lPiQClBVYY4">Cross-Origin Resource Sharing (CORS)</a> is a browser security feature that allows a web server to explicitly grant requests coming from a domain other than its own. By default, browsers don't allow these cross-origin requests and follow a strict rule called <code>Same Origin Resource Sharing</code>.</p>
<p>In many cases, your API server and the web server could be hosted on different domains. In those cases, when the web application attempts to access an API, it faces the CORS error.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/f739eb08-6bb1-4534-9080-0417d511396c.png" alt="CORS Error" style="display:block;margin:0 auto" width="2982" height="742" loading="lazy">

<p>On the server side, you need to have explicit configurations to allow cross-origin requests. For example, you need to add the following response headers:</p>
<pre><code class="language-shell">Access-Control-Allow-Origin: http://localhost:5174
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: *
</code></pre>
<p>So, again, it may not be guaranteed that your CORS error will be resolved at the server side as soon as you want. But you cann't afford to get blocked due to it. So, what's the way around? Yes! The overriding, but this time, overriding the response header.</p>
<p>Go to the network tab of the Chrome DevTools and right-click on the request that has the CORS error. Now, select the <code>Override headers</code> option from the context menu.</p>
<p>By the way, have you noticed that the <code>Override content</code> option is disabled here? This is because we don't have any response as content from this request, as it got an error.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/4d81fbb1-e960-48f2-bdd7-597e686faff6.png" alt="4d81fbb1-e960-48f2-bdd7-597e686faff6" style="display:block;margin:0 auto" width="1280" height="966" loading="lazy">

<p>Clicking on the <code>Overriding headers</code> will take you to the <code>Headers</code> tab where you can find the option to add additional headers to the response headers. Click on the <code>+ Add header</code> button to add the CORS-related headers.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/9acdbde3-0b12-4cef-9437-844ca92d502c.png" alt="Add Header" style="display:block;margin:0 auto" width="1736" height="1036" loading="lazy">

<p>Add all three headers with their respective values one by one:</p>
<pre><code class="language-bash">Access-Control-Allow-Origin: http://localhost:5174 
Access-Control-Allow-Methods: GET 
Access-Control-Allow-Headers: *
</code></pre>
<p>Each of these headers has its own important use:</p>
<ol>
<li><p>With the <code>Access-Control-Allow-Origin</code> header, you can specify the origin domains that are allowed to have a cross-origin request to the server. In this case, the value is <code>http://localhost:5174</code> where we're running a Vite-based ReactJS app.</p>
</li>
<li><p>The header <code>Access-Control-Allow-Methods</code> specifies what kind of HTTP methods are allowed from the originating domain. In this case, we're allowing only the <code>GET</code> method.</p>
</li>
<li><p>The <code>Access-Control-Allow-Headers</code> HTTP response headers specify which HTTP headers can be safely used during a cross-origin request.</p>
</li>
</ol>
<p>Alright, let's add them all and save.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/02fc7203-9ca6-47cf-a478-f20d928ece54.png" alt="CORS Headers" style="display:block;margin:0 auto" width="1714" height="690" loading="lazy">

<p>Like overriding content, overriding the header will also create a folder with the context of the server domain, and under that, a file called <code>.headers</code>. As the file name starts with a dot(.), it may be treated as a hidden file by most operating systems. So make sure you go through the OS settings to view the hidden files to view this file.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/ab93a8d4-b87d-4cf4-8a0e-1d6ce4d26537.png" alt="Hidden headers file" style="display:block;margin:0 auto" width="1394" height="530" loading="lazy">

<p>Once you view and open the file, you'll see the headers you have added with overriding.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/8dfe3ee3-2250-489a-a465-f9796934a470.png" alt="headers content" style="display:block;margin:0 auto" width="1678" height="1122" loading="lazy">

<p>Now, hard refresh your browser, and try to perform the same operation that was giving you the CORS error before. Wow, the error has gone now! You should be able to see the request success and the response coming back from the server.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/351dcb19-af06-4058-b08d-d449b92ab898.png" alt="User Data" style="display:block;margin:0 auto" width="2558" height="1080" loading="lazy">

<p>Just imagine, not a single line of server-side code changes, and you're unblocked so you can move forward with your client-side UI work. Fantastic, isn't it?</p>
<h2 id="heading-additional-tips">Additional Tips</h2>
<p>Before we end, let's learn about a couple more handy tips.</p>
<h3 id="heading-applying-overrides-globally">Applying Overrides Globally</h3>
<p>We applied the CORS error-related header overriding only on the <code>/user</code> API endpoint. What if you need to apply the same overriding for other endpoints, too? You can do it easily by following these simple steps:</p>
<ol>
<li><p>Navigate to the <code>Sources</code> tab.</p>
</li>
<li><p>Select the <code>Overrides</code> sub-tab.</p>
</li>
<li><p>Click on the <code>.headers</code> override.</p>
</li>
<li><p>On the right-side panel, change the value of the <code>Apply to</code> to <code>*</code>.</p>
</li>
</ol>
<p>That's it. Now, the same response headers will be applied as overrides for all the endpoints.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/c15a94e4-a2e9-48b1-8ae1-c781424be388.png" alt="Apply To" style="display:block;margin:0 auto" width="1678" height="666" loading="lazy">

<h3 id="heading-disabling-or-removing-overrides">Disabling or Removing Overrides</h3>
<p>Sometimes, you might want to disable or remove overrides. To disable overrides without removing them, just uncheck the <code>Enable Local Overrides</code> checkbox. To remove all the overrides permanently, click on the stop icon at the top-right corner. Also, to selectively remove an override, right-click on it and delete.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/80c59dc2-0eea-48ad-b499-be1d4493512c.png" alt="80c59dc2-0eea-48ad-b499-be1d4493512c" style="display:block;margin:0 auto" width="848" height="642" loading="lazy">

<h2 id="heading-learn-more-from-the-thinking-in-debugging-mindset">Learn More From the Thinking in Debugging Mindset</h2>
<p>If you've liked this practical, example-driven guide, you'll enjoy my other debugging-related content from the <em>Thinking in Debugging</em> series. Please <a href="https://www.youtube.com/playlist?list=PLIJrr73KDmRwT8Msc4H3_CP5Tf8MqqqVZ">check it out</a>.</p>
<p><a href="https://www.youtube.com/playlist?list=PLIJrr73KDmRwT8Msc4H3_CP5Tf8MqqqVZ"><img src="https://cdn.hashnode.com/uploads/covers/5c9bb4026656f09759cdc1f0/9046f04f-71f1-4303-b39c-7267fd3814bc.png" alt="Thinking in Debugging" style="display:block;margin:0 auto" width="2330" height="1046" loading="lazy"></a></p>
<h2 id="heading-before-we-end">Before We End…</h2>
<p>That’s all! I hope you found this insightful.</p>
<p>Let’s connect:</p>
<ul>
<li><p>Subscribe to my <a href="https://www.youtube.com/tapasadhikary?sub_confirmation=1">YouTube Channel</a>.</p>
</li>
<li><p>Check out my courses, <a href="https://www.youtube.com/playlist?list=PLIJrr73KDmRw2Fwwjt6cPC_tk5vcSICCu">40 Days of JavaScript</a> and <a href="https://www.youtube.com/playlist?list=PLIJrr73KDmRyQVT__uFZvaVfWPdfyMFHC">15 Days of React Design Patterns</a></p>
</li>
<li><p>Follow on <a href="https://www.linkedin.com/in/tapasadhikary/">LinkedIn</a> if you don't want to miss the daily dose of up-skilling tips.</p>
</li>
<li><p>Join my <a href="https://discord.gg/zHHXx4vc2H">Discord Server</a>, and let’s learn together.</p>
</li>
<li><p>Follow my work on <a href="https://github.com/tapascript">GitHub</a>.</p>
</li>
</ul>
<p>See you soon with my next article. Until then, please take care of yourself and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Your UI Won’t Update: Debugging Stale Data and Caching in React Apps ]]>
                </title>
                <description>
                    <![CDATA[ Your UI doesn’t “randomly” refuse to update. In most cases, it’s rendering cached data, which is data that was saved somewhere so the app doesn’t have to do the same work again. Caching is great for performance, but it becomes a pain when you don’t r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-your-ui-wont-update-debugging-stale-data-and-caching-in-react-apps/</link>
                <guid isPermaLink="false">6984d41160b1e5f9aeccaa9e</guid>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwadamisi Samuel ]]>
                </dc:creator>
                <pubDate>Thu, 05 Feb 2026 17:32:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770312709391/8442f6df-1133-47f7-a035-02c958145811.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Your UI doesn’t “randomly” refuse to update. In most cases, it’s rendering cached data, which is data that was saved somewhere so the app doesn’t have to do the same work again.</p>
<p>Caching is great for performance, but it becomes a pain when you don’t realize which layer is reusing old data.</p>
<p>If you’ve ever seen this:</p>
<ul>
<li><p>You update a profile name, but the screen still shows the old one.</p>
</li>
<li><p>You delete an item, but it stays in the list.</p>
</li>
<li><p>Your API returns fresh JSON, but the page refuses to change.</p>
</li>
<li><p>You deploy a fix, but your teammate still sees the old behavior.</p>
</li>
</ul>
<p>You’re probably hitting a cache.</p>
<p>What makes this especially confusing is that not all stale UI comes from “real” caches. Modern web apps have multiple places where data can be reused, saved, or replayed between your UI, your API and when your app is deployed. When you don’t have a clear mental model of these layers, debugging turns into guesswork.</p>
<p>This article lays out a practical guide of the five most common caching layers that cause stale UI, plus one non-cache trap that looks exactly like one. The goal is to help you quickly identify where stale data is coming from, so you can fix the right thing instead of “refreshing harder.”</p>
<h2 id="heading-why-it-matters">Why it Matters</h2>
<p>I first ran into this while building an app where the UI wouldn’t update after a successful change. The API returned 200 OK, the database was correct, but the screen stayed stale. I assumed something was wrong with my code or state logic. Instead, the issue was coming from a caching layer I hadn’t invalidated. That’s the real problem with stale UI, you can’t debug it effectively unless you know which layer might be serving cached data.</p>
<p>When you understand where caching happens:</p>
<ul>
<li><p>You debug faster by identifying the layer instead of guessing.</p>
</li>
<li><p>You avoid production-only bugs caused by caching defaults.</p>
</li>
<li><p>You stop chasing React issues when the data was never fresh.</p>
</li>
</ul>
<p>This article gives you a simple mental model to pinpoint the layer and fix the right thing.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-it-matters">Why it Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-mental-model">The Mental Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-non-cache-cause">Non-Cache Cause</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cache-1-react-query-cache">Cache 1: React Query Cache</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cache-2-nextjs-fetch-caching">Cache 2: Next.js fetch() Caching</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cache-3-browser-http-cache-a-saved-copy-in-your-browser">Cache 3: Browser HTTP Cache (a Saved Copy in Your Browser)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cache-4-cdnhosting-cache">Cache 4: CDN/Hosting Cache</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cache-5-service-worker-cache-only-if-your-site-is-a-pwa">Cache 5: Service Worker Cache (Only if Your Site is a PWA)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-10-second-debug-guide">10-Second Debug Guide</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prevention-set-caching-intentionally">Prevention: Set Caching Intentionally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-recap">Recap</a></p>
</li>
</ul>
<h2 id="heading-the-mental-model">The Mental Model</h2>
<p>When your UI shows data, it feels like it comes straight from your API. In reality, the request/response path can hit multiple reuse points.</p>
<h2 id="heading-non-cache-cause">Non-Cache Cause</h2>
<p>Duplicated React local state (same symptoms as caching). This one isn’t a formal cache, but it causes a lot of “why didn’t it update?” bugs especially for beginners.</p>
<h3 id="heading-the-common-trap">The common trap:</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [name, setName] = useState(user.name) <span class="hljs-comment">// initialized once</span>
</code></pre>
<p><code>useState</code> only uses its argument during the initial render. On every subsequent render, React ignores this value and preserves the existing state.</p>
<p>If <code>user.name</code> later changes (for example, after fresh API data arrives), the <code>name</code> state will not update automatically. At that point, <code>name</code> becomes a stale copy of <code>user.name</code>, and the UI renders outdated data unless you manually synchronize it.</p>
<p>This happens because you have duplicated state:</p>
<ul>
<li><p><code>user.name</code> is the source of truth.</p>
</li>
<li><p><code>name</code> state is a local snapshot taken once.</p>
</li>
</ul>
<p>React does not keep duplicated state in sync for you.</p>
<p>Correct patterns:</p>
<ol>
<li>Render directly from the source when possible.</li>
</ol>
<p>If the value is not being edited locally, do not copy it into state:</p>
<pre><code class="lang-javascript">&lt;span&gt;{user.name}&lt;/span&gt;
</code></pre>
<p>This guarantees the UI always reflects the latest data.</p>
<ol start="2">
<li>Explicitly synchronize local state when editable state is required.</li>
</ol>
<p>If you need local, editable state (for example, a controlled input), you must opt in to synchronization:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [name, setName] = useState(user.name);  

    useEffect(<span class="hljs-function">() =&gt;</span> {    
        setName(user.name); 
     }, [user.name]);
</code></pre>
<p>This effect runs only when <code>user.name</code> changes, explicitly updating local state to match the new source value.</p>
<h2 id="heading-cache-1-react-query-cache">Cache 1: React Query Cache</h2>
<p>React Query (TanStack Query) stores query results in a QueryClient cache (in memory by default) so your UI can render quickly and avoid unnecessary network requests. When a component needs data, React Query can return cached data immediately and then decide whether to fetch the data again based on options like <code>staleTime</code> and “refetch” behaviors (on mount, window focus, reconnect).</p>
<h3 id="heading-common-failure-mode-mutation-succeeds-but-the-ui-stays-old">Common failure mode: mutation succeeds, but the UI stays old</h3>
<p>A 200 OK only confirms the mutation request succeeded. It does not automatically update the cached query data your UI is rendering.</p>
<p>After a mutation, one of these usually happens:</p>
<ul>
<li><p>The query that renders the screen was not invalidated/fetched</p>
</li>
<li><p>You invalidated the wrong query key (the UI reads from a different key)</p>
</li>
<li><p>The UI is rendering local React state that’s out of sync (not the query result)</p>
</li>
</ul>
<p>The simplest “safe” pattern is: invalidate the exact query key your UI uses, so it fetches fresh data.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useMutation, useQueryClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useUpdateProfile</span>(<span class="hljs-params">userId: string</span>) </span>{
  <span class="hljs-keyword">const</span> queryClient = useQueryClient();

  <span class="hljs-keyword">return</span> useMutation({
    <span class="hljs-attr">mutationFn</span>: updateProfileRequest,
    <span class="hljs-attr">onSuccess</span>: <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Invalidate the same key your UI query uses (example: ["user", userId])</span>
      queryClient.invalidateQueries({ <span class="hljs-attr">queryKey</span>: [<span class="hljs-string">"user"</span>, userId] });
    },
  });
}
</code></pre>
<p>If your UI uses a different key (for example <code>["me"]</code> or <code>["user", userId, "profile"]</code>), you must invalidate that key instead, React Query won’t “figure it out” from the URL.</p>
<h3 id="heading-query-keys-react-query-caches-by-key-not-url">Query Keys: React Query Caches by Key, not URL</h3>
<p>React Query does not cache by endpoint URL. The query key is the identity of the cached data. If two different requests share the same key, React Query treats them as the same data and they can overwrite each other.</p>
<p>You should avoid keys like <code>["user"]</code> (too broad), and use keys like <code>["user", userId]</code> and <code>["users", { page, search, filter }]</code>.</p>
<p><strong>Two settings that control “when it will refetch”:</strong></p>
<ul>
<li><p><strong>staleTime:</strong> how long cached data is treated as fresh. While data is fresh, React Query is less likely to refetch automatically.</p>
</li>
<li><p><strong>gcTime (formerly cacheTime):</strong> how long unused query data stays in memory after it’s no longer used by any component, before it’s garbage collected.</p>
</li>
</ul>
<h2 id="heading-cache-2-nextjs-fetch-caching">Cache 2: Next.js fetch() Caching</h2>
<p>This is the one that surprises a lot of frontend devs. Next.js can cache results to speed things up. That means your server might return a previously saved copy of:</p>
<ul>
<li><p>The API data it fetched, or</p>
</li>
<li><p>The page it already built</p>
</li>
</ul>
<p>This is often the first time frontend developers encounter server-side caching behavior that affects UI correctness. So, even if your database has the new value, you can still see the old one, because Next.js didn’t fetch the API again, or didn’t rebuild the page this time.</p>
<p>This mainly applies to the App Router (Next.js calls these saved copies the Data Cache and Full Route Cache).</p>
<h3 id="heading-what-youll-notice-when-this-happens">What you’ll notice when this happens</h3>
<ul>
<li><p>You refresh the page and it still shows the old value.</p>
</li>
<li><p>Your API is correct (Postman/curl shows the new email), but the UI is stuck.</p>
</li>
<li><p>Sometimes it “fixes itself” after a short wait (because the saved copy refreshes on a timer).</p>
</li>
</ul>
<p>For example: “I updated my profile email, but prod still shows the old one”</p>
<p>The page (reads email on the server):</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/settings/page.tsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SettingsPage</span>(<span class="hljs-params"></span>) </span>{
 <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.example.com/users/42"</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
})
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> res.json();

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Settings<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Email: {user.email}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
}
</code></pre>
<p>You submit an “Update email” form, the API returns <strong>200 OK</strong>, the database is updated, but /settings still shows the previous email in production.</p>
<p>That usually means you’re seeing a saved copy somewhere on the server side.</p>
<h3 id="heading-how-to-debug-it">How to debug it</h3>
<h4 id="heading-step-1-reproduce-in-a-production-like-run">Step 1: Reproduce in a production-like run</h4>
<p>Caching can behave differently in development. Run:</p>
<pre><code class="lang-bash">next build &amp;&amp; next start
</code></pre>
<p>Then test again.</p>
<h4 id="heading-step-2-confirm-whether-the-request-is-reaching-your-nextjs-server-at-all">Step 2: Confirm whether the request is reaching your Next.js server at all</h4>
<p>Add a log inside the page:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Rendering /settings at"</span>, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
</code></pre>
<p>Then reload settings twice.</p>
<ul>
<li><p>If you see a new timestamp every reload, the request is reaching your server and the page code is running.</p>
</li>
<li><p>If you don’t see logs in production, your request may not be reaching your server at all (often because a hosting/CDN layer is serving a saved copy before Next.js runs). You’ll confirm that in the CDN section later.</p>
</li>
</ul>
<h4 id="heading-step-3-force-nextjs-to-ask-your-api-every-time">Step 3: Force Next.js to ask your API every time</h4>
<p>Change the fetch to:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.example.com/me"</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
  <span class="hljs-attr">cache</span>: <span class="hljs-string">"no-store"</span>,
});
</code></pre>
<p>This means: don’t save this response – always fetch it again.</p>
<p>If this fixes the stale email then the problem was a saved copy of the API response (Data Cache).</p>
<h4 id="heading-step-4-if-the-email-is-still-stale-force-nextjs-to-rebuild-the-page-every-request">Step 4: If the email is still stale, force Next.js to rebuild the page every request</h4>
<p>Add this to the page file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/settings/page.tsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dynamic = <span class="hljs-string">"force-dynamic"</span>;
</code></pre>
<p>This means: don’t serve a saved copy of the page; rebuild it per request.</p>
<h3 id="heading-a-beginner-safe-setup-for-the-user-settings-pages-with-some-of-the-suggestions">A “beginner-safe” setup for the user settings pages with some of the suggestions:</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/settings/page.tsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> dynamic = <span class="hljs-string">"force-dynamic"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SettingsPage</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.example.com/me"</span>, { <span class="hljs-attr">cache</span>: <span class="hljs-string">"no-store"</span> });
  <span class="hljs-keyword">const</span> me = <span class="hljs-keyword">await</span> res.json();
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Email: {me.email}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
}
</code></pre>
<p>When you want caching for speed, but still need real time updates, these are some options you can take:</p>
<h4 id="heading-option-a-refresh-the-saved-copy-every-n-seconds">Option A: Refresh the saved copy every N seconds</h4>
<p>Good for public pages, not ideal for “my settings must update now.”</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> fetch(url, { <span class="hljs-attr">next</span>: { <span class="hljs-attr">revalidate</span>: <span class="hljs-number">60</span> } });
</code></pre>
<p>This means: “You can reuse a saved copy, but refresh it at most every 60 seconds.”</p>
<h4 id="heading-option-b-refresh-right-after-the-update-best-for-update-email-flows">Option B: Refresh right after the update (best for “update email” flows)</h4>
<p>If you update the email on the server (Server Action or API route), tell Next.js to throw away the saved copy for /settings page so the next visit is fresh:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// app/settings/actions.ts</span>
<span class="hljs-string">"use server"</span>;

<span class="hljs-keyword">import</span> { revalidatePath } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/cache"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateEmail</span>(<span class="hljs-params">email: string</span>) </span>{
  <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.example.com/me/email"</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"PUT"</span>,
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">"content-type"</span>: <span class="hljs-string">"application/json"</span> },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ email }),
  });

  <span class="hljs-comment">// Tell Next.js: next request to /settings should be rebuilt</span>
  revalidatePath(<span class="hljs-string">"/settings"</span>);
}
</code></pre>
<p><strong>Note</strong>: Next.js caching details can differ by version and by App Router vs Pages Router. Instead of trying to memorize defaults, debug by setting the behavior explicitly (no-store, revalidate, force-dynamic) and observe what changes.</p>
<h2 id="heading-cache-3-browser-http-cache-a-saved-copy-in-your-browser">Cache 3: Browser HTTP Cache (a Saved Copy in Your Browser)</h2>
<p>Sometimes the browser reuses a saved copy of an API response (from memory or disk), so it doesn’t fully fetch it again.</p>
<h3 id="heading-what-youll-notice">What you’ll notice</h3>
<p>You open DevTools, and the network shows (from memory cache) or (from disk cache).</p>
<h3 id="heading-fast-check">Fast check</h3>
<p>DevTools → Network</p>
<ul>
<li><p>Turn on Disable cache (only works while DevTools is open)</p>
</li>
<li><p>Reload and retry</p>
</li>
</ul>
<h3 id="heading-why-it-happens">Why it happens</h3>
<p>Usually your server allows caching via headers like Cache-Control or ETag (which can lead to 304 Not Modified).</p>
<h2 id="heading-cache-4-cdnhosting-cache">Cache 4: CDN/Hosting Cache</h2>
<p>This is often a production-only cache, which is why frontend bugs can appear “impossible” to reproduce locally. In production, a CDN/hosting layer can serve a saved copy of a response before your request reaches your server. That’s why “prod is stale, local is fine” happens.</p>
<h3 id="heading-what-youll-notice-1">What you’ll notice</h3>
<ul>
<li><p>Prod is stale, local is fine</p>
</li>
<li><p>Different users see different results (different regions/POPs)</p>
</li>
<li><p>Pages are very fast even right after data changed</p>
</li>
</ul>
<h3 id="heading-fast-check-1">Fast check</h3>
<p>Open DevTools → Network → click the request → Response Headers</p>
<ul>
<li><p>Age: if present and increasing, it’s strong evidence you’re getting a cached response from an intermediary cache</p>
</li>
<li><p>Provider headers can hint HIT/MISS (examples: x-vercel-cache, cf-cache-status)</p>
</li>
<li><p>Source (Age header, HTTP caching): <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc9111">https://www.rfc-editor.org/rfc/rfc9111</a></p>
</li>
</ul>
<h3 id="heading-quick-diagnostic-check">Quick diagnostic check</h3>
<p>Change the URL slightly by adding this to the end of the URL:</p>
<pre><code class="lang-javascript">?debug=<span class="hljs-number">1700000000000</span>
</code></pre>
<p>If the new URL shows fresh data, the edge was likely caching the original URL. This doesn’t fix it for everyone, you’d still need correct cache settings or a purge/invalidation on your CDN.</p>
<h2 id="heading-cache-5-service-worker-cache-only-if-your-site-is-a-pwa">Cache 5: Service Worker Cache (Only if Your Site is a PWA)</h2>
<p>If your site has a service worker, it can return a saved response before the network runs. This can make new deployments or new data seem “ignored.”</p>
<h3 id="heading-what-youll-notice-2">What you’ll notice</h3>
<ul>
<li><p>Works in Incognito but not normal mode</p>
</li>
<li><p>Hard refresh doesn’t help</p>
</li>
<li><p>DevTools “Disable cache” doesn’t fully explain it</p>
</li>
</ul>
<h3 id="heading-fast-check-chrome">Fast check (Chrome)</h3>
<p>Open DevTools → Application → Service Workers</p>
<ul>
<li><p>enable Bypass for network, or Unregister temporarily</p>
</li>
<li><p>reload and retest</p>
</li>
</ul>
<h2 id="heading-10-second-debug-guide">10-Second Debug Guide</h2>
<p>Stale data is rarely random: it usually means a cache layer is doing its job, just not in the way you expect. Modern applications stack multiple caches, so debugging is less about fixing code immediately and more about locating the layer responsible.</p>
<p>Think of this as a quick cheat sheet to figure out which cache layer might be serving stale data, so you can focus your debugging on the right layer.</p>
<ul>
<li><p>No request in Network? Go to <code>Cache 1 (React Query)</code>, then Local state, then <code>Cache 5 (Service worker)</code>.</p>
</li>
<li><p>Request exists, but response is old? Go to <code>Cache 3 (Browser)</code>, <code>Cache 4 (CDN)</code>, then <code>Cache 2 (Next.js)</code>.</p>
</li>
<li><p>Response is fresh, UI is old? Go back to <code>Cache 1 (invalidating / query keys)</code> and Local state.</p>
</li>
</ul>
<p>Once you know the likely layer, use the Fast check in that section to confirm it.</p>
<h2 id="heading-prevention-set-caching-intentionally">Prevention: Set Caching Intentionally</h2>
<p>Most stale-data bugs happen because caching settings were never chosen but the defaults were.</p>
<ul>
<li><p>User-specific pages (settings/admin/dashboard): default to fresh: Next.js: use cache: "no-store" on important fetches, and/or force dynamic routes when needed.</p>
</li>
<li><p>Public pages (marketing/blog/docs): saving + revalidate is usually fine: Decide a revalidate window that matches the business need (seconds/minutes/hours).</p>
</li>
<li><p>React Query: set staleTime based on how often the data actually changes, and make query keys match the inputs.</p>
</li>
<li><p>APIs: set Cache-Control / Vary intentionally so shared caches don’t mix user-specific responses.</p>
</li>
</ul>
<h2 id="heading-recap">Recap</h2>
<p>Caching itself isn’t the problem. Stale UI happens when a cache exists but you didn’t choose it intentionally or align it with the data’s freshness requirements.</p>
<p>If the UI won’t update, it’s usually because you’re seeing a saved copy from React Query, Next.js, the browser, a CDN, or a service worker. And sometimes it’s not a cache at all, it’s local React state</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Debug React State Updates Like a Pro (Without Polluting Production) ]]>
                </title>
                <description>
                    <![CDATA[ When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-debug-react-state-updates-like-a-pro-without-polluting-production/</link>
                <guid isPermaLink="false">698115a622cd39b64c5fac49</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React state management ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kelechi Apugo ]]>
                </dc:creator>
                <pubDate>Mon, 02 Feb 2026 21:22:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770067225475/d0910306-5756-465a-8b6f-adf839fe004a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re debugging a large React codebase, you might start to feel like a detective. Especially when you are looking for unexpected state changes, components that re-render when they like, or Context values that disappear into thin air without any prior warning sign.</p>
<p>And the main difficulty isn’t necessarily what went wrong – it’s pinpointing where it went wrong.</p>
<p>React offers powerful ways to change state, but it doesn’t specify who or what caused those changes. In large apps with many layers of components, hooks, and contexts, this lack of insight can turn simple bugs into frustrating, time-consuming puzzles.</p>
<p>This is where more innovative debugging methods become crucial. Before now, the go-to solution was to sprinkle <code>console.log</code> calls at key points or to fall back to DevTools.</p>
<p>But these days, you can write a small but powerful utility function that can catch the criminal involved in the crimes against your codebase. This utility function can log changes, display meaningful stack traces, and work smoothly with <code>useState</code>, <code>useReducer</code>, Context providers, and custom hooks. And all of the above can occur while remaining invisible in production.</p>
<p>This article guides you through how to use this helper function to improve clarity, minimise guesswork, and debug efficiently without affecting performance or code cleanliness in your live environment.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-problem">The Problem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-problem-exists">Why This Problem Exists</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></a></p>
<ul>
<li><a class="post-section-overview" href="#heading-practical-examples-of-createdebugsetter">Practical Examples of <code>createDebugSetter</code></a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-using-createdebugsetter">Best Practices for using <code>createDebugSetter</code></a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-things-to-avoid">Things to Avoid</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-convert-createdebugsetter-to-a-hook">Bonus: How to Convert <code>createDebugSetter</code> to a Hook</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-problem">The Problem</h2>
<p>React’s state system is powerful, but it hides too much information when something goes wrong – for example, when an unexpected update happens or a component re-renders endlessly. React doesn’t tell you <em>what</em> triggered the update, <em>what</em> changed, or <em>why</em> it happened. This lack of visibility creates several challenges.</p>
<p>The first is that you can’t easily see which component, function, or effect initiated a state update. In large applications, where the same state may be modified from multiple places, this quickly turns debugging into guesswork. Without clear traces, developers often sprinkle <code>console.log</code> throughout their code to find the source of a single update.</p>
<p>Secondly, React lacks a built-in method for directly comparing previous and current values. This complicates diagnosing whether a bug stems from an incorrect calculation, a faulty API response, or erroneous business logic. The challenge increases with nested objects, arrays, or shared context.</p>
<p>Thirdly, Context updates can trigger re-renders across the entire tree, even for components wrapped in memoisation. But React doesn’t explain <em>why</em> a particular provider changed, leaving teams to wonder what triggered the cascade.</p>
<p>Finally, infinite loops caused by effects, unstable dependencies, or repeated setState calls provide no clues in the console. You only see symptoms like “loading…” repeating endlessly, with no indication of the source.</p>
<p>All of this makes debugging complex React apps frustrating, slow, and often misleading without additional tools or structured techniques.</p>
<h2 id="heading-why-this-problem-exists">Why This Problem Exists</h2>
<p>React intentionally conceals its internal update process to keep the framework fast and predictable.</p>
<p>Because of this:</p>
<ul>
<li><p><code>setState()</code> doesn’t report where it was called from</p>
</li>
<li><p>Context re-renders can originate from anywhere.</p>
</li>
<li><p>State overrides can happen silently.</p>
</li>
<li><p>Debugging often relies on manually adding console logs.</p>
</li>
</ul>
<p>In large applications, this lack of visibility makes it nearly impossible to trace unexpected state changes.</p>
<h2 id="heading-my-solution-createdebugsetter">My Solution: <code>createDebugSetter</code></h2>
<p>A small helper function, <code>createDebugSetter</code>, wraps your state setter and logs:</p>
<ul>
<li><p>The label of the state</p>
</li>
<li><p>The new value</p>
</li>
<li><p>A complete stack trace showing exactly where the update originated</p>
</li>
</ul>
<p>And best of all, it automatically disables itself in production using <code>NODE_ENV</code> so there’s no impact on your live app.</p>
<h3 id="heading-createdebugsetter">createDebugSetter</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createDebugSetter</span>(<span class="hljs-params">
  label: <span class="hljs-built_in">string</span>,
  setter:
    | React.Dispatch&lt;React.SetStateAction&lt;unknown&gt;&gt;
    | React.Dispatch&lt;unknown&gt;
</span>): <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">React</span>.<span class="hljs-title">SetStateAction</span>&lt;<span class="hljs-title">unknown</span>&gt;&gt; | <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">unknown</span>&gt; </span>{
  <span class="hljs-comment">// In production, return the original setter unchanged</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD <span class="hljs-comment">/* vite-react */</span>) {
    <span class="hljs-keyword">return</span> setter;
  }

  <span class="hljs-comment">// Create a wrapper that logs before calling the original setter</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">value: React.SetStateAction&lt;unknown&gt; | unknown</span>) =&gt;</span> {
    <span class="hljs-comment">// Log the state change</span>
    <span class="hljs-built_in">console</span>.groupCollapsed(
      <span class="hljs-string">`%c🔄 [<span class="hljs-subst">${label}</span>] State Update`</span>,
      <span class="hljs-string">"color: #adad01; font-weight: bold;"</span>
    );
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, value);
    <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
    <span class="hljs-built_in">console</span>.groupEnd();

    <span class="hljs-comment">// Call the original setter</span>
    setter(value);
  };
}
</code></pre>
<p>The function above is a <strong>debugging wrapper for React state setters</strong> that logs during development.</p>
<p>It takes two parameters: a label for identification and the <code>setState</code> function you want to debug. In development mode, every state update triggers a collapsible console log that shows the new value and the stack trace of the update's origin. In production, the hook skips entirely and returns the original setter unchanged, ensuring zero runtime overhead in deployed applications.</p>
<p>How it does it:</p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// In production, return the original setter unchanged</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">import</span>.meta.env.PROD <span class="hljs-comment">/* vite-react */</span>) {
    <span class="hljs-keyword">return</span> setter;
  }
</code></pre>
<p>In production, the <code>createDebugSetter</code> function returns the React <code>setState</code> as-is. This is because we don’t want to log anything when our code is running in a production environment. Here, we’re using the <code>import.meta.env.PROD</code> from React-vite. It returns a <code>Boolean</code> value that tells us if it’s in the production environment or not.</p>
<p>If it’s not in production, we return the modified setter below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Create a wrapper that logs before calling the original setter</span>
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">value: React.SetStateAction&lt;unknown&gt; | unknown</span>) =&gt;</span> {
    <span class="hljs-comment">// Log the state change</span>
    <span class="hljs-built_in">console</span>.groupCollapsed(
      <span class="hljs-string">`%c🔄 [<span class="hljs-subst">${label}</span>] State Update`</span>,
      <span class="hljs-string">"color: #adad01; font-weight: bold;"</span>
    );
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, value);
    <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
    <span class="hljs-built_in">console</span>.groupEnd();

    <span class="hljs-comment">// Call the original setter</span>
    setter(value);
  };
</code></pre>
<p>This new setter function first logs a collapsible console group with the label and emoji. Then it shows the new value being set. After that, it displays a stack trace showing where the update was triggered. Lastly, it calls the original setter to actually update the state.</p>
<h3 id="heading-practical-examples-of-createdebugsetter">Practical Examples of createDebugSetter</h3>
<p>Let’s now see how <code>createDebugSetter</code> can be used in several places within a codebase.</p>
<h4 id="heading-context-providers">Context Providers</h4>
<p>You can use <code>createDebugSetter</code> within a Context provider to log state changes when <code>setState</code> is called. This can help log and trace state changes whenever <code>setState</code> is called in a Context Provider anywhere in the application.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { createContext, useContext, useState, <span class="hljs-keyword">type</span> ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/createDebugSetter"</span>;

<span class="hljs-keyword">interface</span> User {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  role: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> UserContextType {
  user: User | <span class="hljs-literal">null</span>;
  setUser: React.Dispatch&lt;React.SetStateAction&lt;User | <span class="hljs-literal">null</span>&gt;&gt;;
  login: <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  logout: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> UserContext = createContext&lt;UserContextType | <span class="hljs-literal">undefined</span>&gt;(<span class="hljs-literal">undefined</span>);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UserProvider</span>(<span class="hljs-params">{ children }: { children: ReactNode }</span>) </span>{
  <span class="hljs-keyword">const</span> [user, setUserOriginal] = useState&lt;User | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-comment">// Wrap setter with debug functionality</span>
  <span class="hljs-keyword">const</span> setUser = createDebugSetter(
    <span class="hljs-string">"UserContext"</span>,
    setUserOriginal
  ) <span class="hljs-keyword">as</span> React.Dispatch&lt;React.SetStateAction&lt;User | <span class="hljs-literal">null</span>&gt;&gt;;

  <span class="hljs-keyword">const</span> login = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    setUser({
      name,
      email,
      role: <span class="hljs-string">"user"</span>,
    });
  };

  <span class="hljs-keyword">const</span> logout = <span class="hljs-function">() =&gt;</span> {
    setUser(<span class="hljs-literal">null</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;UserContext.Provider value={{ user, setUser, login, logout }}&gt;
      {children}
    &lt;/UserContext.Provider&gt;
  );
}
</code></pre>
<p>In the above code sample, we create a modified <code>setUserOriginal</code> called <code>setUser</code> that uses <code>createDebugSetter</code> under the hood. We then expose it to the context value instead of <code>setUserOriginal</code> .</p>
<p>Whenever <code>setUser</code> is called, it triggers <code>createDebugSetter</code> which does its job of checking the environment the code is running in, and returns a modified setter that will call <code>setUserOriginal</code> after the logging process, or will return <code>setUserOriginal</code> as-is.</p>
<p>This is useful because Context updates can trigger many re-renders. This reveals exactly who changed the shared state.</p>
<h4 id="heading-usestate">useState</h4>
<p>As you saw in the Context provider example above, we can use the same technique in regular components that use React state setters (just as in Context providers). We log and trace the value. It also shows where it was triggered from within the component or application.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useDebugSetter"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UseStateExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [count, setCountOriginal] = useState(<span class="hljs-number">0</span>);
  <span class="hljs-keyword">const</span> [name, setNameOriginal] = useState(<span class="hljs-string">"React"</span>);

  <span class="hljs-comment">// Wrap setters with debug functionality</span>
  <span class="hljs-keyword">const</span> setCount = useDebugSetter(<span class="hljs-string">"Counter"</span>, setCountOriginal);
  <span class="hljs-keyword">const</span> setName = useDebugSetter(<span class="hljs-string">"Name"</span>, setNameOriginal);

  <span class="hljs-keyword">const</span> handleIncrement = <span class="hljs-function">() =&gt;</span> {
    setCount(count + <span class="hljs-number">1</span>);
  };

  <span class="hljs-keyword">const</span> handleDecrement = <span class="hljs-function">() =&gt;</span> {
    setCount(count - <span class="hljs-number">1</span>);
  };

  <span class="hljs-keyword">const</span> handleNameChange = <span class="hljs-function">() =&gt;</span> {
    setName(name === <span class="hljs-string">"React"</span> ? <span class="hljs-string">"Vite"</span> : <span class="hljs-string">"React"</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;useState Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs when state changes.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Count: &lt;strong&gt;{count}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;button onClick={handleIncrement} style={{ marginRight: <span class="hljs-string">"10px"</span> }}&gt;
          Increment
        &lt;/button&gt;
        &lt;button onClick={handleDecrement}&gt;Decrement&lt;/button&gt;
      &lt;/div&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Name: &lt;strong&gt;{name}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;button onClick={handleNameChange}&gt;Toggle Name&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>This works exactly like the Context providers example. The only differences are that the component uses the <code>setCount</code> and <code>setName</code> functions out of the box in buttons and related components. Also, unlike the Context provider, this component has local state that can be passed to its child components if needed.</p>
<p>This is ideal for monitoring unforeseen local state changes or loops triggered by effects.</p>
<h4 id="heading-usereducer">useReducer</h4>
<p>React reducers are used to calculate complex logic before updating the state. This can introduce unwanted side effects during the complex phase. <code>createDebugSetter</code> can help in debugging, as shown below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/createDebugSetter"</span>;

<span class="hljs-keyword">interface</span> CounterState {
  count: <span class="hljs-built_in">number</span>;
  step: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">type</span> CounterAction =
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"increment"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"decrement"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"reset"</span> }
  | { <span class="hljs-keyword">type</span>: <span class="hljs-string">"setStep"</span>; step: <span class="hljs-built_in">number</span> };

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">counterReducer</span>(<span class="hljs-params">
  state: CounterState,
  action: CounterAction
</span>): <span class="hljs-title">CounterState</span> </span>{
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"increment"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: state.count + state.step };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"decrement"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: state.count - state.step };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"reset"</span>:
      <span class="hljs-keyword">return</span> { ...state, count: <span class="hljs-number">0</span> };
    <span class="hljs-keyword">case</span> <span class="hljs-string">"setStep"</span>:
      <span class="hljs-keyword">return</span> { ...state, step: action.step };
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> state;
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">UseReducerExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [state, dispatchOriginal] = useReducer(counterReducer, {
    count: <span class="hljs-number">0</span>,
    step: <span class="hljs-number">1</span>,
  });

  <span class="hljs-comment">// Wrap dispatch with debug functionality</span>
  <span class="hljs-keyword">const</span> dispatch = createDebugSetter(<span class="hljs-string">"CounterReducer"</span>, dispatchOriginal);

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;useReducer Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs <span class="hljs-keyword">for</span> reducer actions.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p&gt;
          Count: &lt;strong&gt;{state.count}&lt;/strong&gt;
        &lt;/p&gt;
        &lt;p&gt;
          Step: &lt;strong&gt;{state.step}&lt;/strong&gt;
        &lt;/p&gt;

        &lt;div style={{ marginTop: <span class="hljs-string">"10px"</span> }}&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"increment"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Increment (+{state.step})
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"decrement"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Decrement (-{state.step})
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"reset"</span> })}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Reset
          &lt;/button&gt;
          &lt;button
            onClick={<span class="hljs-function">() =&gt;</span>
              dispatch({ <span class="hljs-keyword">type</span>: <span class="hljs-string">"setStep"</span>, step: state.step === <span class="hljs-number">1</span> ? <span class="hljs-number">5</span> : <span class="hljs-number">1</span> })
            }
          &gt;
            Toggle Step ({state.step === <span class="hljs-number">1</span> ? <span class="hljs-string">"1→5"</span> : <span class="hljs-string">"5→1"</span>})
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><code>dispatchOriginal</code>, which is the main dispatch function, is replaced with a custom function called <code>dispatch</code> that uses <code>createDebugSetter</code>. When the custom <code>dispatch</code> function is called, it does the job of <code>createDebugSetter</code> and by extension, the job of <code>dispatchOriginal</code> .</p>
<p>This is perfect for logging reducer actions and understanding complex state transitions.</p>
<h4 id="heading-custom-hooks">Custom Hooks</h4>
<p>Custom hooks are not left out of the equation, as they can use <code>setState</code> in some cases. They’re also capable of running complex logic that could backfire when updating <code>state</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useDebugSetter } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useDebugSetter"</span>;

<span class="hljs-comment">// Custom hook that manages a timer</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useTimer</span>(<span class="hljs-params">initialSeconds: <span class="hljs-built_in">number</span> = 0</span>) </span>{
  <span class="hljs-keyword">const</span> [seconds, setSecondsOriginal] = useState(initialSeconds);
  <span class="hljs-keyword">const</span> [isRunning, setIsRunningOriginal] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// Wrap setters with debug functionality</span>
  <span class="hljs-keyword">const</span> setSeconds = useDebugSetter(<span class="hljs-string">"Timer.seconds"</span>, setSecondsOriginal);
  <span class="hljs-keyword">const</span> setIsRunning = useDebugSetter(<span class="hljs-string">"Timer.isRunning"</span>, setIsRunningOriginal);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!isRunning) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">const</span> interval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
      setSeconds(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev + <span class="hljs-number">1</span>);
    }, <span class="hljs-number">1000</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">clearInterval</span>(interval);
  }, [isRunning, setSeconds]);

  <span class="hljs-keyword">const</span> start = <span class="hljs-function">() =&gt;</span> setIsRunning(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> stop = <span class="hljs-function">() =&gt;</span> setIsRunning(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> reset = <span class="hljs-function">() =&gt;</span> {
    setSeconds(<span class="hljs-number">0</span>);
    setIsRunning(<span class="hljs-literal">false</span>);
  };

  <span class="hljs-keyword">return</span> {
    seconds,
    isRunning,
    start,
    stop,
    reset,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CustomHookExample</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> timer = useTimer(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">const</span> formatTime = <span class="hljs-function">(<span class="hljs-params">seconds: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> mins = <span class="hljs-built_in">Math</span>.floor(seconds / <span class="hljs-number">60</span>);
    <span class="hljs-keyword">const</span> secs = seconds % <span class="hljs-number">60</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${mins.toString().padStart(<span class="hljs-number">2</span>, <span class="hljs-string">"0"</span>)}</span>:<span class="hljs-subst">${secs
      .toString()
      .padStart(<span class="hljs-number">2</span>, <span class="hljs-string">"0"</span>)}</span>`</span>;
  };

  <span class="hljs-keyword">return</span> (
    &lt;div
      style={{
        padding: <span class="hljs-string">"20px"</span>,
        border: <span class="hljs-string">"1px solid #ccc"</span>,
        borderRadius: <span class="hljs-string">"8px"</span>,
        margin: <span class="hljs-string">"10px"</span>,
      }}
    &gt;
      &lt;h2&gt;Custom Hook Example&lt;/h2&gt;
      &lt;p&gt;Open the <span class="hljs-built_in">console</span> to see debug logs <span class="hljs-keyword">for</span> internal hook state changes.&lt;/p&gt;

      &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
        &lt;p style={{ fontSize: <span class="hljs-string">"24px"</span>, fontWeight: <span class="hljs-string">"bold"</span> }}&gt;
          {formatTime(timer.seconds)}
        &lt;/p&gt;
        &lt;p&gt;
          Status: &lt;strong&gt;{timer.isRunning ? <span class="hljs-string">"Running"</span> : <span class="hljs-string">"Stopped"</span>}&lt;/strong&gt;
        &lt;/p&gt;

        &lt;div style={{ marginTop: <span class="hljs-string">"15px"</span> }}&gt;
          &lt;button
            onClick={timer.start}
            disabled={timer.isRunning}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Start
          &lt;/button&gt;
          &lt;button
            onClick={timer.stop}
            disabled={!timer.isRunning}
            style={{ marginRight: <span class="hljs-string">"10px"</span> }}
          &gt;
            Stop
          &lt;/button&gt;
          &lt;button onClick={timer.reset}&gt;Reset&lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>As shown in previous examples, <code>setSecondsOriginal</code> and <code>setIsRunningOriginal</code> are replaced with <code>setSeconds</code> and <code>setIsRunning</code>. The latter uses the <code>createDebugSetter</code> helper function. This enables console log statements to be printed every second for better visualisation.</p>
<p>Custom hooks often hide multiple internal updates, making it hard to see exactly where each begins.</p>
<h2 id="heading-best-practices-for-using-createdebugsetter">Best Practices for Using <code>createDebugSetter</code></h2>
<p>When using helper functions like <code>createDebugSetter</code>, it’s best to keep in mind why you’re actually using them. For our purpose here, we’re using it to debug a React application. So I’ll share some tips that will help with this debugging process.</p>
<h3 id="heading-use-clear-labels">Use Clear Labels</h3>
<p>Using labels that can say where <code>createDebugSetter</code> was triggered from is a step in the right direction. Detailed labels will help you better understand where and why the issue may be occurring. Also, keep in mind that the <code>createDebugSetter</code> utility function could be used in several places in your application, and improper labelling could make debugging difficult.  </p>
<p>Using the name of the component or area that calls it as the label for <code>createDebugSetter</code> can also be a good pointer for clear labelling, as shown below.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Bad</span>
createDebugSetter(<span class="hljs-string">"aaa"</span>, setUser)
createDebugSetter(<span class="hljs-string">"1"</span>, setUser)

<span class="hljs-comment">// Good </span>
createDebugSetter(<span class="hljs-string">"UserContextProvider"</span>, setUser)
createDebugSetter(<span class="hljs-string">"From UserContextProvider"</span>, setUser)
<span class="hljs-comment">// Too long but can still work</span>
createDebugSetter(<span class="hljs-string">"From UserContextProvider in user-context.tsx file"</span>, setUser)
</code></pre>
<h3 id="heading-use-createdebugsetter-only-in-dev-mode">Use <code>createDebugSetter</code> Only in Dev Mode</h3>
<p>Using <code>createDebugSetter</code> only in a development environment can prevent many headaches. It’s not a good practice to mistakenly expose or log sensitive data in production. Also, logging in production can cause cluttering.</p>
<h3 id="heading-use-createdebugsetter-with-react-devtools">Use <code>createDebugSetter</code> with React DevTools</h3>
<p><strong>The</strong> <code>createDebugSetter</code> may not be enough for some complex bugs. You can use <code>createDebugSetter</code> and React DevTools for a more powerful/thorough debugging session<strong>.</strong> Although <code>createDebugSetter</code> cannot be directly integrated with React DevTools, it shows who triggered the update, whereas React DevTools displays what was re-rendered.</p>
<h3 id="heading-place-createdebugsetter-in-utils">Place <code>createDebugSetter</code> in <code>utils</code></h3>
<p><code>createDebugSetter</code> is a utility function, as I have mentioned above. This means you should place it in a <code>utils</code> folder so any team member can access and use it when needed across your React application.</p>
<h2 id="heading-things-to-avoid">Things to Avoid</h2>
<ol>
<li><p>Avoid using debug setters in production builds. While they are safe, unnecessary logs can slow down debugging tools. Also, sensitive credentials could be logged mistakenly. There are professional tools you can use, such as Sentry, that let you trace errors and debug your app effortlessly.</p>
</li>
<li><p>Don’t conditionally wrap setter functions within components. Perform wrapping outside renders to prevent the creation of new setter identities.</p>
</li>
<li><p>Don’t rely on it to replace proper state architecture. This tool helps identify issues, but doesn’t fix poor state design.</p>
</li>
<li><p>Don’t depend solely on console logs. Use it as part of a broader debugging workflow, not as the only strategy.</p>
</li>
</ol>
<h2 id="heading-bonus-how-to-convert-createdebugsetter-to-a-hook">Bonus: How to Convert <code>createDebugSetter</code> to a Hook</h2>
<h3 id="heading-converting-to-a-hook">Converting to a Hook</h3>
<p>The plain <code>createDebugSetter</code> function works, but it creates a new wrapper function on every render when used inside React components. By converting it into a custom hook with <code>useCallback</code>, we can ensure that the wrapper function maintains a stable reference across re-renders, preventing unnecessary performance overhead and making it safe to use in dependency arrays.</p>
<p>Here’s the hook version:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useDebugSetter</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">
  label: <span class="hljs-built_in">string</span>,
  setState: React.Dispatch&lt;React.SetStateAction&lt;T&gt;&gt;
</span>): <span class="hljs-title">React</span>.<span class="hljs-title">Dispatch</span>&lt;<span class="hljs-title">React</span>.<span class="hljs-title">SetStateAction</span>&lt;<span class="hljs-title">T</span>&gt;&gt; </span>{
  <span class="hljs-keyword">const</span> debugSetter = useCallback(
    <span class="hljs-function">(<span class="hljs-params">newValue: React.SetStateAction&lt;T&gt;</span>) =&gt;</span> {
      <span class="hljs-comment">// Only log in development</span>
      <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">import</span>.meta.env.PROD) {
        <span class="hljs-built_in">console</span>.groupCollapsed(
          <span class="hljs-string">`%c🔄 State Update: <span class="hljs-subst">${label}</span>`</span>,
          <span class="hljs-string">"color: #2fa; font-weight: bold;"</span>
        );
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"🆕 New value:"</span>, newValue);
        <span class="hljs-built_in">console</span>.trace(<span class="hljs-string">"📍 Update triggered from:"</span>);
        <span class="hljs-built_in">console</span>.groupEnd();
      }

      setState(newValue);
    },
    [label, setState]
  );

  <span class="hljs-comment">// In production, return the original setter (no wrapping overhead)</span>
  <span class="hljs-comment">// In development, return the debug wrapper</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">import</span>.meta.env.PROD ? setState : debugSetter;
}
</code></pre>
<h3 id="heading-how-the-hook-version-works">How the Hook Version Works</h3>
<p>The core difference between the <code>useDebugSetter</code> hook and <code>createDebugSetter</code> is that the function is wrapped in a <code>useCallback</code> that logs debug information before calling the original setter. Apart from this, all other components of the functions remain the same.</p>
<h3 id="heading-why-the-hook-version-is-better">Why the Hook Version is Better</h3>
<p>The hook version is superior for component usage because it leverages <code>useCallback</code> memoisation of the debug wrapper. This means the function reference stays the same across renders, avoiding potential re-render cascades when the setter is passed to child components or used in <code>useEffect</code> dependencies.</p>
<p>The plain function, by contrast, generates a brand new wrapper on every render, which can break React's optimisation strategies and cause subtle bugs. In production, both versions simply return the original setter, so there's no performance difference there – but in development, the hook prevents unnecessary work.</p>
<h3 id="heading-when-to-use-the-hook">When to Use the Hook</h3>
<p>Use <code>useDebugSetter</code> whenever you're inside a React component and need to debug state updates. This covers the vast majority of cases: wrapping <code>useState</code> setters, passing debug setters to child components, or including them in effect dependencies.</p>
<p>Only reach for the plain <code>createDebugSetter</code> function when you're working outside React components entirely, such as in utility modules, global stores, or configuration files where hooks can't be used. For day-to-day component debugging, the hook is the right choice.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Debugging React state doesn’t have to be guesswork. With a simple helper, you can instantly see what changed, who changed it, where the change originated, and how your app reached that state – all without touching your production environment.</p>
<p>This small utility function can save hours spent searching through your codebase, making you faster, more precise, and more confident in your React application’s behaviour.</p>
<p>Once you adopt this approach, you’ll never debug state the old way again. 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the tailwind-sidebar NPM Package in Your React and Next.js Apps ]]>
                </title>
                <description>
                    <![CDATA[ These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces. In this article, you’ll learn what the tailwind-sidebar NPM package is, how ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tailwind-sidebar-npm-package-in-react-nextjs/</link>
                <guid isPermaLink="false">6967d8278e420016a8b8a729</guid>
                
                    <category>
                        <![CDATA[ Tailwind sidebar ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tailwind CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm packages ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hitesh Chauhan ]]>
                </dc:creator>
                <pubDate>Wed, 14 Jan 2026 17:53:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768413200090/f31cbba6-9b9e-4719-bc07-13fe98049d52.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, developers are increasingly preferring utility-first CSS frameworks like Tailwind CSS to help them build fast, scalable, and highly customizable user interfaces.</p>
<p>In this article, you’ll learn what the <code>tailwind-sidebar</code> NPM package is, how it works internally, and how to install and configure it in a real project. We’ll walk through setting up a responsive sidebar using Tailwind CSS, explore its key features with practical examples, and see how you can customize and control the sidebar behavior to fit different layouts and screen sizes.</p>
<p>If you’re building a React or Next.js application and want a lightweight yet powerful sidebar solution, <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar"><strong>tailwind-sidebar</strong></a> is an excellent choice.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>Basic knowledge of JavaScript (ES6+)</p>
</li>
<li><p>Familiarity with React fundamentals (components, props, JSX)</p>
</li>
<li><p>Basic understanding of Next.js project structure and routing</p>
</li>
<li><p>Experience using npm or yarn for package installation</p>
</li>
<li><p>Working knowledge of Tailwind CSS</p>
</li>
</ul>
<h2 id="heading-introduction-to-the-tailwind-sidebar-npm-package">Introduction to the tailwind-sidebar NPM Package</h2>
<p>The tailwind-sidebar NPM package is a modern, developer-friendly sidebar component that’s built entirely with Tailwind CSS. It’s designed to help developers create responsive, customizable, and accessible sidebars without the overhead of heavy UI frameworks.</p>
<h3 id="heading-understanding-tailwind-sidebar">Understanding Tailwind Sidebar</h3>
<p><code>tailwind-sidebar</code> is a lightweight utility package designed to simplify building responsive sidebars using Tailwind CSS. Instead of manually handling sidebar state, transitions, and responsive behavior, the package provides a small JavaScript layer that works alongside Tailwind’s utility classes.</p>
<p>At its core, the package toggles Tailwind classes to control whether the sidebar is open or closed. This makes it framework-agnostic - it works with plain HTML, as well as frameworks like React, Vue, or Next.js, as long as Tailwind CSS is available.</p>
<p>Because it relies on Tailwind utilities rather than custom CSS, the sidebar stays easy to customize, extend, and maintain.</p>
<h3 id="heading-what-is-tailwind-css">What is Tailwind CSS?</h3>
<p>Tailwind CSS is a utility-first CSS framework that lets you build modern, responsive user interfaces directly in your markup. Instead of predefined components, Tailwind provides low-level utility classes that give you full design control without leaving your HTML.</p>
<p>Tailwind Sidebar is built on top of Tailwind CSS, offering a clean, flexible, and highly customizable sidebar solution for modern web applications.</p>
<h2 id="heading-why-choose-tailwind-sidebar">Why Choose Tailwind Sidebar?</h2>
<h3 id="heading-optimized-performance">Optimized Performance</h3>
<p>Tailwind Sidebar relies on utility classes instead of heavy JavaScript logic. This means that it delivers fast load times and smooth interactions, and is ideal for performance-critical applications.</p>
<h3 id="heading-developer-friendly">Developer-Friendly</h3>
<p>It doesn’t have any complex configuration or component APIs. If you know Tailwind CSS, you already know how to customize the sidebar.</p>
<h3 id="heading-easy-maintenance">Easy Maintenance</h3>
<p>With a simple and predictable structure, updates and custom changes are straightforward, making it suitable for both small projects and large-scale applications.</p>
<h3 id="heading-growing-tailwind-community">Growing Tailwind Community</h3>
<p>Tailwind CSS has a massive and active community. This means better tooling, regular updates, and a wealth of learning resources to support your development workflow.</p>
<h2 id="heading-how-to-get-started-with-tailwind-sidebar">How to Get Started with Tailwind Sidebar</h2>
<p>This section will walk you through installing and setting up <code>tailwind-sidebar</code> in a React and Next.js application. You’ll learn how to add a sidebar, create menus, add a logo, and customize navigation.</p>
<h3 id="heading-step-1-install-tailwind-sidebar">Step 1: Install tailwind-sidebar</h3>
<p>To begin, you’ll need to install <a target="_blank" href="https://www.npmjs.com/package/tailwind-sidebar">tailwind Sidebar</a> into your React and Next.js project. You can do this using either npm or yarn.</p>
<h4 id="heading-using-npm">Using npm:</h4>
<pre><code class="lang-javascript">npm i tailwind-sidebar
</code></pre>
<h4 id="heading-using-yarn">Using yarn:</h4>
<pre><code class="lang-javascript">yarn add tailwind-sidebar
</code></pre>
<p>This will add <code>tailwind-sidebar</code> and its dependencies to your project.</p>
<h3 id="heading-step-2-import-the-tailwind-sidebar-component">Step 2: Import the Tailwind Sidebar Component</h3>
<p>Once the package is installed, you can import the necessary components from tailwind-sidebar into your project. These components will allow you to customize the sidebar with menus, submenus, and even a logo.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {

  AMSidebar,

  AMMenuItem,

  AMMenu,

  AMSubmenu,

  AMLogo,

} <span class="hljs-keyword">from</span> <span class="hljs-string">"tailwind-sidebar"</span>;
</code></pre>
<h4 id="heading-adding-styles-to-tailwind-sidebar">Adding Styles to Tailwind Sidebar</h4>
<p>To use the default styles of tailwind-sidebar, you need to import its CSS file at the top of your project:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"tailwind-sidebar/styles.css"</span>;
</code></pre>
<h3 id="heading-step-3-routing-setup-for-react-and-nextjs">Step 3: Routing Setup for React and Next.Js</h3>
<p>To enable navigation inside <code>tailwind-sidebar</code> components like <code>AMMenuItem</code> or <code>AMLogo</code>, you need to pass a link component from either react-router or next/link using the component prop inside the corresponding component, like what’s shown in the below example:</p>
<p>If you're using <strong>React</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>

     <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart

      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
         <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/"  // Passing link to component for routing
          badge={true}
          badgeType="default"
          badgeColor={"bg-secondary"}
          isSelected={true}
        &gt;
          {/* text for your link */}
          Link Text
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">AMSidebar</span>&gt;</span></span>
</code></pre>
<p>And if you're using <strong>Next.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span>  Link  <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSidebar</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>"<span class="hljs-attr">270px</span>"}&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
        <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> // <span class="hljs-attr">Passing</span> <span class="hljs-attr">link</span> <span class="hljs-attr">to</span> <span class="hljs-attr">component</span> <span class="hljs-attr">for</span> <span class="hljs-attr">routing</span>
        <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>
      &gt;</span>
        Adminmart
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span>
        AdminMart
      <span class="hljs-tag">&lt;/<span class="hljs-name">Logo</span>&gt;</span></span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>
          <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>}
          link="/tes"
          component={Link}
          isSelected={true}
        &gt;
           Link Text {/* text for your link */}
        <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
    &lt;/AMSidebar&gt;
</code></pre>
<h3 id="heading-step-4-initializing-the-sidebar">Step 4: Initializing the Sidebar</h3>
<p>Now we’ll set up the <code>AMSidebar</code> component in your application. You can set the width of the sidebar using the <code>width</code> prop. Here’s a simple example:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt; <span class="hljs-comment">// pass the width you want your sidebar to have</span>

{<span class="hljs-comment">/* Sidebar Content Goes Here */</span>}

&lt;/AMSidebar&gt;
</code></pre>
<p>This initializes the sidebar with a width of <code>270px</code>. You can adjust this width based on your design requirements.</p>
<h3 id="heading-step-5-adding-a-logo-to-the-sidebar">Step 5: Adding a Logo to the Sidebar</h3>
<p>You can add a logo inside the sidebar by using the <code>AMLogo</code> component. To do so, you can provide an <code>img</code> prop to link to a CDN logo image. You can also make the logo clickable by passing a navigation link using the <code>component</code> and <code>href</code> props. Here’s how you can include a logo:</p>
<pre><code class="lang-javascript">&lt;AMSidebar width={<span class="hljs-string">"270px"</span>}&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMLogo</span> <span class="hljs-attr">img</span>=<span class="hljs-string">"https://adminmart.com/wp-content/uploads/2024/03/logo-admin-mart-news.png"</span>
<span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>
<span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> 
&gt;</span>        
Adminmart
<span class="hljs-tag">&lt;/<span class="hljs-name">AMLogo</span>&gt;</span></span>

&lt;/AMSidebar&gt;
</code></pre>
<p>In this example, we’ve added a logo from a CDN using the <code>img</code> prop, used the <code>component</code> prop to pass the <code>Link</code>, and set the navigation path to <code>(/)</code> homepage using the <code>href</code> prop and set the text “AdminMart” as the name of the application.</p>
<h3 id="heading-step-6-creating-a-menu-inside-the-sidebar">Step 6: Creating a Menu Inside the Sidebar</h3>
<p>Now let’s create a menu inside the sidebar using the <code>AMMenu</code> component. You can specify a submenu heading using the <code>subHeading</code> prop. Inside the <code>AMMenu</code>, you can add <code>AMMenuItem</code> components for each item.</p>
<p>You can also provide a <code>link</code> prop along with the <code>component</code> prop to the <code>AMMenuItem</code> to turn the item into a clickable link.</p>
<p>Here’s how you can structure the menu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar
 width={<span class="hljs-string">"270px"</span>}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"HOME"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/"</span>  <span class="hljs-attr">badge</span>=<span class="hljs-string">”true”</span>  <span class="hljs-attr">isSelected</span>=<span class="hljs-string">{true}</span> &gt;</span>
      Modern
    <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>eCommerce<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Analytical<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>We’ve added a <code>AMMenu</code> with the heading “HOME”.</p>
</li>
<li><p>The first <code>AMMenuItem</code> has a <code>link</code> prop, so clicking it will navigate to the homepage <code>(/)</code>.</p>
</li>
<li><p>The second and third <code>AMMenuItem</code> components are simple text items without links.</p>
</li>
</ul>
<p>You can use the <code>badge="true"</code> prop to indicate a badge or notification on the <strong>AMMenuItem</strong>. The <code>isSelected={true}</code> prop marks this menu item as currently selected or active (though you can customize this feature according to your needs).</p>
<h3 id="heading-step-7-adding-submenus-optional">Step 7: Adding Submenus (Optional)</h3>
<p>To add submenus inside the main menu, use the <code>AMSubmenu</code> component. The <code>AMSubmenu</code> can be nested inside the <code>AMMenu</code> component and contains its own set of <code>AMMenuItem</code>. Use the <code>title</code> prop to set the submenu heading</p>
<p>Here’s an example of adding a submenu:</p>
<pre><code class="lang-javascript">&lt;AMSidebar  width={<span class="hljs-string">"270px"</span>}&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SERVICES"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>  <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>   <span class="hljs-attr">link</span>=<span class="hljs-string">"/web-development"</span>&gt;</span>Web Development<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/seo-services"</span>&gt;</span>SEO Services<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Marketing"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/digital-marketing"</span>&gt;</span>Digital Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span>  <span class="hljs-attr">link</span>=<span class="hljs-string">"/content-marketing"</span>&gt;</span>Content Marketing<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>In this example:</p>
<ul>
<li><p>A submenu under the "Marketing" heading is added inside the "SERVICES" menu.</p>
</li>
<li><p>The submenu contains two <code>AMMenuItem</code> with links to different service pages.</p>
</li>
</ul>
<h2 id="heading-features-of-tailwind-sidebar">Features of Tailwind Sidebar:</h2>
<h3 id="heading-utility-first-amp-lightweight">Utility-First &amp; Lightweight</h3>
<p>Tailwind Sidebar is built entirely using Tailwind CSS utility classes. This means there’s no heavy JavaScript logic or extra styling framework, keeping your bundle size small and performance fast.</p>
<p><strong>Code Example:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"bg-gray-900 text-white"</span>&gt;
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
 &lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>bg-gray-900 text-white</code>: Applies a dark background and white text <em>directly</em> via Tailwind classes (no separate CSS file).</p>
</li>
<li><p><code>width="260px"</code>: The component prop sets the sidebar width. Here, it shows how Tailwind utilities combine with props to control layout.</p>
</li>
</ul>
<p>Because all spacing and colors are from Tailwind classes, you don’t need additional custom styles.</p>
<h3 id="heading-fully-responsive-design">Fully Responsive Design</h3>
<p>The sidebar adapts seamlessly to different screen sizes. Whether you’re building for desktop, tablet, or mobile, Tailwind Sidebar ensures a smooth and consistent navigation experience.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"260px"</span> className=<span class="hljs-string">"hidden md:block"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>hidden md:block</code>: Uses Tailwind <em>responsive utility classes</em> to hide the component (<code>hidden</code>) on mobile screens and show it starting at the <code>md</code> breakpoint (<code>md:block</code>).</li>
</ul>
<p>This pattern is Tailwind’s common way of controlling visibility across breakpoints without media queries.</p>
<h3 id="heading-highly-customizable">Highly Customizable</h3>
<p>Tailwind Sidebar allows full customization of colors, hover states, spacing, and typography directly through Tailwind classes. You can customize layout and animations – all without touching custom CSS files.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript"> &lt;AMMenuItem className=<span class="hljs-string">"hover:bg-blue-600 rounded-lg px-4 py-2”&gt;
      Analytics
  &lt;/AMMenuItem&gt;</span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>hover:bg-blue-600</code>: When you hover the menu item, the background changes to blue, purely via Tailwind.</p>
</li>
<li><p><code>rounded-lg</code>: Adds rounded corners.</p>
</li>
<li><p><code>px-4 py-2</code>: Controls horizontal (<code>px</code>) and vertical (<code>py</code>) padding to adjust spacing.</p>
</li>
</ul>
<p>Together, these utilities show how Tailwind gives control of design details directly at the HTML/JSX level.</p>
<h3 id="heading-integration-with-react-amp-nextjs">Integration with React &amp; Next.Js:</h3>
<p>Tailwind Sidebar seamlessly integrates with both React &amp; Next.js, offering a familiar and efficient development experience.</p>
<p>The sidebar works natively with both <strong>React Router</strong> and <strong>Next.js routing</strong> by passing the <code>Link</code> component.</p>
<pre><code class="lang-javascript">React Example
{
  <span class="hljs-comment">/* if you are using react then import link from  */</span>
}

<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
            Dashboard 
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>

Next.js Example
{
  <span class="hljs-comment">/* if you are using nextjs then import link from  */</span>
}

<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{Link}</span> <span class="hljs-attr">link</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>
         Dashboard
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>component={Link} link="/dashboard"</code>: Shows how you pass your framework’s routing component into <code>AMMenuItem</code>, turning it into a real navigational link.</li>
</ul>
<p>This means Tailwind Sidebar adapts to both React Router and Next.js routing without boilerplate.</p>
<h3 id="heading-menu-amp-submenu-support">Menu &amp; Submenu Support</h3>
<p>Organize your navigation with:</p>
<ul>
<li><p>Main menu items</p>
</li>
<li><p>Nested submenus</p>
</li>
</ul>
<p>This makes it easy to manage complex navigation structures while keeping the UI clean and intuitive.</p>
<p><strong>Example usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMMenu subHeading=<span class="hljs-string">"MANAGEMENT"</span>&gt;
             <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Users<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
                <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMSubmenu</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Reports"</span>&gt;</span>
                   <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Sales<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Revenue<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">AMSubmenu</span>&gt;</span></span>
&lt;/AMMenu&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>subHeading="SERVICES"</code>: Adds a labeled grouping for menu items.</p>
</li>
<li><p>The nested <code>&lt;AMSubmenu&gt;</code> demonstrates how nested navigation is rendered and structured in JSX.</p>
</li>
</ul>
<p>This example clearly shows hierarchical menus without additional CSS – structure and Tailwind classes handle layout.</p>
<h3 id="heading-icon-support">Icon Support</h3>
<p>The sidebar comes with built-in support for icons, allowing developers to enhance the visual appeal and usability of their application. Developers can use any icon library and provide the icon component.</p>
<p><strong>Example using lucide-react:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Home } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{18}</span> /&gt;</span>}&gt;
     Home
<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span></span>
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><p><code>icon={&lt;Home size={18} /&gt;}</code>: Here, an icon component is passed as a prop.</p>
</li>
<li><p>You control the size directly via the icon library (Lucide here) and Tailwind handles spacing/placement next to text.</p>
</li>
</ul>
<p>This illustrates how icons and text combine in the sidebar component.</p>
<h3 id="heading-smooth-transitions">Smooth Transitions</h3>
<p>Tailwind Sidebar provides built-in animation support through the animation prop. When enabled, menu items and submenus animate smoothly, improving the overall user experience without requiring custom CSS or JavaScript.</p>
<p><strong>Example Usage:</strong></p>
<pre><code class="lang-javascript">&lt;AMSidebar width=<span class="hljs-string">"270px"</span> animation={<span class="hljs-literal">true</span>}&gt;
           <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AMMenu</span> <span class="hljs-attr">subHeading</span>=<span class="hljs-string">"SETTINGS"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Profile<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">AMMenuItem</span>&gt;</span>Security<span class="hljs-tag">&lt;/<span class="hljs-name">AMMenuItem</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">AMMenu</span>&gt;</span></span>
&lt;/AMSidebar&gt;
</code></pre>
<p>What’s going on here:</p>
<ul>
<li><code>animation={true}</code>: Enables built-in animation support.</li>
</ul>
<p>The example shows how adding this prop triggers smooth transitions defined by the component itself (still relying on Tailwind utilities internally). You don’t have to write CSS keyframes or transition utilities manually.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>You have now successfully integrated a fully functional sidebar in your React and Next.js application using <code>tailwind-sidebar</code>. You can further customize the sidebar by:</p>
<ul>
<li><p>Modifying the width and design.</p>
</li>
<li><p>Adding more submenus, menu items, or icons.</p>
</li>
<li><p>Using links to navigate between pages.</p>
</li>
</ul>
<p>This setup provides a flexible, responsive, and easy-to-use sidebar, which is perfect for most web applications.</p>
<p>If you want to see how this kind of sidebar fits into a real dashboard layout, you can explore an open-source Tailwind CSS admin template at <a target="_blank" href="https://tailwind-admin.com/">https://tailwind-admin.com/</a>.</p>
<h3 id="heading-try-it-out">Try It Out:</h3>
<p>You can view the working demo of the tailwind-sidebar here: <a target="_blank" href="https://tailwind-admin-react-free.netlify.app/"><strong>View Demo</strong></a><strong>.</strong></p>
<p><strong>Note:</strong> in this tutorial, we utilized <code>lucide-react</code> to construct this sidebar. Feel free to choose an alternative library or use different icons based on your specific requirements.</p>
<h3 id="heading-other-sidebar-npm-packages">Other Sidebar NPM Packages</h3>
<p>You can also try <a target="_blank" href="https://www.npmjs.com/package/nextjs-tailwind-sidebar"><strong>Next.js Tailwind Sidebar</strong></a> – a simple and responsive sidebar built for Next.js, and <a target="_blank" href="https://www.npmjs.com/package/react-tailwind-sidebar"><strong>React Tailwind Sidebar</strong></a> – a lightweight Tailwind-based sidebar for React applications.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Figma MCP vs Kombai: Cloning the Front End from Figma with AI Tools ]]>
                </title>
                <description>
                    <![CDATA[ Frontend automation is moving fast. Tools like Figma MCP and Kombai can read design context and generate working UI code. I wanted to see what you actually get in practice, so I decided to compare them. Figma MCP exposes design metadata to AI clients... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/figma-mcp-vs-kombai-frontend-clone-comparison/</link>
                <guid isPermaLink="false">693703cb19e18638588e6285</guid>
                
                    <category>
                        <![CDATA[ #ai-tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Shrijal Acharya ]]>
                </dc:creator>
                <pubDate>Mon, 08 Dec 2025 16:58:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765205804241/295ef345-b776-458a-bcdb-f1157c9c185b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Frontend automation is moving fast. Tools like Figma MCP and Kombai can read design context and generate working UI code. I wanted to see what you actually get in practice, so I decided to compare them.</p>
<p>Figma MCP exposes design metadata to AI clients, while Kombai is a frontend-first agent that integrates with editors and existing stacks.</p>
<p>In this article, we’ll feed the same two Figma files into both tools, review how close the output is to the designs, and look at the code structure in a real editor.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-whats-the-deal">What's the Deal?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-meet-the-tools">Meet the Tools</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-kombaihttpskombaicom">Kombai</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-figma-mcphttpswwwfigmacomblogintroducing-figma-mcp-server">Figma MCP</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-comparison-with-figma">Frontend Comparison with Figma</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-1-simple-portfolio-design">Test 1: Simple Portfolio Design</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-figma-mcp">Figma MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-kombai">Kombai</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-test-2-complex-learning-dashboard">Test 2: Complex Learning Dashboard</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-figma-mcp-1">Figma MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-kombai-1">Kombai</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-you-should-know-before-using-these-tools">What You Should Know Before Using These Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-verdict-and-whats-next">Final Verdict and What's Next?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-whats-the-deal">What's the Deal?</h2>
<p>Cloning complex Figma designs by hand isn’t fun anymore, nor is writing your CSS line by line with exact precision.</p>
<p>And sure, you can attach a screenshot or whatever to GPT, but it often ends up with something that barely looks like your design. That's where Kombai or the Figma MCP come in.</p>
<p>They actually get your Figma design metadata and give you frontend code that's super close to the real thing.</p>
<p>So now, instead of spending hours rebuilding what's already in your design file, you can focus more on small tweaks and what actually matters.</p>
<h2 id="heading-meet-the-tools">Meet the Tools</h2>
<h3 id="heading-kombaihttpskombaicom"><a target="_blank" href="https://kombai.com/">Kombai</a></h3>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xu6m5bt4wrvrttn24121.png" alt="Kombai - AI Agent for Frontend" width="1920" height="1080" loading="lazy"></p>
<p>Kombai is an AI agent designed for frontend work. It takes input from Figma (like text, images, or your existing code), understands your stack, and converts it into clean, production-ready UI.</p>
<p>💡 It’s made specifically for frontend work, so you can expect it to be very good at that (unlike more generic tools like ChatGPT or Claude).</p>
<p>Kombai also handles large repositories easily. It doesn't just convert Figma designs into code. It actually understands your entire frontend codebase, even if it's huge.</p>
<p>So, even if you're working on a small side project or a very large production app, it can read, change, and write code that fits perfectly into your existing project.</p>
<p><strong>Note:</strong> Kombai isn’t just good at cloning Figma designs and writing clean code. It actually understands your whole repo, too. You can chat with it like GPT, but it already knows your frontend. It can help refactor code, clean things up, or make changes without ever touching your backend logic.</p>
<p>Pretty handy, right?</p>
<p>No backend code is ever touched, which ensures none of your business logic is mistakenly changed.</p>
<p>You can also add Kombai right inside your editor. It works with VSCode, Cursor, Windsurf, and Trae. Just grab it from the extension marketplace, launch it, and you’re ready to go.</p>
<p>With Kombai, you can:</p>
<ul>
<li><p>Turn Figma designs into code (React, HTML, CSS, and so on) using the component library your project already uses.</p>
</li>
<li><p>Work with a frontend-smart engine that understands 30+ libraries including Next.js, MUI, and Chakra UI.</p>
</li>
<li><p>Stay in your editor, follow your own conventions, and ship faster with good accuracy.</p>
</li>
<li><p>And most importantly, preview the changes in a sandbox so you can approve or reject the change before committing it to the files.</p>
</li>
</ul>
<p>You can be up and running in under a minute. Here are the steps to get started:</p>
<ul>
<li><p>Install the extension for your editor</p>
</li>
<li><p>Sign in and connect your project</p>
</li>
<li><p>Paste a Figma link or describe what you want to build</p>
</li>
<li><p>Review the output and commit your code</p>
</li>
</ul>
<p>You can find it in the Extension marketplace of your IDE.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764351498060/13a64c1f-f3f0-4bdd-9691-45cad38688de.png" alt="Kombai - Cursor marketplace extension" class="image--center mx-auto" width="1916" height="997" loading="lazy"></p>
<p>Now, using it is just as simple as accessing it from the left sidebar and having a chat similar to how you would with ChatGPT. (Optionally, you can add your tech stack, but Kombai handles it automatically.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764351618867/d748f56c-c173-428b-bc80-82f0822730bf.png" alt="Kombai open inside the Cursor editor, highlighting the user interface" class="image--center mx-auto" width="1916" height="997" loading="lazy"></p>
<p>Head to the <a target="_blank" href="https://docs.kombai.com/get-started/welcome">docs</a> to get started and find the setup for your editor.</p>
<p><strong>Pricing Note</strong>: Kombai is a paid tool but gives you a free plan with 300 credits per month, which is great for personal projects. For more advanced workflows, you can move up to the Pro plan or the Enterprise plan.</p>
<p>If you spend most of your time on the frontend, Kombai may be a good fit.</p>
<h3 id="heading-figma-mcphttpswwwfigmacomblogintroducing-figma-mcp-server"><a target="_blank" href="https://www.figma.com/blog/introducing-figma-mcp-server/">Figma MCP</a></h3>
<p>Figma MCP (Model Context Protocol) lets AI agents connect directly to your Figma files. It closes the gap between your designs and your AI tools by giving them structured access to real design data instead of relying on screenshots or rough estimates.</p>
<p>It works by exposing your design's node tree, styles, layout rules, and component structure so the model can build the UI with actual design data.</p>
<p>That means tools like Claude Code, Gemini CLI, Cursor, and VSCode can actually <strong>read your designs</strong>, including layers, components, colors, spacing, and text, and use that context to generate accurate, production-ready code or design updates.</p>
<p>With Figma MCP, you can:</p>
<ul>
<li><p>Let AI tools pull live data from your Figma files, so your code suggestions always match your latest designs</p>
</li>
<li><p>Ask your AI assistant to inspect components, layouts, or styles directly from Figma</p>
</li>
<li><p>Generate UI code that reflects real design and structure instead of guessing from an image</p>
</li>
<li><p>Keep designers and developers in sync without constantly sending files back and forth.</p>
</li>
</ul>
<p>Setting it up is simple:</p>
<ul>
<li><p>Run the Figma MCP server locally</p>
</li>
<li><p>Authorize your Figma workspace</p>
</li>
<li><p>Connect your editor or AI tool (Cursor, Claude Code, Gemini CLI, and so on)</p>
</li>
</ul>
<p>For this test, I'll be using Figma MCP inside Claude Code in Linux, and setting it up is as simple as adding the following JSON in your Claude configuration file <code>~/.claude.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"mcpServers"</span>: {
    <span class="hljs-attr">"Framelink MCP for Figma"</span>: {
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"npx"</span>,
      <span class="hljs-attr">"args"</span>: [<span class="hljs-string">"-y"</span>, <span class="hljs-string">"figma-developer-mcp"</span>, <span class="hljs-string">"--figma-api-key=YOUR-KEY"</span>, <span class="hljs-string">"--stdio"</span>]
    }
  }
}
</code></pre>
<p>For Windows users:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"mcpServers"</span>: {
    <span class="hljs-attr">"Framelink MCP for Figma"</span>: {
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"cmd"</span>,
      <span class="hljs-attr">"args"</span>: [<span class="hljs-string">"/c"</span>, <span class="hljs-string">"npx"</span>, <span class="hljs-string">"-y"</span>, <span class="hljs-string">"figma-developer-mcp"</span>, <span class="hljs-string">"--figma-api-key=YOUR-KEY"</span>, <span class="hljs-string">"--stdio"</span>]
    }
  }
}
</code></pre>
<p><strong>Pricing Note</strong>: To use Figma MCP, you need to have a paid Figma plan, either Professional, Organization, or Enterprise. But there's a community-maintained open-source MCP server, <a target="_blank" href="https://github.com/GLips/Figma-Context-MCP">Figma-Context-MCP</a>, that you can test out for free – which I'll be using for this test.</p>
<p>Once it’s running, any MCP-supported tool can understand your design files, making frontend coding development much more accurate.</p>
<p>Check the <a target="_blank" href="https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server">Figma MCP Guide</a> to get started.</p>
<h2 id="heading-frontend-comparison-with-figma">Frontend Comparison with Figma</h2>
<p>For this test, we'll be comparing Kombai with Figma MCP using two Figma designs: one is a simple portfolio design, and the other is a more complex learner dashboard.</p>
<p><strong>NOTE:</strong> For this test with Figma MCP, I'll be using Sonnet 4, which, in my experience, has been the best model for coding the frontend. I've also tested with the recent GPT-5 and Opus 4, but Sonnet 4 seems to be the best for frontend work. If you want to try other models, feel free to do so and see if you notice much difference in the results.</p>
<blockquote>
<p>💁 <strong>Prompt</strong>: Clone this Figma design from this Figma frame link attached. Write clean, maintainable, and responsive code that matches the design closely. Keep components simple, reusable, and production-ready.</p>
</blockquote>
<p><strong>Quick note about the videos in the next section:</strong> The demo recordings are pretty long because I kept them raw. The idea is to show how the tools behave in real time. If you only care about the final output, feel free to skip to the end of each video.</p>
<h2 id="heading-test-1-simple-portfolio-design">Test 1: Simple Portfolio Design</h2>
<p>Let's start with a simpler design that doesn't have much going on in the UI.</p>
<p>You can find the Figma design template here: <a target="_blank" href="https://www.figma.com/design/ikqgqDYKWsM6OXwdz1IFCp/Personal-Portfolio-Website-Template--Community---Copy-?node-id=0-1&amp;t=HBdIdagaA7tSxpoV-1">Personal Portfolio Template</a></p>
<h3 id="heading-figma-mcp">Figma MCP</h3>
<p>Here's the response from Figma MCP:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/fyj0LT4GDVQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>This is pretty decent. The overall UI looks good, and the colors and fonts are all accurate. The biggest visual issues are with the hero image and a few icon placements, which are a bit off compared to the original Figma file.</p>
<p>The overall implementation took just about 5 minutes of coding and achieved this entire result in one go, as you see in the video demo. The time it takes isn't really dependent on the MCP itself but mostly on the model, so the timings will vary based on the model you choose to work with. The timing is something you can simply ignore here.</p>
<p>The whole page is split into sensible components (<code>Header</code>, <code>Hero</code>, <code>Projects</code>, <code>ProjectCard</code>, <code>Footer</code>) and composed in a clean <code>page.tsx</code>.</p>
<pre><code class="lang-tsx">export default function Home() {
  return (
    &lt;div className="min-h-screen bg-bg-gray"&gt;
      &lt;Header /&gt;
      &lt;main&gt;
        &lt;Hero /&gt;
        &lt;Projects /&gt;
      &lt;/main&gt;
      &lt;Footer /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>That is a nice, readable starting point for a Next app.</p>
<p>You can find the code it generated <a target="_blank" href="https://gist.github.com/shricodev/285295e78ebc41db37d0b65277abbe09">here</a>.</p>
<p>But here are some issues I noticed right away:</p>
<ol>
<li>The hero decoration is positioned with pretty brittle absolute values:</li>
</ol>
<pre><code class="lang-tsx">&lt;div className="hidden lg:block absolute right-0 top-0 w-[720px] h-[629px] pointer-events-none"&gt;
  &lt;div className="relative w-full h-full"&gt;
    &lt;div className="absolute left-0 top-0 w-[777px] h-[877px] -translate-y-[248px] bg-brand-yellow" /&gt;
    &lt;div className="absolute left-0 top-0 w-full h-full"&gt;
      &lt;img
        src="/images/hero-decoration-58b6e4.png"
        alt="Decorative"
        className="w-full h-full object-cover"
      /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>This achieves the desired look at one screen size, but it can easily become misaligned when you resize. When compared side by side with the Figma frame, the hero image and yellow shape do not align as they should.</p>
<ol start="2">
<li>Fixed Header</li>
</ol>
<p>For a simple portfolio page with a short hero, a fixed header is not always worth the complexity.</p>
<p>The problem here is that since the header is fixed to the top, the rest of the content also starts from the top. On smaller devices, this might cover parts of the content when scrolling.</p>
<pre><code class="lang-tsx">return (
  &lt;header className="fixed top-0 left-0 right-0 bg-bg-gray z-50 h-14"&gt;
    {/* ... */}
    &lt;button
      onClick={() =&gt; scrollToSection("about")}
      className="font-raleway ..."
    &gt;
      About
    &lt;/button&gt;
    {/* more buttons */}
  &lt;/header&gt;
);
</code></pre>
<p>This is still a great head start, though it is not quite at the level where I would add it to a production repo without tidying up some of the layout changes.</p>
<h3 id="heading-kombai">Kombai</h3>
<p>Here's the response from Kombai:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/s-ocABi-V0o" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>Visually, this one is extremely close to the Figma template. Apart from the hero image being slightly off from the Figma design, I see no other differences. It actually feels like the design is exactly copy-pasted.</p>
<p>Notice that the font, images, and icons are exactly the same, which to me is insane.</p>
<p>You can find the code it generated <a target="_blank" href="https://gist.github.com/shricodev/41fdf0596f312573e0efd44a30b5b36b">here</a>.</p>
<p>Here are the specific things it does better in this simple example.</p>
<ol>
<li>It mirrors the Figma typography and colors as real tokens</li>
</ol>
<p>Kombai sets up <code>globals.css</code> with Figma-like tokens and even defines utility classes for the text styles:</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-comment">/* ... */</span>
}

<span class="hljs-keyword">@theme</span> inline {
  <span class="hljs-comment">/* ... */</span>
}

<span class="hljs-keyword">@utility</span> text-heading-large {
  <span class="hljs-comment">/* ... */</span>
}

<span class="hljs-keyword">@utility</span> text-subtitle {
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>That is very similar to how a designer would set up styles in Figma, and it means you can reuse these utilities in new screens instead of retyping Tailwind font sizes everywhere.</p>
<ol start="2">
<li>Components are cleaner and more reusable</li>
</ol>
<p>All the other components, like <code>Hero</code> or some smaller button components, use the same styles set up in <code>styles.css</code>.</p>
<pre><code class="lang-tsx">const baseClasses =
  "text-button px-6 py-3 rounded-sm transition-all hover:opacity-90";

const variantClasses =
  variant === "primary"
    ? "bg-(--primary-yellow) text-(--foreground)"
    : "bg-transparent border-2 border-(--foreground) text-(--foreground) hover:bg-(--foreground) hover:text-white";
</code></pre>
<p>The footer pulls each icon into its own component:</p>
<pre><code class="lang-tsx">import InstagramIcon from "./icons/InstagramIcon";
import LinkedInIcon from "./icons/LinkedInIcon";
import MailIcon from "./icons/MailIcon";
</code></pre>
<p>In practice, that means if the designer swaps the mail icon or tweaks the size, there is a single place to update it.</p>
<p>So for this simple test, Kombai’s output is both closer to the visual design and a bit nicer structurally for a real project. I would still tweak naming and some minor details, but I would happily keep most of this as is. How crazy is that?</p>
<h2 id="heading-test-2-complex-learner-dashboard">Test 2: Complex Learner Dashboard</h2>
<p>So, for the second one, let's create a slightly more complex design with a lot happening in the UI.</p>
<p>You can find the Figma design template here: <a target="_blank" href="https://www.figma.com/design/hATPCahjQRzz0dXao2QH1U/Dashboard---Online-Learning-Profile--Community-?node-id=10-1626&amp;t=sn9rVXVzXlzzdusd-0">Learning Dashboard</a></p>
<h3 id="heading-figma-mcp-1">Figma MCP</h3>
<p>Here's the response from Figma MCP:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/gyZX9s1S0EA" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>This is good, considering the complexity of the design. It’s able to put all the images and assets in place. This is much better than what I expected. But there's a slight inconsistency in the placement of images between the original design and the implementation, as you can see for yourself.</p>
<p>If I compare the time, this got it done super fast, in just about <strong>8 minutes</strong>, whereas Kombai took over 15 minutes to get it done (but with a better result).</p>
<p>You can find the code it generated <a target="_blank" href="https://gist.github.com/shricodev/a15cbff76f4256a20fa098d69f5b4661">here</a>.</p>
<p>Here's what I like and dislike about a few things it did here:</p>
<ol>
<li>Great smaller components, but everything is still quite page-centric</li>
</ol>
<p>It does break things into logical components like <code>Sidebar</code>, <code>Input</code>, <code>Button</code>, <code>StatCard</code>, <code>CourseCard</code>, and <code>Icons</code>. The main page then stitches them together:</p>
<pre><code class="lang-tsx">export default function Home() {
  const mentors = [
    {
      id: 1,
      name: "John Doe",
      subject: "UI/UX Design",
      color: "bg-purple-500",
    },
    // ...
  ];

  return (
    &lt;div className="flex items-center gap-8 w-full max-w-[1440px] h-[933px] bg-white rounded-[20px] mx-auto overflow-hidden"&gt;
      {/* Sidebar */}
      &lt;Sidebar /&gt;

      {/* Main content */}
      &lt;main className="flex flex-col items-center gap-6 pt-5 pb-0 flex-1 h-full overflow-hidden"&gt;
        {/* Search, hero, cards, mentor table */}
      &lt;/main&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>The separation into components is nice, but everything is still wired directly inside one big page component with inline mock data. For a real app, I would want that data in its own module, ideally typed, so it is not mixed with layout logic.</p>
<ol start="2">
<li>Hard-coded dimensions tied to the original frame</li>
</ol>
<p>The outer container is pinned to a specific height:</p>
<pre><code class="lang-tsx">&lt;div className="flex items-center gap-8 w-full max-w-[1440px] h-[933px] bg-white rounded-[20px] mx-auto overflow-hidden"&gt;
</code></pre>
<p>That’s fine if you are literally recreating a 1440 by 933 frame for a screenshot, but in a live app, it means:</p>
<ul>
<li><p>You get weird empty space on taller screens.</p>
</li>
<li><p>Anything that grows vertically (longer course titles, more mentors) will either overflow or get clipped.</p>
</li>
</ul>
<p>The hero banner has the same kind of pixel-exact positioning:</p>
<pre><code class="lang-tsx">&lt;div className="relative w-full h-[181px] bg-primary rounded-[20px] overflow-hidden"&gt;
  &lt;Image
    src="/images/star1.svg"
    alt="Star"
    width={80}
    height={80}
    className="absolute top-[45px] left/[683px] opacity-25"
  /&gt;
  {/* four more star images with fixed top/left */}
&lt;/div&gt;
</code></pre>
<p>This is great for matching the specific Figma design, but as soon as the width changes, these positions stop lining up perfectly.</p>
<p>So overall, I would call this result surprisingly good for a single prompt, but a bit rigid and template-like once you start thinking about real data and using it in production.</p>
<h3 id="heading-kombai-1">Kombai</h3>
<p>Here's the response from Kombai:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/b8C3AVyz7rE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>You will see in the video that I had to fix a small error with an extra prompt, but after that, it produced a fully working dashboard. The visual match is very strong, given how complex the layout is.</p>
<p>You can find the code it generated <a target="_blank" href="https://gist.github.com/shricodev/bc86951ed09c2b3ef6500cc40f3c0b0b">here</a>.</p>
<p>Here is what stands out compared to the MCP output.</p>
<ol>
<li>It treats the Figma file like a real product, not just a static screen.</li>
</ol>
<p>Instead of wiring everything in a single page with inline arrays, Kombai creates proper domain types and a <code>mock-data.ts</code>:</p>
<pre><code class="lang-tsx">import { UserProfile, Friend, Course, ProgressCard, Mentor } from "./types";

export const courses: Course[] = [
  {
    id: "1",
    title: "Beginner's Guide to becoming a professional frontend developer",
    category: "Frontend",
    thumbnail: "/images/course-coding.jpg",
    instructor: {
      name: "Prashant Kumar singh",
      role: "software Developer",
      avatar: "/images/avatar-prashant.jpg",
    },
  },
  // ...
];
</code></pre>
<p>That looks much closer to what you would expect in a production codebase: clear types, data separated from layout, and a page component that just composes everything.</p>
<ol start="2">
<li>Better mapping of the smaller UI pieces</li>
</ol>
<p>The course card is similar to the MCP one, but now it is fully driven by a <code>Course</code> object:</p>
<pre><code class="lang-tsx">export function CourseCard({ course }: { course: Course }) {
  return (
    &lt;div className="flex flex-col gap-2.5 rounded-[20px] bg-white shadow-[0px_14px_42px_rgba(8,15,52,0.06)] overflow-hidden min-w-[268px]"&gt;
      &lt;div className="relative"&gt;
        &lt;Image
          src={course.thumbnail}
          alt={course.title}
          width={244}
          height={113}
          className="w-full h-28 object-cover rounded-t-xl"
        /&gt;
        &lt;button className="absolute top-3 right-3 w-2 h-2 bg-white rounded-full" /&gt;
      &lt;/div&gt;
      &lt;div className="px-3 pb-4 flex flex-col gap-2.5"&gt;
        &lt;span className="text-[8px] font-normal uppercase text-primary px-3 py-1 bg-purple-50 rounded w-fit"&gt;
          {course.category}
        &lt;/span&gt;
        &lt;p className="text-[14px] font-medium text-text-primary leading-tight"&gt;
          {course.title}
        &lt;/p&gt;
        &lt;div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden"&gt;
          &lt;div
            className="h-full bg-primary rounded-full"
            style={{ width: "60%" }}
          /&gt;
        &lt;/div&gt;
        {/* instructor avatar and name */}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>The structure and text styles are very close to the original design, and because the card is fully data-driven, you can plug in real data without touching the JSX.</p>
<ol start="3">
<li>Design tokens and typography utilities again</li>
</ol>
<p>Just like in the portfolio example, Kombai sets up a proper token layer for the dashboard:</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-comment">/* ... */</span>
}

<span class="hljs-keyword">@utility</span> heading-section {
  <span class="hljs-comment">/* ... */</span>
}

<span class="hljs-keyword">@utility</span> text-caption {
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>The components then reuse these utilities, which keeps the code close to the design system instead of scattering font sizes and colors everywhere.</p>
<ol start="4">
<li>Things I would still tweak</li>
</ol>
<p>It is not perfect:</p>
<ul>
<li><p>The Next <code>layout.tsx</code> is still using the default Geist fonts and “Create Next App” metadata, so you would want to align that with the Inter font and real app title.</p>
</li>
<li><p>Some of the mock data has inconsistent casing in names and roles, which you would clean up in a real project.</p>
</li>
<li><p>The play button on the course card is just a white dot button for now, so you would still plug in the real icon.</p>
</li>
</ul>
<p>But even with those issues, it is very close to something I would actually keep in a production repo after a quick pass.</p>
<p>Now, this is not as perfect as the previous Kombai implementation, and it did not run into errors. But considering how complex this design is, with multiple different cards with images and all, it's still really impressive to me.</p>
<p>For this one, it took a bit longer to code, but in my opinion, the extra time was worth it.</p>
<p>Imagine you're building something similar and get a response this good already. Then it's not that big of a deal to iterate a little bit, right? You don't have to start from scratch. Just make a few changes if required, and you're done.</p>
<h2 id="heading-what-you-should-know-before-using-these-tools">What You Should Know Before Using These Tools</h2>
<p>As good as these tools are, they’re not something you can just trust blindly. They’ll get you off to a solid start, but you’ll still need to tweak a few things before calling it production-ready.</p>
<p><strong>Kombai</strong> does a great job cloning Figma designs and writing clean, modular code. It breaks components into smaller files and generally follows good structure.</p>
<p>The only issue I noticed is that it sometimes slips on naming conventions. Since it scans your entire codebase to stay consistent with your setup, it can be a bit slower to generate code, but that’s also what makes it smarter. You’re not just getting a Figma cloner, you’re getting an assistant that actually understands your frontend.</p>
<p><strong>Figma MCP</strong> is fast and does a decent job matching the UI, although the results depend a lot on the model you use for generation. If your main goal is to clone Figma designs quickly and you don’t mind refining the output, it’s a good option.</p>
<p>In short, both tools can save you a ton of time, but they’re not plug-and-play replacements for a frontend workflow. Treat them as part of your toolkit, and you’ll get the best results.</p>
<h2 id="heading-final-verdict-and-whats-next">Final Verdict, and What's Next?</h2>
<p>Now that you’ve got the gist of what these tools can do, go ahead and try them out. You can turn your Figma designs into working frontends in just a few minutes without all the endless play with CSS.</p>
<p>To sum up, here’s the quick rundown:</p>
<ul>
<li><p>If you want production-ready code that actually looks like your Figma design and you mostly live in VS Code, Cursor, or any GUI IDE, go with Kombai. It nails the details and even understands your codebase, which is completely missing in Figma MCP.</p>
</li>
<li><p>If you just want to clone a Figma design quickly and don’t mind if things are <em>slightly</em> off, Figma MCP is totally fine. It gets the job done pretty well.</p>
</li>
</ul>
<p>Basically, choose Kombai if you care about precision and code quality with codebase understanding.</p>
<p>Choose Figma MCP if you want something quick, that <em>works</em> and looks decent enough. 🤷‍♂️</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So, what do you think? Pretty cool, right? This was a fun little experiment to see how close tools like Figma MCP and Kombai can get to cloning real frontends straight from Figma.</p>
<p>If you’re into building frontends and want to save yourself a few hours of CSS pain, definitely give them a try. Just don’t expect them to be perfect in one try – their output still needs review and likely a little refining.</p>
<p>That’s all for this one. Thank you for reading! ✌️</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Front-End Developers Should Understand UI/UX Design ]]>
                </title>
                <description>
                    <![CDATA[ When users interact with a website or application, the first thing they notice isn’t the code. Instead, it’s the design and experience. Smooth navigation, intuitive layouts, and visually appealing interfaces are what keep users engaged. Behind these ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-front-end-developers-should-understand-uiux-design/</link>
                <guid isPermaLink="false">68c4087abba5a7e638ae4cd6</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI UX ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Asfak Ahmed ]]>
                </dc:creator>
                <pubDate>Fri, 12 Sep 2025 11:48:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757677089930/07115f35-ba9e-452a-bf95-3a96de7a5d24.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When users interact with a website or application, the first thing they notice isn’t the code. Instead, it’s the design and experience. Smooth navigation, intuitive layouts, and visually appealing interfaces are what keep users engaged.</p>
<p>Behind these experiences, front-end developers play a pivotal role. They don’t just turn mockups into functioning code – they shape the bridge between design and functionality, making user experiences come to life.</p>
<p>To do this effectively, front-end developers need to understand UI (User Interface) and UX (User Experience) design.</p>
<ul>
<li><p><strong>UI</strong> focuses on the look and feel of the product, including colors, typography, buttons, layouts, and visual consistency.</p>
</li>
<li><p><strong>UX</strong> focuses on how users interact with the product, including ease of use, accessibility, responsiveness, and overall satisfaction.</p>
</li>
</ul>
<p>In today’s competitive digital landscape, knowing only code isn’t enough. Let’s explore why UI/UX knowledge is crucial for front-end developers and how it impacts both the developer’s career and the end-user experience.</p>
<h2 id="heading-table-of-contents"><strong>Table of Content</strong>s:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-front-end-devs-should-understand-uxui">Why Front-End Devs Should Understand UX/UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-uiux-principles-every-front-end-developer-should-know">Key UI/UX Principles Every Front-End Developer Should Know</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-of-uiux-knowledge-for-front-end-developers">Benefits of UI/UX Knowledge for Front-End Developers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-developers-can-learn-uiux">How Developers Can Learn UI/UX</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-to-keep-in-mind-about-uxui">What to Keep in Mind about UX/UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-front-end-devs-should-understand-uxui">Why Front-End Devs Should Understand UX/UI</h2>
<p>Front-end development is not just about writing clean code. It’s also about creating experiences. A developer might know every JavaScript framework and CSS trick, but if they don’t understand UI (User Interface) and UX (User Experience) principles, their work risks feeling clunky, confusing, or unappealing.</p>
<h3 id="heading-why-uiux-knowledge-matters">Why UI/UX Knowledge Matters</h3>
<ol>
<li><p><strong>Bridging the gap between design and development:</strong> Front-end developers act as the bridge between design mockups and functional applications. By understanding UI/UX principles, developers can implement designs more accurately, while also identifying potential improvements in usability or accessibility.</p>
</li>
<li><p><strong>Improving usability:</strong> UI/UX knowledge helps developers think beyond visuals. They consider user flow, navigation, responsiveness, and accessibility. For example, a button’s size, contrast, and placement affect how easily users interact with it.</p>
</li>
<li><p><strong>Creating consistent experiences:</strong> Without consistency, users get frustrated. Developers who understand design systems and UX patterns ensure that spacing, typography, colors, and interactions feel uniform across the app.</p>
</li>
<li><p><strong>Boosting collaboration with designers:</strong> Developers who know UI/UX can speak the same language as designers. This leads to smoother handoffs, fewer revisions, and faster delivery. Instead of just “coding what’s given,” they can proactively suggest alternatives when something won’t scale well.</p>
</li>
<li><p><strong>Improving user satisfaction and business goals:</strong> Ultimately, great UI/UX translates to happy users. An application that is visually appealing, easy to navigate, and responsive encourages engagement, reduces bounce rates, and drives conversions.</p>
</li>
</ol>
<h2 id="heading-key-uiux-principles-every-front-end-developer-should-know">Key UI/UX Principles Every Front-End Developer Should Know</h2>
<p>A website or application may be powered by complex logic in the background, but what users truly experience is the interface. This is where understanding UI/UX principles becomes essential. Let’s explore some of the core ideas every developer should master.</p>
<h3 id="heading-consistency-builds-trust">Consistency Builds Trust</h3>
<p>One of the most fundamental principles of good UI/UX is consistency. When layouts, typography, buttons, and navigation behave the same way across an application, users instantly feel more confident.</p>
<p>Imagine a scenario where a “Sign Up” button looks completely different on each page – one rounded, another outlined, another square. This inconsistency doesn’t just look unprofessional, it breaks trust and forces users to pause and think. A consistent interface, on the other hand, feels reliable and predictable.</p>
<p><strong>Example:</strong></p>
<p>The image below compares two sets of buttons to highlight the importance of design consistency. On the left, the "Consistent Buttons" example shows "Sign Up" and "Login" buttons that look identical in color, shape, and style, making the user interface appear clear and professional. On the right, the "Inconsistent Buttons" example displays the same buttons but with different colors, shapes, and text styles, which can make the interface confusing and visually unappealing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757048948178/8d74c12b-f1ac-442a-81e5-906c5b0dce2c.png" alt="Consistency Builds Trust example image" class="image--center mx-auto" width="1363" height="180" loading="lazy"></p>
<h3 id="heading-guiding-users-with-visual-hierarchy">Guiding Users with Visual Hierarchy</h3>
<p>Users rarely read web pages word by word. Instead, they scan for what matters. That’s why visual hierarchy is so important. By adjusting size, contrast, color, and spacing, developers can subtly guide attention. Headlines should naturally stand out from body text, and primary actions like “Buy Now” or “Sign Up” should be visually stronger than secondary options.</p>
<p>A great example of this is a pricing page where the recommended plan is highlighted with a brighter color and a slightly larger card, drawing the eye instantly.</p>
<p><strong>Example:</strong></p>
<p>The image below shows three pricing plans Base, Pro, and Enterprise, with the Pro plan visually highlighted in the center. The design uses size, color contrast, and placement to guide users attention toward the Pro plan, illustrating the concept of visual hierarchy for easier decision-making.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757049059813/a2363890-44f6-4394-b307-db62fd3b38b0.png" alt="Guiding Users with Visual Hierarchy example image" class="image--center mx-auto" width="1130" height="644" loading="lazy"></p>
<h3 id="heading-accessibility-for-everyone">Accessibility for Everyone</h3>
<p>A beautiful interface is meaningless if it’s not usable by all. <a target="_blank" href="https://www.freecodecamp.org/news/why-accessibility-matters-in-ui-ux-design/">Accessibility</a> ensures that people with disabilities can interact with your site just as easily as others. This means paying attention to color contrast so text remains readable, using semantic HTML so screen readers can interpret content, and adding descriptive alt text to images.</p>
<p>Even small improvements – like ensuring forms have proper labels – can transform usability. A login form without labels may look clean, but it excludes users who rely on assistive technology.</p>
<p><strong>Example:</strong></p>
<p>The picture below compares two login forms to show the difference between poor and good form design. The "Bad Form" on the left lacks clear labels for its input fields and has a dull, hard-to-see login button, making it less user-friendly. The "Good Form" on the right uses visible labels for each field and a prominent, easy-to-find login button, which improves clarity and usability for users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757049101402/de1a3424-d599-4243-b760-a9114b9d99f0.png" alt="Accessibility for Everyone example image" class="image--center mx-auto" width="1370" height="390" loading="lazy"></p>
<h3 id="heading-designing-for-all-screens">Designing for All Screens</h3>
<p>With most traffic now coming from mobile devices, responsive design is no longer optional. A layout that looks perfect on desktop but breaks on mobile will drive users away.</p>
<p>Mobile-first thinking encourages developers to start small and then expand layouts for larger screens. This ensures that buttons are touch-friendly, text remains readable, and navigation adapts naturally. A dashboard that stacks neatly into a single column on mobile but expands gracefully on desktop provides a consistent experience everywhere.</p>
<p><strong>Example:</strong></p>
<p>The image below shows how pricing plans are tailored for various screen sizes. On the left, the "Desktop Design" displays multiple plans side by side, allowing easy comparison. On the right, the "Mobile Design" stacks the plans vertically, making them easy to read and navigate on smaller screens.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757049871365/4a5b8bfb-5a83-415b-9ec2-55802abb9fb9.png" alt="Designing for All Screens example image" class="image--center mx-auto" width="1036" height="552" loading="lazy"></p>
<h3 id="heading-clear-feedback-and-interaction">Clear Feedback and Interaction</h3>
<p>Users should never be left guessing. Every interaction must provide feedback – whether that’s a hover animation on a button, a loading indicator when submitting a form, or a success/error message after an action.</p>
<p>Consider a “Submit” button: if it does nothing after being clicked, the user may think the site is broken. But if it shows a spinner followed by a success message, the experience feels reassuring and complete.</p>
<p><strong>Example:</strong></p>
<p>The gif below demonstrates clear user feedback during a button interaction. When the "Submit" button is clicked, a loading spinner appears to show that the action is being processed, followed by a success message once the process is complete. This ensures users know their action is registered and provides reassurance, preventing confusion or uncertainty.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757076411317/42e940e9-cf3d-4155-be43-94217f135468.gif" alt="Clear Feedback and Interaction example gif video" class="image--center mx-auto" width="1920" height="920" loading="lazy"></p>
<h3 id="heading-designing-with-the-user-in-mind">Designing with the User in Mind</h3>
<p>At the heart of UI/UX is empathy. A developer must always ask: “Is this intuitive for someone seeing it or using it for the first time?”</p>
<p>Placing the search bar at the top of a page makes sense because that’s where users expect it. Hiding it deep inside a menu might seem clever, but it often frustrates users. The best interfaces are designed around user expectations, not developer preferences.</p>
<p><strong>Example:</strong></p>
<p>The GIF image below demonstrates the impact of user-centered design in UI/UX. The "Good UX" example places the search bar at the top of the page, making it immediately visible and easy for users to find. In contrast, the "Bad UX" example hides the search bar inside a menu, which makes it harder to access and can frustrate users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757076951279/47559d0f-7603-457f-be8f-f99797cf6ade.gif" alt="Designing with the User in Mind example gif video" class="image--center mx-auto" width="1920" height="904" loading="lazy"></p>
<h2 id="heading-benefits-of-uiux-knowledge-for-front-end-developers">Benefits of UI/UX Knowledge for Front-End Developers</h2>
<p>Front-end developers often see themselves as the builders, the ones who transform design files into functional web pages and applications. But those who also understand UI and UX principles unlock a significant advantage. Their work doesn’t just “look like the design” – it feels intuitive, efficient, and enjoyable for the end user.</p>
<h3 id="heading-bridging-the-gap-between-design-and-code">Bridging the Gap Between Design and Code</h3>
<p>Designers create mockups with a vision in mind, but many of those details get lost when moving into code. A developer with UI/UX knowledge understands why spacing is generous, why a button changes color on hover, or <em>why</em> a particular layout improves navigation.</p>
<p>Instead of copying pixels mechanically, they preserve the intent of the design. Without this understanding, designs may look “close enough” but feel awkward in practice.</p>
<p><strong>Example:</strong></p>
<p>The image below compares bad versus good UI design. The left side shows a messy header with mismatched buttons that have different shapes, sizes, and colors, demonstrating poor design consistency. The right side shows the same header redesigned properly with uniform blue buttons that are consistently sized and aligned, creating a much cleaner and more professional appearance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757050797636/a9559e8b-ef9e-4aa9-9c79-08b1a986e0a2.png" alt="Bridging the Gap Between Design and Code example imge" class="image--center mx-auto" width="1895" height="331" loading="lazy"></p>
<h3 id="heading-building-for-usability-not-just-functionality">Building for Usability, Not Just Functionality</h3>
<p>Functionality answers the question: <em>Does this work?</em> Usability asks: <em>Does this feel natural to use?</em> Developers who understand UI/UX instinctively add helpful touches like loading indicators, clear error messages, and accessible labels.</p>
<p>Without this mindset, users encounter confusing flows, tiny buttons, hidden actions, or forms with no feedback. The product works, technically, but it doesn’t <em>work well.</em></p>
<p><strong>Example:</strong></p>
<p>The image below shows a login form comparison between poor and improved UX design. The left side demonstrates bad UX with empty input fields that have no placeholder text, a bland gray login button, and unclear help text that says "Forgot password? Not obvious." The right side shows the improved version with helpful placeholder text like "Enter your email" and "Enter your password," a prominent blue login button that's more visually appealing, and clearer, actionable help text that says "Forgot password? Click here to reset."</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757050840166/34aacf33-3dec-446f-bada-22b8a9c14f2d.png" alt="Building for Usability, Not Just Functionality example image" class="image--center mx-auto" width="1903" height="493" loading="lazy"></p>
<h3 id="heading-faster-collaboration-and-fewer-iterations">Faster Collaboration and Fewer Iterations</h3>
<p>When developers understand design language, they can talk specifics, “The spacing here should follow the 8px grid,” instead of vague complaints like “This looks off.” This cuts down on revisions, keeps teams aligned, and builds trust between designers and developers.</p>
<p>Without this shared language, collaboration slows. Misinterpretations multiply, feedback loops drag on, and projects risk missing deadlines.</p>
<h3 id="heading-stronger-problem-solving-and-adaptability">Stronger Problem-Solving and Adaptability</h3>
<p>Projects rarely stick to the original plan. Edge cases and new requirements always emerge. Developers with UI/UX insight can make smart, user-centered choices on the fly, selecting layouts, typography scales, or navigation patterns that enhance the experience.</p>
<p>Without this knowledge, fixes tend to be purely technical, functional, but clunky, leaving users to struggle through awkward flows.</p>
<p><strong>Example:</strong></p>
<p>The image below demonstrates dashboard navigation design. The left side shows a "Cluttered Dashboard" with nine separate menu items scattered randomly (Users, Settings, Reports, Analytics, Notifications, Billing, Integrations, Logs, Support), making it overwhelming and difficult to navigate. The right side shows a "UX-Improved Dashboard" that organizes the same options into three logical categories: "User Management" (Users, Roles), "Settings" (General, Billing, Integrations), and "Analytics &amp; Support" (Reports, Analytics, Support). The improved version reduces cognitive load by grouping related functions, making the interface much easier to understand and navigate.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757050891751/50920984-c330-40b3-957b-6b56b18eaea0.png" alt="Enhancing Problem-Solving Skills example image" class="image--center mx-auto" width="1899" height="497" loading="lazy"></p>
<h3 id="heading-competitive-and-professional-advantage">Competitive and Professional Advantage</h3>
<p>In today’s job market, many developers can code a responsive layout. What makes someone stand out is their ability to think about both aesthetics and usability. Employers and clients value these hybrid professionals who bridge two traditionally separate worlds.</p>
<p>Without UI/UX awareness, developers risk blending into the crowd. Their work may be solid, but it lacks the polish and empathy that win user trust and give products a competitive edge.</p>
<h3 id="heading-missed-opportunities-for-user-trust">Missed Opportunities for User Trust</h3>
<p>Users form judgments about applications within seconds. If the interface looks clunky or confusing, it signals to them that the product may not be reliable. Developers who overlook UI/UX miss this psychological layer of trust. An intuitive, visually consistent interface not only helps users accomplish their tasks but also assures them that the product is worth relying on.</p>
<p><strong>Without UI/UX, developers often:</strong></p>
<ul>
<li><p>Push out features that confuse rather than guide.</p>
</li>
<li><p>Create inconsistent layouts that erode confidence.</p>
</li>
<li><p>Overwhelm users with unnecessary complexity.</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<p>The image below illustrates how UI/UX design affects user trust. The left side shows "Missed Trust" with a poorly designed interface that has inconsistent styling - a basic input field, a small gray "Continue" button, and a confusing pink "Next Step?" button that creates uncertainty about what action to take. The right side shows "Built for Trust" with a clean, professional design featuring a well-styled input field with placeholder text and a prominent, clear "Continue" button that gives users confidence in their actions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757152308435/754f5bf3-0fa0-4bf4-b725-634516fbe487.png" alt="Missed Opportunities for User Trust example image" class="image--center mx-auto" width="1054" height="354" loading="lazy"></p>
<h3 id="heading-inclusivity-and-accessibility">Inclusivity and Accessibility</h3>
<p>Accessibility is a core part of good UX. Developers with this awareness naturally consider screen reader support, keyboard navigation, and high-contrast modes. This ensures products welcome <em>all</em> users.</p>
<p>Without it, developers unintentionally exclude large groups of people and may even face compliance issues. The result isn’t just a less usable product, it fails to respect and include its audience.</p>
<h2 id="heading-how-developers-can-learn-uiux">How Developers Can Learn UI/UX</h2>
<p>For a long time, developers and designers were seen as working on two separate islands. Developers wrote code, designers crafted interfaces, and the two worlds met somewhere in the middle. But today, the line is much blurrier. Employers and teams increasingly expect developers to have at least a foundational understanding of UI/UX.</p>
<p>The good news is that UI/UX isn’t a mysterious art that only designers can master. Developers can learn it step by step, much like they learned programming languages or frameworks. It requires observation, practice, and empathy for the end user.</p>
<h3 id="heading-start-with-observation">Start with Observation</h3>
<p>One of the simplest ways to begin learning UI/UX is by observing products you already use every day. Pay attention to how you navigate your favorite apps or websites. Notice how buttons are placed, how forms are structured, or how feedback is given when you complete an action. Ask yourself: <em>Why does this feel easy to use? Why does this frustrate me?</em></p>
<p>Observation builds awareness. Once you start noticing design decisions in your daily digital life, you’ll begin to understand the invisible rules that shape user experiences.</p>
<h3 id="heading-understand-the-basics-of-ux-principles">Understand the Basics of UX Principles</h3>
<p>Before diving deep, you should familiarize yourself with some core UX principles. These aren’t abstract theories but practical guidelines for making products usable. To review them:</p>
<ul>
<li><p><strong>Consistency</strong>: Users expect familiar patterns. A back button should always go back, not do something unpredictable.</p>
</li>
<li><p><strong>Clarity</strong>: Every element should serve a purpose. If it confuses, it doesn’t belong.</p>
</li>
<li><p><strong>Feedback</strong>: Users need to know when an action has been registered, whether it’s a loading spinner, a success message, or an error prompt.</p>
</li>
<li><p><strong>Accessibility</strong>: Interfaces should be usable by everyone, including people with disabilities.</p>
</li>
</ul>
<p>Learning these basics gives you a strong foundation for evaluating and improving your work.</p>
<h3 id="heading-practice-through-side-projects">Practice Through Side Projects</h3>
<p>Theory alone won’t help – as a dev, you need to practice. A simple way to start is by taking small side projects and focusing not just on functionality but also on experience. Build a to-do app, for instance, but pay attention to how tasks are added, how the interface responds, and how information is displayed.</p>
<p>Treat each project as a learning ground for experimenting with usability, not just coding. You’ll begin to notice where users get stuck and how small tweaks can create big improvements.</p>
<h3 id="heading-learn-design-tools-but-dont-get-overwhelmed">Learn Design Tools, But Don’t Get Overwhelmed</h3>
<p>You don’t need to become a master of Figma or Sketch overnight, but learning the basics of design tools can help you bridge the gap between development and design. Being able to open a Figma file, understand layout grids, or inspect spacing gives you a significant advantage when collaborating with designers.</p>
<p>The goal isn’t to replace designers but to communicate better with them and reduce misinterpretations.</p>
<h3 id="heading-follow-real-world-uiux-examples">Follow Real-World UI/UX Examples</h3>
<p>Another powerful way to learn is by studying existing products. Look at how popular apps like Spotify, Airbnb, or Notion structure their interfaces. Read UX case studies where designers explain their decisions. These real-world examples give context to abstract principles and help developers see how theory translates into practice.</p>
<h3 id="heading-seek-feedback-from-designers-and-users">Seek Feedback from Designers and Users</h3>
<p>Learning UI/UX isn’t just about self-study. It’s also about listening. Share your projects with others, especially designers or actual users, and ask for feedback. What felt easy? What was confusing? As a developer, you may overlook usability issues because you’re too close to the code. External feedback helps you see the product with fresh eyes.</p>
<h3 id="heading-build-empathy-through-testing">Build Empathy Through Testing</h3>
<p>Nothing teaches UI/UX faster than watching someone use your product. Conduct informal usability tests with friends, colleagues, or even strangers. Observe where they hesitate, where they click instinctively, and where they get frustrated. This direct exposure helps developers understand that design isn’t just about what looks good, it’s about what feels natural in practice.</p>
<h3 id="heading-keep-learning-from-design-communities">Keep Learning from Design Communities</h3>
<p>UI/UX is a constantly evolving field. You can grow by engaging with design communities, reading blogs, or following design challenges. Platforms like Dribbble, Behance, and UX Collective provide inspiration and practical lessons. Over time, this exposure sharpens your design instincts and keeps you aligned with industry trends.</p>
<h3 id="heading-some-helpful-resources-to-learn-uiux">Some Helpful Resources to Learn UI/UX:</h3>
<p>📚 <strong>Books:</strong></p>
<ul>
<li><p><strong>Refactoring UI (Adam Wathan &amp; Steve Schoger)</strong>: <a target="_blank" href="https://refactoringui.com/book">https://refactoringui.com/book</a> (I’ve read this book, and the feedback is awesome)</p>
</li>
<li><p><strong>The Design of Everyday Things (Don Norman)</strong>: <a target="_blank" href="https://jnd.org/the-design-of-everyday-things/">https://jnd.org/the-design-of-everyday-things/</a></p>
</li>
<li><p><strong>Laws of UX (Jon Yablonski)</strong>: <a target="_blank" href="https://lawsofux.com/">https://lawsofux.com/</a></p>
</li>
</ul>
<p><strong>🌐 Blogs &amp; Guides:</strong></p>
<ul>
<li><p><strong>Smashing Magazine</strong>: <a target="_blank" href="https://www.smashingmagazine.com/category/uxdesign/">https://www.smashingmagazine.com/category/uxdesign/</a></p>
</li>
<li><p><strong>UX/UI Design Tutorial</strong>: <a target="_blank" href="https://www.freecodecamp.org/news/ui-ux-design-tutorial-from-zero-to-hero-with-wireframe-prototype-figma/">https://www.freecodecamp.org/news/ui-ux-design-tutorial-from-zero-to-hero-with-wireframe-prototype-figma/</a></p>
</li>
<li><p><strong>UX Collective</strong>: <a target="_blank" href="https://uxdesign.cc/">https://uxdesign.cc/</a></p>
</li>
<li><p><strong>Nielsen Norman Group (NNG)</strong>: <a target="_blank" href="https://www.nngroup.com/articles/">https://www.nngroup.com/articles/</a></p>
</li>
</ul>
<p>🎥 <strong>YouTube:</strong></p>
<ul>
<li><p><strong>DesignCourse (Gary Simon)</strong>: <a target="_blank" href="https://www.youtube.com/c/DesignCourse">https://www.youtube.com/c/DesignCourse</a></p>
</li>
<li><p><strong>Flux Academy</strong>: <a target="_blank" href="https://www.youtube.com/c/FluxWithRanSegall">https://www.youtube.com/c/FluxWithRanSegall</a></p>
</li>
<li><p><strong>Steve Schoger (Design Tips)</strong>: <a target="_blank" href="https://www.youtube.com/c/SteveSchoger">https://www.youtube.com/c/SteveSchoger</a></p>
</li>
<li><p><strong>Kevin Powell</strong>: <a target="_blank" href="https://www.youtube.com/kepowob">https://www.youtube.com/kepowob</a></p>
</li>
</ul>
<p><strong>🛠️ Tools &amp; Practice:</strong></p>
<ul>
<li><p><strong>Figma</strong>: <a target="_blank" href="https://www.figma.com/">https://www.figma.com/</a></p>
</li>
<li><p><strong>Dribbble</strong>: <a target="_blank" href="https://dribbble.com/">https://dribbble.com/</a></p>
</li>
<li><p><strong>Behance:</strong> <a target="_blank" href="https://www.behance.net/">https://www.behance.net/</a></p>
</li>
<li><p><strong>Mobbin</strong>: <a target="_blank" href="https://mobbin.com/">https://mobbin.com/</a></p>
</li>
</ul>
<h2 id="heading-what-to-keep-in-mind-about-uxui">What to Keep in Mind about UX/UI</h2>
<p>Some of these tips might feel counterintuitive to anyone who lives in code all day. Yet they explain why users behave the way they do, and why some products succeed where others fail. Here are some of the weird but true things about UI/UX that every developer should know.</p>
<h3 id="heading-users-rarely-read-they-scan">Users Rarely Read. They Scan….</h3>
<p>You may spend hours ensuring error messages, tooltips, or onboarding text are written clearly. But here’s the strange reality: most users don’t actually <em>read</em> them. They scan. Their eyes dart across headings, buttons, and visuals, often skipping over entire paragraphs.</p>
<p>This means your beautifully worded instructions may never be read. Instead, design needs to be built around <strong>visual cues and intuitive flows</strong> that guide users without relying on text.</p>
<p><strong>Example:</strong></p>
<p>The image contrasts verbose versus concise user instructions. The left side shows "Wordy Instructions" with a lengthy paragraph that overwhelms users with unnecessary details about clicking settings, navigating tabs, selecting preferences, and remembering to save changes. The right side demonstrates "Quick Setup" with a clean, numbered three-step process: "1. Open Settings, 2. Choose Preferences, 3. Click Save" followed by a clear "Save &amp; Continue" button. The improved version eliminates cognitive overload by presenting the same information in a scannable, actionable format that's much easier for users to follow and complete successfully.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757151919813/47bb5556-954d-43f0-ba10-98c9ebedcb7a.png" alt="Users Rarely Read. They Scan example image" class="image--center mx-auto" width="1041" height="326" loading="lazy"></p>
<h3 id="heading-a-buttons-size-can-outweigh-its-functionality">A Button’s Size Can Outweigh Its Functionality</h3>
<p>From a developer’s perspective, a button either triggers a function or it doesn’t. But in UX, the <strong>size, placement, and color</strong> of that button can matter more than the functionality behind it.</p>
<p>For example, two buttons may perform the same action, but if one is too small, hidden, or styled poorly, users might not notice it at all. The function exists in code, but in practice, it doesn’t exist for the user.</p>
<p><strong>Example:</strong></p>
<p>The image shows how button size and design affect usability. The left side has a small, gray "Save" button that's hard to notice and might be missed by users. The right side has the same "Save" button but larger, darker, and more prominent, making it much more likely that users will see and click it. Same function, but better design leads to better user interaction.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757151978289/c347439c-004a-4ca4-9a8c-8528ca48bc4c.png" alt="A Button’s Size Can Outweigh Its Functionality example image" class="image--center mx-auto" width="921" height="271" loading="lazy"></p>
<h3 id="heading-more-features-often-mean-less-usability">More Features Often Mean Less Usability</h3>
<p>It feels logical to think that giving users more options and features adds value. But weirdly, the opposite is often true. The more you add, the harder the product becomes to use.</p>
<p>This is called the <strong>paradox of choice</strong>: too many options create decision fatigue. Developers love shipping features, but good UX often means stripping things away until only the essentials remain.</p>
<h3 id="heading-ugly-designs-sometimes-work-better">“Ugly” Designs Sometimes Work Better</h3>
<p>Developers may assume a slick, modern UI always wins, but there are cases where “ugly” or “dated” designs perform better because they feel familiar and trustworthy. Think of Craigslist: it hasn’t changed much in decades, but people use it because it feels simple. This shows that usability can beat aesthetics when it comes to building trust.</p>
<p><strong>Example:</strong></p>
<p>The image illustrates that modern aesthetics don't always equal better usability. The left side shows a "Modern &amp; Slick" design with a sleek, dark "Explore" button that looks impressive and cutting-edge but might feel unfamiliar or untrustworthy to some users. The right side shows an "Old-School &amp; Familiar" design with a simple gray "Continue" button that appears outdated but feels trustworthy and familiar to users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757152055106/b01d0abd-ef1a-42ac-9670-00601f27ef10.png" alt="“Ugly” Designs Sometimes Work Better example image" class="image--center mx-auto" width="1049" height="273" loading="lazy"></p>
<h3 id="heading-users-dont-think-like-developers">Users Don’t Think Like Developers</h3>
<p>This might be the weirdest truth of all: users don’t care about code efficiency, data structures, or the elegant architecture behind the app. They only care about what’s on their screen and whether it makes sense.</p>
<p>For example, a developer might proudly optimize a search algorithm, but if the search bar is hidden in a menu instead of being front and center, users will complain that “the app doesn’t have search.” The feature exists, but in their minds, it doesn’t.</p>
<p><strong>Example:</strong></p>
<p>The GIF image demonstrates the impact of user-centered design in UI/UX. The "Good UX" example places the search bar at the top of the page, making it immediately visible and easy for users to find. In contrast, the "Bad UX" example hides the search bar inside a menu, which makes it harder to access and can frustrate users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757076951279/47559d0f-7603-457f-be8f-f99797cf6ade.gif" alt="Users Don’t Think Like Developers example gif video" class="image--center mx-auto" width="1920" height="904" loading="lazy"></p>
<h3 id="heading-small-delays-feel-bigger-than-they-are">Small Delays Feel Bigger Than They Are</h3>
<p>From a technical side, shaving 300 milliseconds off a request may not feel like a huge deal. But to a user, even slight delays feel magnified. A loading spinner that lingers for a second can feel like forever. UX research shows that anything beyond 2–3 seconds risks losing the user’s attention.</p>
<p>This means developers must think not only about raw performance but also about perceived performance using skeleton screens, micro-animations, or progress indicators to make waits feel shorter.</p>
<p><strong>Example:</strong></p>
<p>The image compares the loading experience design. The left side shows "Feels Slow" with just a basic spinner and "Loading..." text that makes even short delays feel endless to users. The right side shows "Feels Faster" using skeleton screens - gray placeholder blocks that mimic the content structure while loading, making the same delay feel much shorter because users can see that progress is happening and get a preview of what's coming.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757152098385/a764127e-7a34-4ac8-b04b-444f911b40b4.png" alt="Small Delays Feel Bigger Than They Are example image" class="image--center mx-auto" width="1045" height="338" loading="lazy"></p>
<h3 id="heading-accessibility-isnt-just-for-some-users">Accessibility Isn’t Just for “Some” Users</h3>
<p>Developers sometimes assume accessibility features (like screen reader support, keyboard navigation, or high-contrast modes) are niche concerns. But here’s the reality: everyone benefits.</p>
<p>Captions help people in noisy environments, large touch targets help on small screens, and good color contrast improves readability for everyone. Accessibility isn’t just about inclusivity – it’s about creating universally better experiences.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In modern web development, understanding UI/UX isn’t optional. It’s essential. Front-end developers aren’t just programmers – they are the architects of user experience. By learning design principles, developers improve collaboration, write smarter code, and create products that truly serve users.</p>
<p>Whether you’re just starting or already experienced, investing time in UI/UX knowledge will set you apart and open new opportunities. Explore design resources, collaborate closely with designers, and experiment with small design tasks in your projects.</p>
<p>At the end of the day, the best front-end developers don’t just write code, they create experiences users love.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Break Free from Tutorial Hell: A Practical Guide ]]>
                </title>
                <description>
                    <![CDATA[ If you have ever spent weeks hopping from one coding tutorial to another, only to freeze the moment you are asked to build something from scratch, you're not alone. This loop, known as tutorial hell, is where many aspiring developers get stuck. It fe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-break-free-from-tutorial-hell/</link>
                <guid isPermaLink="false">68af2c4fa028696d3cf7c091</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ coding journey ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #beginners #learningtocode #100daysofcode ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Andrew Ezeani ]]>
                </dc:creator>
                <pubDate>Wed, 27 Aug 2025 16:03:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756310586139/70a20570-61f6-4771-b0a9-718573d710f5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you have ever spent weeks hopping from one coding tutorial to another, only to freeze the moment you are asked to build something from scratch, you're not alone.</p>
<p>This loop, known as tutorial hell, is where many aspiring developers get stuck. It feels productive, but over time, you start to realize you’re not really learning how to think or build like a developer. You’re just getting better at following instructions.</p>
<p>In this guide, I’ll walk you through exactly how to break free from that cycle. This is not a pseudo-motivational guide. It’s practical steps based on personal experience, so you can finally start building projects with confidence.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-tutorials-are-not-enough">Why Tutorials Are Not Enough</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-i-broke-free">How I Broke Free</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-accept-that-you-know-nothing-yet">Step 1: Accept That You Know Nothing – Yet</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-start-simple">Step 2: Start Simple</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-break-down-the-project-into-smaller-parts">Step 3: Break Down the Project Into Smaller Parts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-dont-fear-bugs-theyre-signs-of-progress">Step 4: Don’t Fear Bugs – They’re Signs of Progress</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-show-others-your-work">Step 5: Show Others Your Work</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-build-more-similar-projects">Step 6: Build More Similar Projects</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-why-tutorials-are-not-enough">Why Tutorials Are Not Enough</h2>
<p>Tutorials are a great way to get started. They provide structure, simplify complexity, and offer quick wins that make you feel like you’re making progress. In the early stages, that sense of momentum is crucial.</p>
<p>But tutorials can be deceptive. When you’re following along, you’re not solving problems, you’re just replicating solutions that have been worked out for you. It feels productive, but it’s passive learning. You’re learning how to follow instructions, not how to think through a problem or make decisions on your own – that’s the real trap.</p>
<p>Tutorials teach you how to code, but not how to build. You might learn the syntax, but you’re not learning how to build from scratch, troubleshoot unexpected bugs, or take ownership of a project. Over time, after multiple tutorials, this creates a false sense of mastery. It makes you believe that you’re knowledgeable enough, even though you haven’t tested your skills yet.</p>
<p>I know this because I was stuck there too.</p>
<h2 id="heading-how-i-broke-free">How I Broke Free</h2>
<p>Breaking free from tutorial hell was not easy. I had to change my mindset and accept something uncomfortable: I didn’t know as much as I thought I did. That realization stung, but it was freeing. I stopped trying to build big, impressive projects – like a Facebook or Netflix clone – and focused instead on small, achievable ones. Projects that challenged me just enough to learn, but not so much that I would burn out.</p>
<p>I also changed how I approached learning. Instead of trying to absorb everything up front, I started with a goal and only learned what I needed to achieve it. This made my learning process more purposeful and, surprisingly, more enjoyable.</p>
<p>What follows are the exact steps that helped me transition from relying on tutorials to building confidently on my own.</p>
<h3 id="heading-step-1-accept-that-you-know-nothing-yet">Step 1: Accept That You Know Nothing – Yet</h3>
<p>It’s natural to feel confident after completing a few tutorial-based projects. But it’s important to manage your expectations and honestly evaluate your skill level. One pitfall I had was overestimating my ability because of the projects I had built following tutorials.</p>
<p>I had to pause and ask myself an important question: Was the success of those projects dependent on me?</p>
<p>After some reflection, the answer was disappointing but clear – they weren’t. I didn’t possess the knowledge I assumed I had. I was just good at following the carefully laid out instructions from the tutorial.</p>
<p>In hindsight, I realized that building a project is more than just writing code. It involves research, planning, and critical thinking – things tutorials often don’t teach or focus on. Writing code is usually the last thing that’s done. This explains why a common complaint from new developers is, “I know the syntax, but I don’t know what to do when building a project”.</p>
<p>Think of tutorials as training wheels. They help you ride a bicycle (build a project) by providing balance (the tutorial), but they don’t teach you how to maintain that balance on your own. When the training wheels come off – when you try to build independently – you fall off the bicycle because you never learned how to ride one without it.</p>
<p>It’s hard to admit that weeks (or months) of following tutorials have not prepared you as well as you thought. But accepting that truth is the first step toward breaking free from tutorial hell. I had to face this reality, and while it wasn’t easy, that moment of clarity became the turning point in my journey.</p>
<h3 id="heading-step-2-start-simple">Step 2: Start Simple</h3>
<p>Choosing a good first project was tricky. The common advice I got was to build a project I liked or that solved a personal problem. While that sounded reasonable, it didn’t work for me as a beginner. After several failed attempts, I realized those ideas required more knowledge than I had, so it led to frustration and self-doubt.</p>
<p>Problem-solving is a vital skill for programmers, but in the early stages, choosing a project based on a personal problem can be counterproductive. The complexity often outweighs your current abilities, creating unnecessary pressure.</p>
<p>As a beginner, your priority should be building confidence. The easiest way to do that is by starting with simple projects. A simple project is one you can do without feeling overwhelmed. For me, that was <a target="_blank" href="https://www.frontendmentor.io/challenges/qr-code-component-iux_sIO_H">this project</a> on building a QR code generator.</p>
<p>How did I choose it? I used a simple rule: if I looked at the design or the general idea for a project and had no clue on how to build it, I assumed it was beyond my skill level and looked for something simpler. It was not a quick 2-minute decision – I usually gave myself up to 2 two hours to brainstorm. If I was still stuck after that, it was a sign that the project might be too advanced for me.</p>
<p>Platforms like <a target="_blank" href="https://www.frontendmentor.io/">FrontendMentor</a> and <a target="_blank" href="https://icodethis.com/">iCodeThis</a> offer free project designs categorized by difficulty. They helped me focus on writing code without worrying about what to build from scratch.</p>
<p>At this stage, my goal was not to build something impressive – it was to build something that worked.</p>
<h3 id="heading-step-3-break-down-the-project-into-smaller-parts">Step 3: Break Down the Project into Smaller Parts</h3>
<p>When starting a project, it’s easy to feel overwhelmed by its scope. The key to overcoming that feeling is to break up the project into small, manageable pieces. By starting with the easier tasks, you ease into the process, gain momentum, and build confidence before tackling the harder parts.</p>
<p>For my <a target="_blank" href="https://www.frontendmentor.io/solutions/simple-qr-code-component-using-html-and-css-Tba5LCTyD">first project</a>, I divided it into two major sections: the background and the card component. Then I split each section into even smaller parts. Here’s an image of the project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755770750207/ec6b3d76-3c2b-4242-96f6-72dc58bdd191.png" alt="A blue QR code on a background with text below encouraging users to improve front-end skills by building projects." width="1024" height="751" loading="lazy"></p>
<h3 id="heading-i-started-by-dividing-the-card-component-into-clear-sections">I started by dividing the card component into clear sections:</h3>
<ul>
<li><p>The card background.</p>
</li>
<li><p>The card size.</p>
</li>
<li><p>The card contents.</p>
</li>
</ul>
<p>Then I broke the card contents even further:</p>
<ul>
<li><p>The QR image</p>
</li>
<li><p>The heading text</p>
</li>
<li><p>The subtext</p>
</li>
</ul>
<p>Next, I turned each of those items into a simple to-do list of actionable tasks:</p>
<ol>
<li><p>Add a background color to the body element.</p>
</li>
<li><p>Create the card component.</p>
</li>
<li><p>Give the card a fixed width and height.</p>
</li>
<li><p>Center the card component.</p>
</li>
<li><p>Add the background color of the card component.</p>
</li>
<li><p>Add the QR image.</p>
</li>
<li><p>Add the heading text.</p>
</li>
<li><p>Add the sub-text.</p>
</li>
<li><p>Make the card component responsive.</p>
</li>
</ol>
<p>Here’s how that looked in practice for the first few steps:</p>
<p>Task 1: Add a background color to the body</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">220</span>, <span class="hljs-number">220</span>, <span class="hljs-number">220</span>);
}
</code></pre>
<p>Task 2: Create the card component</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"parent"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- content --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<p>Task 3: Give the card a fixed width and height</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.parent</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">320px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">500px</span>;
}
</code></pre>
<p>Task 4: Center the card component</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
}
</code></pre>
<p>After this, I continued with the same approach for the remaining tasks: adding the card’s background, QR image, heading text, subtext, and finally making the card responsive.</p>
<p>Each small win built my confidence and kept me motivated to move forward. By the end, the entire project felt much less intimidating because I could see steady progress at every step.</p>
<p>Breaking a project into smaller parts is not just about staying organized – it's about making the project feel doable. Focusing on one small task at a time, I moved steadily towards completing the project.</p>
<p>You can see the full source code for this project on my GitHub: <a target="_blank" href="https://github.com/ezeaniiandrew/Simple-QR-Code-Component/">View the repo here</a>.</p>
<h3 id="heading-step-4-dont-fear-bugs-theyre-signs-of-progress">Step 4: Don’t Fear Bugs – They’re Signs of Progress</h3>
<p>When I finally started building projects, one of the things I dreaded most was encountering an error. Logical errors were fine by me – they were quiet. No red text in the console to feed my insecurities. But those console errors, written in red, were my nightmare. They made me feel like programming wasn’t for me. Like I should give up and do something else.</p>
<p>It took a while, but eventually I learned to see bugs for what they represented at that stage of my journey: signs of progress. Because if I had a bug, it meant I had written enough code for something to go wrong. That was a big step forward from not knowing where to begin.</p>
<p>When you encounter a bug in your code, start by isolating the problem. Check the error message in your console – it’s usually the best clue on what went wrong. Read it carefully to understand what’s causing the issue. If you’re unsure after reading the error message, copy and search the message online. You will likely find solutions on platforms like <a target="_blank" href="https://stackoverflow.com/">Stack Overflow</a>.</p>
<p>Then try out the solutions you find, and once the bug is fixed, take the time to understand both the mistake and the solution. This step is crucial. If you don’t know why the error happened, you are more likely to repeat it.</p>
<p>If there is no error message, you’re likely dealing with a logic bug. Review your code for typos, incorrect variable names, or missed function calls. If everything looks fine, try reverting to the last working version of your project, then reapply your changes one step at a time, testing after each update. This approach helps you pinpoint where the bug was introduced.</p>
<p>If you are still stuck, ask for help. A more experienced developer or a colleague can offer a fresh perspective. If you’re working alone and don’t have anyone to ask, take a break. Go for a walk, or do something completely unrelated. Sometimes, you are just mentally tired, and the solution becomes clear once you return with fresh eyes.</p>
<p>And if nothing works, the project may simply be too complex for your current skill level – and that’s okay. Shift your focus back to something simpler to improve your understanding. You can always return to the project later when you are more experienced.</p>
<h3 id="heading-step-5-show-others-your-work">Step 5: Show Others Your Work</h3>
<p>Learning is hard – and learning without feedback is even harder. In this journey, it’s difficult to thrive in isolation. Sometimes, the difference between crossing the finish line and giving up lies in the people around us. That is why it’s essential, especially in the early stages, to join programming communities and consistently share your work online.</p>
<p>When I started, I was building in a bubble for a while, but I always felt I wasn’t making enough progress. Eventually, I joined a few tech communities – <a target="_blank" href="https://discord.gg/UAfh3qzhYb">Frontend Mentor</a> and <a target="_blank" href="http://javascript.info">Javascript.info</a> on Discord – and that helped me tremendously. I met other developers and even collaborated on a project with two of them. Although the group later dissolved due to circumstances beyond our control, the experience was invaluable.</p>
<p>That project taught me things I couldn’t have learned working alone. I learned how to collaborate with teammates across different time zones, use Git more effectively, resolve merge conflicts, and improve my understanding of the DOM traversal methods.</p>
<p>It’s not an easy step, especially if you are not social media inclined, but it is an important one. These communities are made up of people at different stages of their tech careers. Sharing your struggles and encouraging one another creates a much needed support system. On days when your code doesn’t work and you feel like giving up, these communities will remind you that you are not alone.</p>
<p>Another benefit is access to free code reviews and feedback from more experienced developers. Their insights can accelerate your growth, boost your confidence, and help you approach challenges with a fresh perspective.</p>
<h3 id="heading-step-6-build-more-similar-projects">Step 6: Build More Similar Projects</h3>
<p>Completing your first project is a significant milestone. I remember feeling a strong sense of accomplishment and immediately wanted to take on something more complex. But that decision backfired. The next project I chose, an ecommerce site, was well beyond my current ability. I got stuck halfway, lost momentum, and eventually abandoned it. That experience forced me to reassess how I approached my learning.</p>
<p>I realized that the goal at this stage wasn’t to build something impressive, it was to build consistently. So I returned to simpler projects. I built another card component, landing page, and variations of UI components I had already worked on. These may have seemed repetitive, but each one helped me improve and cement my knowledge. I understood layout better, debugged faster, and wrote cleaner code.</p>
<p>By the time I had built several similar projects, I could feel the difference. I wasn’t just building things, I understood what I was doing and why. The growth gave me the confidence to take on more advanced challenges.</p>
<p>If you’ve just completed your first project, don’t rush to build something complex. Focus on projects that align with your current skill level. Repetition will reinforce what you’ve learned, improve your understanding, and give you the confidence to handle more difficult projects when the time comes.</p>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>At this point, you have built multiple projects, and your confidence as a developer has grown. That progress deserves acknowledgement. Before jumping into the next big challenge, take time to pause and reflect.</p>
<p>Look back on what you've built. Consider how much you've improved. If there are areas where your approach could have been better, refine them. If not, take a moment to appreciate how far you’ve come. That reflection helps you take stock of what you've learnt and prepares you for what comes next.</p>
<p>When you're ready for your next project, choose it carefully. Choose a project more challenging than your last, but not so complex that it disrupts your momentum. Aim for projects that stretch you just enough to push you forward. Sustainable growth is key.</p>
<p>Breaking free from tutorial hell isn't about quitting tutorials altogether. It's about knowing when to stop relying on them and start thinking for yourself. If you stay consistent and keep building, you will grow into a confident, independent developer.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fetch API Data in React Using Axios ]]>
                </title>
                <description>
                    <![CDATA[ Learning how to fetch data from APIs is a must-have skill for any developer. Whether you're building a simple portfolio site or working on real-world applications, you'll often need to connect to external data sources. Being comfortable with API call... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-fetch-api-data-in-react-using-axios/</link>
                <guid isPermaLink="false">6864461e87c1e49678c8da93</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ axios in react ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwadamisi Samuel ]]>
                </dc:creator>
                <pubDate>Tue, 01 Jul 2025 20:33:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751385483454/7e7949aa-4bcd-4f58-9725-36df67b866a5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Learning how to <code>fetch</code> data from <code>APIs</code> is a must-have skill for any developer. Whether you're building a simple portfolio site or working on real-world applications, you'll often need to connect to external data sources. Being comfortable with API calls shows you're ready to contribute to real projects and work well in a team.</p>
<p>This beginner-friendly tutorial is designed for junior developers and anyone new to React. You'll learn how to <code>fetch data</code> from an API, then <code>store</code> and <code>display</code> it in your React app. No advanced knowledge required – we'll break everything down step by step, so you can follow along and build confidence as you go.</p>
<p>We'll be using <code>React</code>, <code>Vite</code>, <code>Axios</code>, and <code>Tailwind CSS</code> to build a simple app that retrieves and displays data from a public API. First, we’ll fetch data using the built-in fetch method. Then we’ll refactor it using Axios, a popular library that simplifies <code>HTTP requests</code>.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this article, you should:</p>
<ul>
<li><p>Be familiar with basic React concepts like components and <code>useState</code></p>
</li>
<li><p>Know what an <code>API</code> is and that it returns data (usually in JSON)</p>
</li>
<li><p>Have some experience with JavaScript promises and the <code>.then()</code> method. (If you’ve seen or used <code>.then()</code> before, that’s enough – we'll build on that).</p>
</li>
<li><p>Be comfortable using <code>map()</code> to render lists from arrays (the data we get from the API)</p>
</li>
<li><p>Be able to run a React project using tools like Vite or Create React App</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-an-api-and-why-do-we-need-it">What is an API and Why Do We Need it?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-types-of-apis-youll-encounter">Types of APIs You’ll Encounter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tools-well-use">Tools We’ll Use</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-fetch-data-with-react">How to Fetch Data with React</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-fetch-data-with-fetch">How to Fetch Data with fetch()</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-refactor-with-axios">Refactor with Axios</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-loading-and-error-states">How to Handle Loading and Error States</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-loading-state">What is a Loading State?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-an-error-state">What is an Error State?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-keep-your-api-keys-safe">How to Keep Your API Keys Safe</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-why-its-important">Why it's Important:</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-fun-public-apis-to-practice-with">Fun Public APIs to Practice With</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-is-an-api-and-why-do-we-need-it">What is an API and Why Do We Need it?</h2>
<p>An API, or Application Programming Interface, is a way for different systems to communicate. Think of it like a waiter at a restaurant. You tell the waiter what you want from the menu (the request), they take that order to the kitchen (the server), and then bring your food back to the table (the response).</p>
<p>In web development, APIs let your frontend application talk to a backend service. Most of the time, this communication happens through HTTP requests. You make a request to a specific URL (called an endpoint), and you get a response, usually in <code>JSON (JavaScript Object Notation)</code> format. JSON is lightweight, easy to read, and works well with JavaScript.</p>
<p>Here’s a basic GET request example:</p>
<pre><code class="lang-javascript">GET https:<span class="hljs-comment">//jsonplaceholder.typicode.com/users</span>
</code></pre>
<p>This GET request asks the server for a list of users. The response will look something like this:</p>
<pre><code class="lang-javascript">[
  {
    <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"John Doe"</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"JohnDOe@email.com"</span>,
  },
  {
    <span class="hljs-string">"id"</span>: <span class="hljs-number">2</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Jane Doe"</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"JaneDoe@email.com"</span>,
  },
   <span class="hljs-comment">//...more users</span>
]
</code></pre>
<p>Your React app can grab this JSON, store it in state, and display it in the browser. That’s the basic API cycle you’ll see again and again in real-world applications:</p>
<ul>
<li><p>Make the request</p>
</li>
<li><p>Wait for the response</p>
</li>
<li><p>Parse the JSON</p>
</li>
<li><p>Use the data in your UI</p>
</li>
</ul>
<p>Understanding <code>APIs</code> and <code>JSON</code> is essential. You’ll use them to fetch user profiles, submit forms, update dashboards, search databases, and so much more.</p>
<h2 id="heading-types-of-apis-youll-encounter">Types of APIs You’ll Encounter</h2>
<p>Not all APIs are the same. Understanding the types of APIs you'll come across will help you know what tools or steps you’ll need to work with them.</p>
<h3 id="heading-1-public-apis-no-key-required">1. Public APIs (No Key Required)</h3>
<p>These are open-access APIs that anyone can use. They don’t require authentication or an API key. They're great for testing, learning, and building demo apps.</p>
<p>Example:</p>
<p><code>GET https://jsonplaceholder.typicode.com/users</code></p>
<h3 id="heading-2-public-apis-with-api-key">2. Public APIs (With API Key)</h3>
<p>Some APIs are public, but still require an API key. This helps the provider track usage and prevent abuse. You'll usually sign up to get a free key.</p>
<p>Example:</p>
<p><code>GET https://newsapi.org/v2/top-headlines?country=us&amp;apiKey=YOUR_API_KEY</code></p>
<ul>
<li><p><code>https://newsapi.org/v2/top-headlines</code> – the actual API endpoint</p>
</li>
<li><p><code>country=us</code> – a query parameter specifying you want “US headlines”</p>
</li>
<li><p><code>apiKey=YOUR_API_KEY</code> – this is your personal API key you get after signing up on <a target="_blank" href="http://newsapi.org">newsapi.org</a></p>
</li>
</ul>
<p><strong>To use these APIs, you’ll need to:</strong></p>
<ul>
<li><p>Sign up on the provider’s site</p>
</li>
<li><p>Store your key (safely) in your app (we will explore that later on)</p>
</li>
<li><p>Pass it as a query parameter or header</p>
</li>
</ul>
<h3 id="heading-3-private-apis">3. Private APIs</h3>
<p>These are usually used internally in companies. They often require more advanced forms of authentication, like OAuth tokens or session cookies. You won’t typically use these unless you’re working on the backend or within a team project.</p>
<h3 id="heading-4-using-bearer-tokens-for-api-authentication">4. Using Bearer Tokens for API Authentication</h3>
<p>When working with modern APIs, it's common to encounter APIs that require authentication using a <code>Bearer token</code> instead of a simple API key in the URL. The only difference here is that you pass in an <code>object</code> that contains the Bearer token instead of just the api key variable (for example, The Movie Database (TMDB) API).</p>
<p>This approach is more secure because it keeps the token out of the URL and browser history. It also aligns with token-based authentication standards like OAuth 2.0. </p>
<p><strong>Note:</strong> When working with third-party APIs, always check the documentation to see how authentication should be handled. Authentication methods vary – some APIs require passing the key in the URL, others expect it in the headers.</p>
<h2 id="heading-tools-well-use">Tools We’ll Use</h2>
<ul>
<li><p><strong>React:</strong> our JavaScript UI library of choice</p>
</li>
<li><p><strong>Tailwind CSS:</strong> For quick styling</p>
</li>
<li><p><strong>fetch:</strong> Native browser method for making HTTP requests</p>
</li>
<li><p><strong>Axios:</strong> Optional library that makes requests more convenient</p>
</li>
</ul>
<p>Learning how to use these tools and methods will make it easier to adapt to different production methods and environments.</p>
<h2 id="heading-how-to-fetch-data-with-react">How to Fetch Data with React</h2>
<p>Now you need to understand the basic structure and tools you need in order to fetch data with React and store that data to use in your components. To do this properly, you'll need to understand a few core tools and concepts:</p>
<ul>
<li><p><strong>useState hook:</strong> Lets you create and manage local state inside your component. You'll use it to hold the data you fetch, and track things like whether you're still loading or if there was an error.</p>
</li>
<li><p><strong>useEffect hook:</strong> Allows you to perform operations that need to run after the component renders, such as fetching data, subscribing to events, or updating the DOM.</p>
</li>
<li><p><strong>HTTP Requests:</strong> These are how you talk to APIs. You can use the browser-native fetch() method or third-party tools like Axios.</p>
</li>
</ul>
<p>A basic data fetching flow looks like this:</p>
<ul>
<li><p>Set up state to hold your data when it arrives </p>
</li>
<li><p>Use the useEffect() hook to make the API call</p>
</li>
<li><p>Handle loading and error states</p>
</li>
<li><p>Store and display the data once it arrives</p>
</li>
</ul>
<p>Now that you’ve got the fundamentals, let’s walk through two ways to fetch data: first using <code>fetch()</code>, then using <code>Axios</code>.</p>
<h3 id="heading-how-to-fetch-data-with-fetch">How to Fetch Data with <code>fetch()</code></h3>
<p>The <code>fetch()</code> method is a native browser feature that allows you to send <code>HTTP</code> requests directly from the frontend. It’s useful for making basic API calls without any additional libraries.</p>
<p>To use fetch() in React, you'll typically follow this pattern:</p>
<ul>
<li><p>Use the useEffect() hook to ensure the fetch call only runs once when the component mounts.</p>
</li>
<li><p>Call fetch('url') to send the HTTP GET request.</p>
</li>
<li><p>Use .json() to parse the JSON response.</p>
</li>
<li><p>Store the response in state using useState().</p>
</li>
</ul>
<p><strong>Let’s see an example:</strong></p>
<p>Import the necessary hooks and make the fetch call inside useEffect.js:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [users, setUsers] = useState([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users'</span>)
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> setUsers(data));
   <span class="hljs-comment">// res.json() converts the raw response into JSON.</span>
   <span class="hljs-comment">// setUsers(data) updates the React state with that JSON and stores it in state so you can access it.</span>

  }, []);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"p-6 max-w-4xl mx-auto"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl font-bold mb-4"</span>&gt;</span>User List (using fetch)<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"</span>&gt;</span>
        {users.map(user =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{user.id}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white shadow p-4 rounded-xl"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg font-semibold"</span>&gt;</span>{user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-600"</span>&gt;</span>{user.email}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-600"</span>&gt;</span>{user.company.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
<span class="hljs-comment">//Map through the stored data in the state and display the contents</span>
<span class="hljs-comment">// in a list and access the properties from the data(user.name)</span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h3 id="heading-refactor-with-axios">Refactor with Axios</h3>
<p>Axios is a third-party library that makes HTTP requests easier and more reliable. While fetch() is built into the browser, Axios simplifies several things, like automatic JSON parsing and cleaner error handling.</p>
<h4 id="heading-why-use-axios-over-fetch">Why Use Axios Over fetch:</h4>
<ul>
<li><p>Axios automatically converts the response to JSON – you don’t need to call .json() manually.</p>
</li>
<li><p>It has built-in support for request and response interceptors.</p>
</li>
<li><p>It makes it easier to send headers, handle errors, and work with non-GET requests (POST, DELETE, and so on).</p>
</li>
</ul>
<p><strong>First, install Axios in your project via the terminal:</strong></p>
<p><code>npm install axios</code></p>
<p>Import the necessary hooks and Axios and make the API call inside useEffect.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [users, setUsers] = useState([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    axios.get(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users'</span>)
      .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> setUsers(response.data);
      <span class="hljs-comment">// response.data contains the parsed JSON from the API.</span>
      <span class="hljs-comment">// setUsers(data) updates the React state with that JSON and stores it in state so you can access it.</span>
  }, []);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"p-6 max-w-4xl mx-auto"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl font-bold mb-4"</span>&gt;</span>User List (using Axios)
      <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"</span>&gt;</span>
        {users.map(user =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{user.id}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white shadow p-4 rounded-xl"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg font-semibold"</span>&gt;</span>{user.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-600"</span>&gt;</span>{user.email}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-600"</span>&gt;</span>{user.company.name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
<span class="hljs-comment">//Map through the stored data in the state and display the contents</span>
<span class="hljs-comment">// in a list and access the properties from the data(user.name)</span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-how-to-handle-loading-and-error-states">How to Handle Loading and Error States</h2>
<p>When working with data fetching in React, things don't always go perfectly. Sometimes data takes time to arrive and other times the request fails. Loading and Error states come in handy because they give you feedback and make it both user and developer friendly.</p>
<h3 id="heading-what-is-a-loading-state">What is a Loading State?</h3>
<p>A loading state is used to show that data is being fetched. Without it, users might not know what is happening and think the request did not go through or the app isn't working. You typically use a boolean to track this.</p>
<h3 id="heading-what-is-an-error-state">What is an Error State?</h3>
<p>An error state tells you something went wrong – maybe the API is down, or the URL was incorrect. Catching and displaying these errors helps you debug faster and gives users clear feedback.</p>
<h4 id="heading-code-snippet">Code Snippet:</h4>
<p>Here's how you might add loading and error handling to a basic fetch() request:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [users, setUsers] = useState([]);
<span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);

useEffect(<span class="hljs-function">() =&gt;</span> {
  fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
      <span class="hljs-keyword">if</span> (!res.ok) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Network response was not ok'</span>);
      <span class="hljs-keyword">return</span> res.json();
    })
    .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
      setUsers(data);
      setLoading(<span class="hljs-literal">false</span>);
    })
    .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
      setError(err.message);
      setLoading(<span class="hljs-literal">false</span>);
    });
}, []);

<span class="hljs-keyword">if</span> (loading) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
<span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Error: {error}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;
</code></pre>
<p>This gives you a smoother user experience and makes your app more reliable.</p>
<h2 id="heading-how-to-keep-your-api-keys-safe">How to Keep Your API Keys Safe</h2>
<p>If you're using an API that requires a key, it's critical to keep that key secure. Never hardcode your API keys directly into your React components or push them to public repositories. Instead, store them in a <code>.env</code> file at the root of your project(the same directory as your package.json file). In your <code>.env</code> file do this:</p>
<p><code>VITE_API_KEY=your_actual_key_here</code></p>
<p>To access it in your app, use:</p>
<p><code>const apiKey = import.meta.env.VITE_API_KEY;</code></p>
<p>You can then use this key in your API requests. Here's how you would include it in the Axios example:</p>
<pre><code class="lang-javascript">axios.get(<span class="hljs-string">`https://api.example.com/data?apikey=<span class="hljs-subst">${apiKey}</span>`</span>)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    setUsers(response.data);
    setLoading(<span class="hljs-literal">false</span>);
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    setError(error.message);
    setLoading(<span class="hljs-literal">false</span>);
  });
</code></pre>
<p><strong>Note:</strong> In Vite, all environment variables must start with VITE_ to be accessible in the browser. Make sure to add .env to your .gitignore file so it doesn’t get pushed to GitHub.</p>
<p>Hiding your key helps prevent exposing your it to the public, especially if your project is shared on GitHub or deployed online.</p>
<h3 id="heading-why-this-is-important">Why this is important:</h3>
<ul>
<li><p>Exposed keys can be abused, leading to overages or bans from the API provider</p>
</li>
<li><p>You could lose access or rack up charges depending on the service</p>
</li>
<li><p>In secure apps, exposed keys can be a major security vulnerability</p>
</li>
</ul>
<p>Always treat your keys like passwords. If a key does get exposed, revoke it and generate a new one from your API provider’s dashboard.</p>
<h2 id="heading-fun-public-apis-to-practice-with">Fun Public APIs to Practice With</h2>
<p>Here are some fun and free APIs you can use to build practice projects.:</p>
<p><strong>1. JSONPlaceholder:</strong> Fake data for testing: users, posts, comments, todos. No key required.</p>
<p><strong>2. The Dog API:</strong> Get random pictures, breed info, and search by breed. Requires a free API key.</p>
<p><strong>3. The Cat API:</strong> Just like the Dog API, but for cats. Great for image-heavy apps. Free API key.</p>
<p><strong>4. PokeAPI:</strong> Fetch detailed Pokémon data. Great for cards, search filters, or games. No key required.</p>
<p><strong>5. TMDB API:</strong> Get movie data, trending shows, cast details, posters, and more. Requires a free API key from TMDB( You can clone popular streaming sites with this).</p>
<p><strong>6. REST Countries API:</strong> Retrieve country names, capitals, regions, flags, and populations. No API key required.</p>
<p><strong>7. Bored API:</strong> Get random activity suggestions for when you're bored. No key required.</p>
<p><strong>8. JokeAPI:</strong> Fetch jokes by category or type (safe for work, programming, dark humor). No key needed.</p>
<p><strong>9. Rick and Morty API:</strong> Explore characters, locations, and episodes. Perfect for fans. No key required.</p>
<p><strong>10. NASA APIs:</strong> Explore images, astronomy data, and space facts. Requires free API key from NASA.</p>
<p>Play around with different data formats, add filters or search, and combine multiple APIs into one project. It's great practice for real-world app development.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>What you've just built is the foundation of countless real-world applications. The ability to fetch, manage, and display data from APIs is essential in web development.</p>
<p>From here, you can extend this app to:</p>
<ul>
<li><p>Add search or filter functionality</p>
</li>
<li><p>Paginate the results</p>
</li>
<li><p>Display details on a separate page</p>
</li>
</ul>
<p>As you continue to grow as a developer, the patterns you practiced here will show up again and again. Mastering them now sets you up for success later.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Realtime Chat Application with Angular 20 and Supabase ]]>
                </title>
                <description>
                    <![CDATA[ Chat applications let you talk in real-time with your friends, family, or coworkers, and help you quickly, effectively, and efficiently transfer of information. When you’re building modern web applications, chat applications are now pretty much a req... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-realtime-chat-app-with-angular-20-and-supabase/</link>
                <guid isPermaLink="false">6850913c10d2d4fd5525e0d6</guid>
                
                    <category>
                        <![CDATA[ Angular ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Angular ]]>
                    </category>
                
                    <category>
                        <![CDATA[ supabase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ deji adesoga ]]>
                </dc:creator>
                <pubDate>Mon, 16 Jun 2025 21:48:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750094888966/7ac31fee-bd4d-4353-b8cb-911ac60b4516.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Chat applications let you talk in real-time with your friends, family, or coworkers, and help you quickly, effectively, and efficiently transfer of information. When you’re building modern web applications, chat applications are now pretty much a requirement to enable collaboration and enhanced the user experience.</p>
<p>In this tutorial, we will break down how to build a chat application using modern technologies like Angular and Supabase. Building this chat application will help you learn features such as Google OAuth 2.0 for authentication, Angular router for navigation, the <code>CanActivate</code> route guard for route protection, and how to call Supabase functions to create, fetch and delete chats.</p>
<p>On the backend, you will learn how to create database tables in Supabase. You’ll also learn about Supabase functions and Supabase triggers.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-installations-and-account-configuration">Installations and Account Configuration:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-user-interface-of-the-angular-application">How to Create the User Interface of the Angular Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-a-new-supabase-project">How to Set Up a New Supabase Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-google-oauth-20-for-authentication-and-authorization">How to Set Up Google OAuth 2.0 for Authentication and Authorization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-the-router-of-the-angular-application">How to Configure the Router of the Angular Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-authentication-service">How to Set Up the Authentication Service</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-service-functions-for-login-and-sign-out-functionality">How to Create the Service Functions for Login and Sign Out Functionality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-the-authentication-service-function-in-the-template">How to Integrate the Authentication Service Function in the Template</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-route-protection-in-angular">How to Create Route Protection in Angular</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-setup-the-users-table-in-supabase-using-the-sql-editor">How to Create and Setup the Users Table in Supabase using the SQL Editor</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-row-level-security-policies-in-supabase-with-the-sql-editor">How to Configure Row Level Security Policies in Supabase with the SQL Editor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-supabase-functions-in-supabase-with-the-sql-editor">How to Configure Supabase Functions in Supabase with the SQL Editor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-supabase-trigger-in-supabase-with-the-sql-editor">How to Configure Supabase Trigger in Supabase with the SQL Editor</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-setup-the-chat-table-in-supabase-using-the-user-interface">How to Create and Setup the Chat Table in Supabase using the User Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-setup-the-chat-table-policies-in-supabase">How to Create and Setup the Chat Table Policies in Supabase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-functionality-to-create-a-new-chat-message-in-the-angular-application">How to Integrate Functionality to Create a New Chat Message in the Angular Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-fetch-data-in-the-angular-application-from-supabase">How to Fetch Data in the Angular Application from Supabase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-delete-data-in-the-angular-application">How to Delete Data in the Angular Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-logout-functionality-in-the-angular-application">How to Implement Logout Functionality in the Angular Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>HTML</p>
</li>
<li><p>JavaScript</p>
</li>
<li><p>TypeScript</p>
</li>
</ul>
<h2 id="heading-installations-and-account-configuration"><strong>Installations and Account Configuration:</strong></h2>
<p>Before we begin, make sure you have the following installed and ready:</p>
<ul>
<li><p><strong>Node.js and npm:</strong> Angular requires Node. You can check to see if you have it (and what version you have) by running <code>node -v</code> in your terminal.</p>
</li>
<li><p><strong>Angular CLI:</strong> This is the command-line tool to scaffold and manage Angular projects. If you don’t have it, install it with <code>npm install -g @angular/cli</code>. Verify with <code>ng version</code>.</p>
</li>
<li><p><strong>A Supabase account:</strong> Supabase offers a free tier. Sign up on the <a target="_blank" href="http://supabase.com/">Supabase</a> website if you haven’t already.</p>
</li>
</ul>
<p>You can also watch the video version of this article below, or on my <a target="_blank" href="https://youtu.be/8SRhekaJ5iI?si=Vddj2ayZ0rF1R3W2">YouTube channel</a>:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/8SRhekaJ5iI" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-how-to-create-the-user-interface-of-the-angular-application">How to Create the User Interface of the Angular Application</h2>
<p>To create the user interface of the application, we’ll use <a target="_blank" href="https://getbootstrap.com/docs/5.0/getting-started/introduction/">Bootstrap 5</a>. In the <code>index.html</code> file of the Angular application, you are going to paste the Bootstrap 5 CDN link as seen below:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>NgChat<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">base</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/x-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"favicon.ico"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
    <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">app-root</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">app-root</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"</span>
    <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"</span>
    <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Above, you have two CDN links from Bootstrap 5. The first is the <code>&lt;link&gt;</code> tag within the head section, while the second is the <code>&lt;script&gt;</code> tag which is right below the <code>&lt;app-root&gt;&lt;/app-root&gt;</code> tag.</p>
<p>Now that you have the Bootstrap 5 CDN link setup within the project, the next step is to create two new components called <strong>chat</strong> and <strong>login</strong>, respectively, within a pages folder. You can do that using the command below:</p>
<pre><code class="lang-powershell">ng g c pages/chat<span class="hljs-literal">-component</span> &amp;&amp; ng g c pages/login<span class="hljs-literal">-component</span>
</code></pre>
<p>The <code>login-component.html</code> is going to contain the code below:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"login-block"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-md-12"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-lg btn-google btn-block text-uppercase btn-outline"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">"https://res.cloudinary.com/dz4tt9omp/image/upload/v1712537582/google-logo.png"</span>&gt;</span> Signup Using Google<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<p>While the <code>login-component.css</code> will contain the code below:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.login-block</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">300px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">display</span>:flex;
  <span class="hljs-attribute">justify-content</span>:center;
  <span class="hljs-attribute">align-items</span>:center;
  <span class="hljs-attribute">height</span>:<span class="hljs-number">100vh</span>;
}

<span class="hljs-selector-class">.btn</span> {
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">2px</span>;
  <span class="hljs-attribute">text-transform</span>: capitalize;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">15px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">19px</span>;
  <span class="hljs-attribute">cursor</span>: pointer
}


<span class="hljs-selector-class">.btn-google</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#545454</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ffffff</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">1px</span> <span class="hljs-number">2px</span> <span class="hljs-number">1px</span> <span class="hljs-number">#ddd</span>;
}
</code></pre>
<p>To see how the user interface looks, you can call the <code>&lt;app-login /&gt;</code> tag with the <code>app.component.html</code> file, since the route navigations have not yet been configured. The user interface should look like the screenshot below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746232060227/98f18b59-d433-486b-adbe-f28302e8d901.png" alt="Screenshot of a web page running on localhost at port 4200. The page displays a centered white background with a single button labeled &quot;SIGNUP USING GOOGLE&quot; that includes a Google logo icon. The button is slightly elevated with a shadow effect." class="image--center mx-auto" width="1920" height="951" loading="lazy"></p>
<h2 id="heading-how-to-set-up-a-new-supabase-project">How to Set Up a New Supabase Project</h2>
<p>To set up Supabase, you will need to create a new account on <a target="_blank" href="https://supabase.com/">Supabase.com</a> by using either a GitHub account, or the traditional email and password. Once you’ve done this, you will be presented with a form to create a new organization as you can see in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747178536860/972076bf-ae5e-4b33-b18c-011e1e63b3a0.png" alt="Supabase form to create a new organization with name, type, and plan fields." class="image--center mx-auto" width="1920" height="892" loading="lazy"></p>
<p>The organization will be created as fast as your internet speed. Once that is done, the next form you’ll see will allow you to create a new Supabase project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747180463389/a41cd54d-4514-4a8b-8230-a348723b0a2f.png" alt="Supabase interface for creating a new project, showing fields for organization, project name, database password, and region selection." class="image--center mx-auto" width="1910" height="890" loading="lazy"></p>
<p>As you can see from the image above, all you need to do to create a new project is to set a database password and select a region close to where you think most of your users will be. This will help reduce latency. With that you can now click on the create button to create a new project.</p>
<p>Once the project creation is complete, you will be navigated to the dashboard below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747180885966/b9b89775-36cb-49e8-a596-c5ae4bad75a7.png" alt="Supabase dashboard showing a new project with no tables and a task list in progress." class="image--center mx-auto" width="1920" height="891" loading="lazy"></p>
<p>With that, you have now set up your new Supabase project.</p>
<h2 id="heading-how-to-set-up-google-oauth-20-for-authentication-and-authorization">How to Set Up Google OAuth 2.0 for Authentication and Authorization</h2>
<p>To set up Google OAuth 2.0, you need to create an account on <a target="_blank" href="https://console.cloud.google.com">Google Cloud Console</a>. Once you create an account, you will be navigated to the dashboard, where you can create a new project by clicking on the select project button on the top left-hand side of the dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747353793461/b42cd8f9-5a6c-4579-be15-074f9bf90e13.png" alt="Google Cloud console welcome page showing a $300 free credit offer. A 'Select a project' button is highlighted near the top." class="image--center mx-auto" width="1912" height="932" loading="lazy"></p>
<p>Once you’ve selected the newly created project, you can now begin implementing Google OAuth 2.0 by following these steps:</p>
<ul>
<li><p>Click on the hamburger menu on the left-hand side of the dashboard and hover over <strong>APIs and services.</strong></p>
</li>
<li><p>Click on <strong>Credentials</strong>, on the Credentials page, select <strong>Create Credentials</strong> at the top menu of the dashboard. A dropdown menu will appear. Select <strong>Create OAuth client ID</strong>.</p>
</li>
<li><p>On the Client ID page, you’ll get a warning message that says “To create an OAuth client ID, you must first configure your consent screen.” Click on the <strong>Configure consent screen</strong> button.</p>
</li>
</ul>
<p>Next, you’ll be directed to the Branding page. Click on the getting Started button, and you’ll be presented with a form on the overview page as you can see below. Then just fill out the form:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747358499033/47c07eda-9b1e-45ce-ae0b-bb478178a0a2.png" alt="Google Cloud console showing the 'Create branding' page under the Google Auth Platform. The user is filling out app information, including app name and support email, as part of the project configuration steps." class="image--center mx-auto" width="1916" height="885" loading="lazy"></p>
<p>You can now create the OAuth Consent Screen by heading to the Clients tab on the left-side of the dashboard and filling out the details for your application type, the name of your OAuth 2.0 client, as well as the Authorized JavaScript origins.</p>
<p>For the Authorized JavaScript origins, you can enter the URL (<a target="_blank" href="http://localhost:4200">http://localhost:4200</a>), since that is the development URL for our Angular application. Then click on the create button. You may get a warning saying “Note: It may take five minutes to a few hours for settings to take effect.”</p>
<p>Once the configuration is complete, you will get a modal that contains a Client ID and a Client Secret, as you can see below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747361106624/6ba6ed98-469a-4154-9d47-6719d982176e.png" alt="A Google Cloud OAuth client creation dialog displays the Client ID, Client secret, creation date, status as enabled, and a warning about downloading credentials before June 2025." class="image--center mx-auto" width="1918" height="884" loading="lazy"></p>
<p>Make sure you copy the Client ID and Client secret, as you will use this in the Supabase dashboard.</p>
<p>To complete the authentication and authorization setup, head to the Supabase dashboard. Then navigate to the Authentication menu, which is located in the items on the left-side of the dashboard. On this part of the dashboard, you will select <strong>Sign In / Providers</strong>.</p>
<p>On the Sign In / Providers page, scroll down to the <strong>Auth Providers</strong>, then select and enable <strong>Google</strong>. This is where you will paste in the credentials of the Client ID and Client Secret created on the <strong>Google Cloud Console</strong>. Then click on the save button – and make sure you copy the Callback URL (for OAuth).</p>
<p>The final step in this process is to head back to the GCP dashboard, and under the Clients tab, click on the edit icon of the OAuth 2.0 Client IDs you created previously.</p>
<p>Under the Authorized redirect URIs, click on the Add URI button. An input box will appear. Paste in the link of the Callback URL (for OAuth) you grabbed in the Supabase dashboard and click save.</p>
<h2 id="heading-how-to-configure-the-router-of-the-angular-application">How to Configure the Router of the Angular Application</h2>
<p>Earlier in this tutorial, you created two components: Chat and Login. At this point, you need to setup the route configuration in the <code>app.routes.ts</code>. In this file, add the code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'chat'</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/chat/chat-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.ChatComponent),
  },
  {
    path: <span class="hljs-string">'login'</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/login/login-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.LoginComponent),
  },
  {
    path: <span class="hljs-string">''</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/login/login-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.LoginComponent),
  },
];
</code></pre>
<p>Above, you can see the two components now have their separate routes called <strong>chat</strong> and <strong>login</strong>, respectively. They can be accessed anywhere in the application.</p>
<h2 id="heading-how-to-set-up-the-authentication-service">How to Set Up the Authentication Service</h2>
<p>To setup the authentication service in the Angular application, use the following command:</p>
<pre><code class="lang-bash">ng g s services/auth-service
</code></pre>
<p>Next, you’ll generate the environments folders to setup the environment variables using the below command:</p>
<pre><code class="lang-bash">ng g environments
</code></pre>
<p>The final configuration you need to do from the terminal before you begin creating the function for the Angular authentication service is to install Supabase with the command below:</p>
<pre><code class="lang-bash">npm i @supabase/supabase-js
</code></pre>
<p>And with that, you now have Supabase installed in the project and you can begin integrating the functions in the service. Start from the <code>environment.development.ts</code> file<strong>.</strong> The current structure of this file should look this way by default:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {};
</code></pre>
<p>To configure this file, you need to head to the Supabase dashboard. Locate and select the settings menu on the left hand panel of the dashboard. Under the <strong>Configuration</strong> tab, click on Data API.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747569518560/94ae00c7-4f62-4cc7-a0f9-250088754acb.png" alt="Screenshot of Supabase API settings, showing the project URL and API keys section with arrows pointing to the URL and keys, plus buttons to copy each credential." class="image--center mx-auto" width="1918" height="886" loading="lazy"></p>
<p>You can now grab both the Project URL and anon public key (the arrow is pointing to it in the image above). You can now head over to the <code>environment.development.ts</code> file and paste in the values of the copied link following the format below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> environment = {
  production: <span class="hljs-literal">false</span>,
  supabaseUrl: <span class="hljs-string">'https://zktqzszvllbxvjfzkhvk.supabase.co'</span>,
  supabaseKey:
    <span class="hljs-string">'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InprdHF6c3p2bGxieHZqZnpraHZrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDcyNTg3MDgsImV4cCI6MjA2MjgzNDcwOH0.qf3MA-La6se8QijzLFALKc_XdiISmzDk7AZw4-na0uA'</span>,
};
</code></pre>
<p>With the environment variables all in place, you can now create the functions for the authentication service.</p>
<p>In the <code>auth-service.ts</code> which you created previously, start by importing the Supabase package as well as the environments file as you can see below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;
</code></pre>
<p>Next, complete the injection of the Supabase <code>npm</code> package by injecting it into your constructor:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthService {
  supabase!: SupabaseClient;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
      <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );
  }
}
</code></pre>
<h3 id="heading-how-to-create-the-service-functions-for-login-and-sign-out-functionality">How to Create the Service Functions for Login and Sign Out Functionality</h3>
<p>The final step in setting up the <code>Auth</code>service is to create the functions which will later be called in the template. You are going to create four functions which you can see in the code below:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">private</span> router = inject(Router);
  <span class="hljs-keyword">private</span> _ngZone = inject(NgZone);

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );

    <span class="hljs-built_in">this</span>.supabase.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event, session</span>) =&gt;</span> {

      <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'session'</span>, <span class="hljs-built_in">JSON</span>.stringify(session?.user));

      <span class="hljs-keyword">if</span> (session?.user) {
        <span class="hljs-built_in">this</span>._ngZone.run(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/chat'</span>]);
        });
      }
    });
  }

  get isLoggedIn(): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">const</span> user = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'session'</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

    <span class="hljs-keyword">return</span> user === <span class="hljs-string">'undefined'</span> ? <span class="hljs-literal">false</span> : <span class="hljs-literal">true</span>;
  }

  <span class="hljs-keyword">async</span> signInWithGoogle() {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.auth.signInWithOAuth({
      provider: <span class="hljs-string">'google'</span>,
    });
  }

  <span class="hljs-keyword">async</span> signOut() {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.auth.signOut();
  }
}
</code></pre>
<p>The first function created is within the constructor. This is the <code>onAuthStateChange</code> callback function which is derived from Supabase and allows us to listen to Auth changes. It accepts two parameters called <code>event</code> and <code>session</code>.</p>
<p>Here, two conditions were instantiated within the <code>onAuthStateChange</code> callback function. They say that when the <code>session?.user</code> exists, you proceed to set the value to the local storage, and then navigate the user to the dashboard using the Angular router (which has been imported and injected using the <code>inject()</code> function).</p>
<p>The second function, <code>isLoggedIn()</code>, is a getter function that returns a Boolean. It returns either true or false, depending on if it is able to retrieve the user session from <code>localStorage</code>. This function will be used in the authentication guard which you’ll create later.</p>
<p>The third function, <code>signInWithGoogle()</code>, allows the user log into the dashboard using the <code>signInWithOAuth()</code> method provided by Supabase. This allows the user to log into the dashboard using a Google Gmail account.</p>
<p>The final function, <code>signOut()</code>, allows users to logout of the dashboard by resetting the state of the user session to null.</p>
<p>With all these functions created, the final code base in the <code>auth-service.ts</code> should look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable, NgZone, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;
<span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthService {
  <span class="hljs-keyword">private</span> supabase!: SupabaseClient;

  <span class="hljs-keyword">private</span> router = inject(Router);
  <span class="hljs-keyword">private</span> _ngZone = inject(NgZone);
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );

    <span class="hljs-built_in">this</span>.supabase.auth.onAuthStateChange(<span class="hljs-function">(<span class="hljs-params">event, session</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'event'</span>, event);
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'session'</span>, session);

      <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'session'</span>, <span class="hljs-built_in">JSON</span>.stringify(session?.user));

      <span class="hljs-keyword">if</span> (session?.user) {
        <span class="hljs-built_in">this</span>._ngZone.run(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/chat'</span>]);
        });
      }
    });
  }

  get isLoggedIn(): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">const</span> user = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'session'</span>) <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

    <span class="hljs-keyword">return</span> user === <span class="hljs-string">'undefined'</span> ? <span class="hljs-literal">false</span> : <span class="hljs-literal">true</span>;
  }

  <span class="hljs-keyword">async</span> signInWithGoogle() {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.auth.signInWithOAuth({
      provider: <span class="hljs-string">'google'</span>,
    });
  }

  <span class="hljs-keyword">async</span> signOut() {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.auth.signOut();
  }
}
</code></pre>
<p>You can now utilize these functions anywhere in the Angular project as a form of state management.</p>
<h3 id="heading-how-to-integrate-the-authentication-service-function-in-the-template">How to Integrate the Authentication Service Function in the Template</h3>
<p>The first function we’ll use is the <code>signInWithGoogle()</code> function. We’ll use it in the <code>login-component.ts</code> file to allow users log into the application as you can see below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/auth-service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-login'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [],
  templateUrl: <span class="hljs-string">'./login-component.html'</span>,
  styleUrl: <span class="hljs-string">'./login-component.css'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> LoginComponent {
  <span class="hljs-keyword">private</span> auth = inject(AuthService);

  <span class="hljs-keyword">async</span> handleAuth() {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.auth.signInWithGoogle();
  }
}
</code></pre>
<p>Above, you implemented three features:</p>
<ul>
<li><p>Importing <code>AuthService</code> into the <code>LoginComponent</code></p>
</li>
<li><p>Injecting <code>AuthService</code> using the Inject function into the <code>LoginComponent</code></p>
</li>
<li><p>Creating the <code>handleAuth()</code> function that allows you call the <code>signInWithGoogle()</code> from the <code>AuthService</code> file.</p>
</li>
</ul>
<p>Now you can head to the <code>login-component.html</code> file and call the <code>handleAuth(</code>) function as below within the <code>&lt;a&gt;</code> tag:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"login-block"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-md-12"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"handleAuth()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-lg btn-google btn-block text-uppercase btn-outline"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
            <span class="hljs-attr">src</span>=<span class="hljs-string">"https://res.cloudinary.com/dz4tt9omp/image/upload/v1712537582/google-logo.png"</span>&gt;</span> Signup Using Google<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<p>Before you test the implementation, you will need to set the URL configuration in the Supabase dashboard. The URL configuration allows the URLs that authentication providers permit to redirect and post authentication, including wildcards.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748363896805/0b8f2ed1-ba45-44b9-93b8-0f470cb5ff32.png" alt="Interface displaying authentication settings, including site URL configuration and allowed redirect URLs for a web application." class="image--center mx-auto" width="1920" height="886" loading="lazy"></p>
<p>As you can see in the above image, the two Redirect URLs provided are localhost, since we are still currently creating the app in our local machine.</p>
<p>With this, you can test the Google OAuth 2.0 configuration by typing the localhost URL (<a target="_blank" href="http://localhost:4200/">http://localhost:4200</a>) in the browser, clicking on the <strong>Signup Using Google</strong> button<strong>,</strong> and selecting a Gmail account you want to sign up/login with. Then you should get navigated to the Chat component.</p>
<h2 id="heading-how-to-create-route-protection-in-angular">How to Create Route Protection in Angular</h2>
<p>To create route protection in Angular, you can use an in-built mechanism called a <strong>Route Guard</strong>. The Route Guard is used to control access to certain parts of the Angular application using certain conditions before a route is activated or accessible to the user.</p>
<p>In our case, you will be generating the Route Guard as a function (which is the default in our current version of Angular (20), instead of as a class) using the command below:</p>
<pre><code class="lang-bash">ng generate guard auth-guard
</code></pre>
<p>You will then see this prompt that asks “Which type of guard would you like to create?”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748365560338/650c5bf7-8e1c-45cf-915b-c988c167416a.png" alt="650c5bf7-8e1c-45cf-915b-c988c167416a" class="image--center mx-auto" width="1872" height="379" loading="lazy"></p>
<p>Use the spacebar to select <code>CanActivate</code>, and then press the Enter key to generate the Guard. Two files will be generated: the <code>auth-guard.spec.ts</code> file (for testing), and the <code>auth-guard.ts</code> file. Within the <code>auth-guard.ts</code> file, you will see the boilerplate code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { CanActivateFn } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authGuard: CanActivateFn = <span class="hljs-function">(<span class="hljs-params">route, state</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
};
</code></pre>
<p>You can start modifying the above template by importing the Angular Router, the <code>AuthService</code> file that you created earlier, as well as the Inject function:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { CanActivateFn, Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./services/auth-service'</span>;
<span class="hljs-keyword">import</span> { inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
</code></pre>
<p>Next, use the <code>isLoggedIn</code> getter that you created earlier in the <code>AuthService</code> file (which returns a Boolean) to conditionally activate the Chat dashboard for the user based on their login status using the code below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { CanActivateFn, Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./services/auth-service'</span>;
<span class="hljs-keyword">import</span> { inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authGuard: CanActivateFn = <span class="hljs-function">(<span class="hljs-params">route, state</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (inject(AuthService).isLoggedIn === <span class="hljs-literal">false</span>) {
    inject(Router).navigate([<span class="hljs-string">'/login'</span>]);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }
};
</code></pre>
<p>To complete the Guard integration, head over to the <code>app.routes.ts</code> file and import and inject the Authentication Guard as you can see below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { authGuard } <span class="hljs-keyword">from</span> <span class="hljs-string">'./auth-guard'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'chat'</span>,
    canActivate: [authGuard],
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/chat/chat-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.ChatComponent),
  },
  {
    path: <span class="hljs-string">'login'</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/login/login-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.LoginComponent),
  },
  {
    path: <span class="hljs-string">''</span>,
    loadComponent: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./pages/login/login-component'</span>).then(<span class="hljs-function">(<span class="hljs-params">com</span>) =&gt;</span> com.LoginComponent),
  },
];
</code></pre>
<p>With this, the route protection implementation is now complete and only authenticated users can view the dashboard.</p>
<h2 id="heading-how-to-create-and-setup-the-users-table-in-supabase-using-the-sql-editor">How to Create and Setup the Users Table in Supabase using the SQL Editor</h2>
<p>To create and setup the users table, use the schema below:</p>
<ul>
<li><p>id (uuid)</p>
</li>
<li><p>full_name (text)</p>
</li>
<li><p>avatar_url (text)</p>
</li>
</ul>
<p>You can use the <strong>SQL</strong> Editor in Supabase. The SQL Editor is the third item on the menu panel in the Supabase dashboard. Here you are going to type in the query below in the SQL Editor input field:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-built_in">public</span>.users (
   id <span class="hljs-type">uuid</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">null</span> <span class="hljs-keyword">references</span> auth.users <span class="hljs-keyword">on</span> <span class="hljs-keyword">delete</span> <span class="hljs-keyword">cascade</span>,
   full_name <span class="hljs-type">text</span> <span class="hljs-keyword">NULL</span>,
   avatar_url <span class="hljs-type">text</span> <span class="hljs-keyword">NULL</span>,
   <span class="hljs-keyword">primary key</span> (id)
);
</code></pre>
<p>You can now click on the Run button on the bottom right. You should get a message that says: <strong>Success. No rows returned</strong>, as you can see in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748394167618/5db26c97-a947-4233-8232-3aba72187a12.png" alt="SQL code for creating a user profile table with fields for id, full name, and avatar URL. Returns No rows returned after execution." class="image--center mx-auto" width="1918" height="885" loading="lazy"></p>
<p>Now let’s go ahead and enable row level security, as well as the Supabase function and trigger.</p>
<h3 id="heading-how-to-configure-row-level-security-policies-in-supabase-with-the-sql-editor">How to Configure Row Level Security Policies in Supabase with the SQL Editor</h3>
<p>Row Level Security (RLS) in Supabase allows you to control access to individual rows in your database tables based on custom logic. It’s one of the core features for building secure, multi-user applications with Supabase.</p>
<p>RLS lets you define SQL policies that determine which users can <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code> specific rows in a table.</p>
<p>To enable RLS in the <strong>users</strong> table, type the command below in your SQL Editor:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-built_in">public</span>.users <span class="hljs-keyword">ENABLE</span> <span class="hljs-keyword">ROW</span> <span class="hljs-keyword">LEVEL</span> <span class="hljs-keyword">SECURITY</span>;
</code></pre>
<p>For the purpose of this tutorial, you are going to create just two policies, which are:</p>
<ol>
<li><p>The ability for users to access their own profile</p>
</li>
<li><p>The ability for users to update their own profile</p>
</li>
</ol>
<h4 id="heading-the-ability-for-users-to-access-their-own-profile">The ability for users to access their own profile</h4>
<p>To enable users access their own profile, head back to the SQL editor and create a new snippet with the following query:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">POLICY</span> "Permit Users to Access Their Profile"
  <span class="hljs-keyword">ON</span> <span class="hljs-built_in">public</span>.users
  <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">SELECT</span>
  <span class="hljs-keyword">USING</span> ( auth.uid() = id );
</code></pre>
<p>With this query, users will be able to access their own profile as long as the authenticated user’s ID matches the <code>id</code> of the column of the row.</p>
<h4 id="heading-the-ability-for-users-to-update-their-own-profile">The ability for users to update their own profile</h4>
<p>To enable users update their own profile, head back to the SQL editor and create a new snippet with the following query:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">POLICY</span> "Permit Users to Update Their Profile"
  <span class="hljs-keyword">ON</span> <span class="hljs-built_in">public</span>.users
  <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">UPDATE</span>
  <span class="hljs-keyword">USING</span> ( auth.uid() = id );
</code></pre>
<p>With the above query, users will be able to update their own profile as long as the authenticated user’s ID matches the <code>id</code> of the column of the row.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748397263186/11e886f4-0d08-4907-9859-9269ee0da2ef.png" alt="Screenshot of SQL Editor displaying a policy script for user profile updates, with navigation pane and no results returned." class="image--center mx-auto" width="1910" height="886" loading="lazy"></p>
<h3 id="heading-how-to-configure-supabase-functions-in-supabase-with-the-sql-editor">How to Configure Supabase Functions in Supabase with the SQL Editor</h3>
<p><strong>Supabase Functions</strong> are serverless functions that can be deployed and run within your Supabase project using <strong>Supabase Edge Functions</strong>.</p>
<p>In this project, you will create a trigger function that automatically creates a new row in the users table whenever a new user is created in the <code>auth.users</code> table.</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">CREATE</span>
<span class="hljs-keyword">OR REPLACE</span> <span class="hljs-keyword">FUNCTION</span> <span class="hljs-built_in">public</span>.user_profile() <span class="hljs-keyword">RETURNS</span> <span class="hljs-type">TRIGGER</span> <span class="hljs-keyword">AS</span> $$<span class="pgsql"> <span class="hljs-keyword">BEGIN</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <span class="hljs-built_in">public</span>.users (id, full_name,avatar_url)
<span class="hljs-keyword">VALUES</span>
  (
    <span class="hljs-built_in">NEW</span>.id,
    <span class="hljs-built_in">NEW</span>.raw_user_meta_data -&gt;&gt; <span class="hljs-string">'full_name'</span>::<span class="hljs-type">TEXT</span>,
    <span class="hljs-built_in">NEW</span>.raw_user_meta_data -&gt;&gt; <span class="hljs-string">'avatar_url'</span>::<span class="hljs-type">TEXT</span>
  );
<span class="hljs-keyword">RETURN</span> <span class="hljs-built_in">NEW</span>;
<span class="hljs-keyword">END</span>;
$$</span> <span class="hljs-keyword">LANGUAGE</span> plpgsql <span class="hljs-keyword">SECURITY</span> <span class="hljs-keyword">DEFINER</span>;
</code></pre>
<p>To summarize the above query:</p>
<ul>
<li><p>You start by defining or replacing a function named <code>user_profile()</code> that will be used as a trigger.</p>
</li>
<li><p>Next, the trigger inserts a new row into the <code>public.users</code> table, and then extracts the <code>full_name</code> and <code>avatar_url</code> from the user's metadata as text.</p>
</li>
<li><p>The inserted record is now returned when the trigger function is complete</p>
</li>
<li><p>Finally, you use the <code>SECURITY DEFINER</code> keyword so that the function can run with the privileges of the user who created it.</p>
</li>
</ul>
<h3 id="heading-how-to-configure-supabase-trigger-in-supabase-with-the-sql-editor">How to Configure Supabase Trigger in Supabase with the SQL Editor</h3>
<p>A trigger in Supabase is a PostgreSQL feature used to automatically run a function in response to events on a table (SELECT, INSERT, UPDATE, or DELETE). It’s mostly used with Row Level Security or syncing data across tables.</p>
<p>In this project, you will create a Supabase trigger that automatically runs a function after a new user is created in the <code>auth.users</code> table.</p>
<pre><code class="lang-pgsql"> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TRIGGER</span>
  create_user_trigger
  <span class="hljs-keyword">AFTER</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">ON</span> auth.users
  <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">EACH</span> <span class="hljs-keyword">ROW</span>
  <span class="hljs-keyword">EXECUTE</span> <span class="hljs-keyword">PROCEDURE</span>
    <span class="hljs-built_in">public</span>.user_profile();
</code></pre>
<p>To summarize the above query:</p>
<ul>
<li><p>The first line creates a <strong>trigger</strong> named <code>create_user_trigger</code>.</p>
</li>
<li><p>Next, the INSERT ON statement is activated when a user signs up and a new row is inserted into the <code>auth.users</code> table</p>
</li>
<li><p>Then the trigger runs once for every new user added in a new row.</p>
</li>
<li><p>Finally, the custom function <code>public.user_profile()</code> is called to perform some logic, typically inserting data into the <code>users</code> table.</p>
</li>
</ul>
<p>With the above integration, you can now log into the dashboard with a new google account and view the users table. There you will see the data that contains the <strong>id</strong>, <strong>full_name</strong>, and <strong>avatar_url</strong> as you can see below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748560146471/4c7663a8-6708-461a-8ed5-7ed8a3931318.png" alt="Screenshot of a database table editor displaying user data, including name and avatar URL, with options for filtering and sorting." class="image--center mx-auto" width="1920" height="878" loading="lazy"></p>
<h2 id="heading-how-to-create-and-setup-the-chat-table-in-supabase-using-the-user-interface">How to Create and Setup the Chat Table in Supabase using the User Interface</h2>
<p>To create the chat table, you will use the user interface in Supabase instead of the SQL Editor. To do this, you need to head to the Table Editor menu on the dashboard and click on the <strong>New Table</strong> button.</p>
<p>Once selected, a modal will popup which contains some input fields such as the table name, description, and columns. You can call the table name chat and omit the description for now since it’s optional. In the columns section, fill out the fields using the schema below:</p>
<ul>
<li><p>id (uuid)</p>
</li>
<li><p>Created At (date)</p>
</li>
<li><p>text (text)</p>
</li>
<li><p>editable (boolean)</p>
</li>
<li><p>sender (uuid)</p>
</li>
</ul>
<p>You can see the configuration for this in the table below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748564800333/0e879684-75d3-4c47-9f38-56d6041d6b38.png" alt="Screenshot of a table editor interface displaying fields for creating a new database table with various data types and options." class="image--center mx-auto" width="1882" height="879" loading="lazy"></p>
<p>Next up, you need to add a foreign key relation for the users table. To do this, you scroll to the bottom of the modal and click on the <strong>Add foreign key relation</strong> button. This will prompt another modal on top of the current modal. Here you can take the following steps:</p>
<ul>
<li><p>Under the <strong>Select a table to reference to</strong> label, select the <strong>users</strong> table<strong>.</strong></p>
</li>
<li><p>Under the <strong>public.chat</strong> label, select the <strong>sender</strong> option.</p>
</li>
<li><p>Under public.users label, select <strong>uuid.</strong></p>
</li>
<li><p>Under the <strong>Action if referenced row is updated</strong> label, select <strong>Cascade</strong>.</p>
</li>
<li><p>Under the <strong>Action if referenced row is removed</strong> label, select <strong>Cascade</strong> as well.</p>
</li>
</ul>
<p>If you’ve followed the above steps, you can now click on the save button, which successfully creates the chat table.</p>
<h2 id="heading-how-to-create-and-setup-the-chat-table-policies-in-supabase">How to Create and Setup the Chat Table Policies in Supabase</h2>
<p>The final step you need to perform for the chat table is to add a Row Level Security policy. You can do this by clicking the <strong>Add RLS policy</strong> button at the top of the chat table page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748567233239/6f69e305-5e09-4485-89b7-0115d41c8e0f.png" alt="A web interface displaying a Table Editor with options for managing chat and user data in a database schema." class="image--center mx-auto" width="1915" height="385" loading="lazy"></p>
<p>A new page will appear. Then you can click on the <strong>Create policy</strong> button, which displays a modal.</p>
<p>The first policy you will create is the <strong>DELETE</strong> policy, which will have the configuration you can see in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748779442105/f977dafb-ab73-4bb7-8643-9aa4e889c359.png" alt="Screenshot of a database policy settings interface for deleting user records based on user ID in a chat application." class="image--center mx-auto" width="1920" height="777" loading="lazy"></p>
<p>From the above image, we made these four implementations:</p>
<ul>
<li><p>First, we entered the policy name as “<strong>Delete by User ID</strong>“.</p>
</li>
<li><p>Next we selected the <strong>DELETE</strong> policy command clause.</p>
</li>
<li><p>Then under the targeted roles, we selected authenticated in the drop down select, to allow only authenticated users to perform delete operations.</p>
</li>
<li><p>Finally, under the <strong>USE OPTIONS ABOVE TO EDIT</strong> section, in line 7, we condition the query as <code>(auth.uid() = sender)</code> This allows only logged in users to delete their data.</p>
</li>
</ul>
<p>You can now click on the <strong>Save policy</strong> button to complete the DELETE setup.</p>
<p>The second policy you will create is the <strong>INSERT</strong> policy, which will have the configuration you can see in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748823531256/14aea41d-f221-42df-a739-b40a680395e3.png" alt="A policy configuration interface for inserting records in a chat table, targeting authenticated users with specific criteria." class="image--center mx-auto" width="1900" height="879" loading="lazy"></p>
<p>From the above image, four implementations were made:</p>
<ul>
<li><p>First, we entered the policy name as “<strong>Insert for Authenticated Users</strong>“.</p>
</li>
<li><p>Next we selected the <strong>INSERT</strong> policy command clause.</p>
</li>
<li><p>Then under the targeted roles, we selected authenticated in the drop down to allow only authenticated users perform insert operations.</p>
</li>
<li><p>Finally, under the <strong>USE OPTIONS ABOVE TO EDIT</strong> section, in line 7, the query was conditioned as <code>((sender = auth.uid()) AND (created_at = now()))</code>. The first condition ensures that the <code>sender</code> field in the inserted row matches the currently logged-in user's ID (from the Supabase JWT), while the second condition ensures that the <code>created_at</code> field is exactly equal to the current timestamp at the time of insertion.</p>
</li>
</ul>
<p>The third policy you will create is the <strong>SELECT</strong> policy, which will have the configuration you can see in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748824129598/8c15b701-dadb-4763-b5eb-e9484bd84470.png" alt="Row-level security policy configuration for a database table, specifying access permissions for authenticated users based on SELECT criteria." class="image--center mx-auto" width="1904" height="882" loading="lazy"></p>
<p>From the above image, we implemented four things:</p>
<ul>
<li><p>First, we entered the policy name as “<strong>Read Data for Authenticated Users</strong>“.</p>
</li>
<li><p>Next we selected the <strong>SELECT</strong> policy command clause, <em>pun intended</em> <strong>😊*</strong>.*</p>
</li>
<li><p>Then under the targeted roles, we selected authenticated in the drop down to allow only authenticated users perform select operations.</p>
</li>
<li><p>Finally, under the <strong>USE OPTIONS ABOVE TO EDIT</strong> section, in line 7, the query was conditioned as <code>true</code>. This allows all authenticated users to read all rows, or chats in our case.</p>
</li>
</ul>
<p>With the above implementation, you’ve created all the policies needed for the chat application.</p>
<h2 id="heading-how-to-integrate-functionality-to-create-a-new-chat-message-in-the-angular-application">How to Integrate Functionality to Create a New Chat Message in the Angular Application</h2>
<p>Now let’s add the code that lets users create a new chat message. First, start by creating a new Angular service using the command below:</p>
<pre><code class="lang-powershell">ng g s services/chat<span class="hljs-literal">-service</span>
</code></pre>
<p>Within the <code>chat-service.ts</code> file, you can now configure the Supabase client, just as we did in the <code>auth-service.ts</code> file as seen below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable, signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ChatService {
  supabase!: SupabaseClient;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );
  }
}
</code></pre>
<p>Next, create the function that enables you to create a new chat message. The function called <code>chatMessage()</code> is below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable, signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ChatService {
  supabase!: SupabaseClient;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );
  }

  <span class="hljs-keyword">async</span> chatMessage(text: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.from(<span class="hljs-string">'chat'</span>).insert({ text });

      <span class="hljs-keyword">if</span> (error) {
        alert(error.message);
      }
    } <span class="hljs-keyword">catch</span> (error) {
      alert(error);
    }
  }
}
</code></pre>
<p>The above <code>chatMessage</code> function sends a chat message by inserting it into the <code>chat</code> table in Supabase.</p>
<p>You can now call this service in the <code>chat-component.ts</code> file. Within the <code>chat-component.ts</code>, import and inject the <code>chat-service.ts</code> file.</p>
<p>To send the data to the Supabase database, you need to setup Reactive form. Reactive form in Angular enables you to get data from an input field, which can be passed as a payload and then inserted into the database.</p>
<p>To setup a Reactive form in Angular, follow these steps:</p>
<ul>
<li><p>Import <code>FormBuilder</code>, <code>FormGroup</code>, <code>ReactiveFormsModule</code>, and <code>Validators</code> from <code>@angular/forms</code></p>
</li>
<li><p>Insert the <code>ReactiveFormsModule</code> inside of the imports array.</p>
</li>
<li><p>Inject the <code>FormBuilder</code> as a variable.</p>
</li>
<li><p>Declare a property that will hold the <strong>Reactive Form group.</strong></p>
</li>
<li><p>Inject the <code>FormBuilder</code> into the <code>ngOnInit</code> lifecycle hook.</p>
</li>
</ul>
<p>The code for the Reactive form setup is below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/auth-service'</span>;
<span class="hljs-keyword">import</span> {
  FormBuilder,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-chat'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [ReactiveFormsModule],
  templateUrl: <span class="hljs-string">'./chat-component.html'</span>,
  styleUrl: <span class="hljs-string">'./chat-component.css'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ChatComponent {
  chatForm!: FormGroup;
  <span class="hljs-keyword">private</span> fb = inject(FormBuilder);

  ngOnInit() {
    <span class="hljs-built_in">this</span>.chatForm = <span class="hljs-built_in">this</span>.fb.group({
      chat_message: [<span class="hljs-string">''</span>, Validators.required],
    });
  }
}
</code></pre>
<p>To complete the Reactive form setup, bind the <code>FormGroup</code> into the HTML file. Also bind the disabled attribute, which disables the button when the form is invalid, as you can see below:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"chatForm"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex-grow-0 py-3 px-4 border-top"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"input-group"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"chat_message"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Type your message"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"!chatForm.valid"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
           <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>With the Reactive form setup complete, you can now create the function that calls the service which allows you create a new chat message.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/auth-service'</span>;
<span class="hljs-keyword">import</span> {
  FormBuilder,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;
<span class="hljs-keyword">import</span> { ChatService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/chat-service'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-chat'</span>,
  standalone: <span class="hljs-literal">true</span>,
  imports: [ReactiveFormsModule],
  templateUrl: <span class="hljs-string">'./chat-component.html'</span>,
  styleUrl: <span class="hljs-string">'./chat-component.css'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ChatComponent {
  <span class="hljs-keyword">private</span> chat_service = inject(ChatService);
  chatForm!: FormGroup;
  <span class="hljs-keyword">private</span> fb = inject(FormBuilder);

  ngOnInit() {
    <span class="hljs-built_in">this</span>.chatForm = <span class="hljs-built_in">this</span>.fb.group({
      chat_message: [<span class="hljs-string">''</span>, Validators.required],
    });
  }

  onSubmit() {
    <span class="hljs-keyword">const</span> formValue = <span class="hljs-built_in">this</span>.chatForm.value.chat_message;
    <span class="hljs-built_in">this</span>.chat_service
      .chatMessage(formValue)
      .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
        <span class="hljs-built_in">this</span>.chatForm.reset();
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        alert(err.message);
      });
  }
}
</code></pre>
<p>The <code>onSubmit()</code> function in the above code basically does the following tasks:</p>
<ul>
<li><p>Gets the data from the Reactive form input field using the variable called <code>formValue</code></p>
</li>
<li><p>Calls the <code>chatMessage()</code> method from the <code>ChatService</code>, passing the data from the input field.</p>
</li>
<li><p>If successful, it resets the form.</p>
</li>
<li><p>If there's an error, it shows an alert with the error message.</p>
</li>
</ul>
<p>In the <code>chat-component.html</code> file, use of the <code>(ngSubmit)</code> directive to bind the <code>onSubmit()</code> function to the form:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"chatForm"</span> (<span class="hljs-attr">ngSubmit</span>)=<span class="hljs-string">"onSubmit()"</span>&gt;</span>
</code></pre>
<p>You can now test to see if the data we send from the input field saves directly into the <strong>chat</strong> database table.</p>
<p><strong>NOTE:</strong> make sure you <strong>delete all current users saved in the users table and authentication page on Supabase</strong> before trying this out for best results.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749042436895/a50416e7-3f8b-48a9-b8f3-96df520b2238.png" alt="Screenshot of a chat interface showing a message from &quot;Sharon Doe,&quot; with a timestamp and a text input field at the bottom." class="image--center mx-auto" width="1917" height="880" loading="lazy"></p>
<p>From the above image, you will click on the send button and send the <strong>Test</strong> data in the input field.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749043485066/94b53998-e924-44c2-bbfd-e26d591fdf54.png" alt="Table editor interface displaying the &quot;chat&quot; table with columns including ID, created_at, text, editable, and sender, showing entries and configuration options." class="image--center mx-auto" width="1911" height="739" loading="lazy"></p>
<p>The data should now be successfully saved into the database and the <strong>INSERT</strong> operation should now be integrated into the Angular application.</p>
<h2 id="heading-how-to-fetch-data-in-the-angular-application-from-supabase">How to Fetch Data in the Angular Application from Supabase</h2>
<p>To fetch data from the chat table from Supabase, start by creating a service function in the <code>chat-service.ts</code> file, as seen below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable, signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { SupabaseClient, createClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@supabase/supabase-js'</span>;
<span class="hljs-keyword">import</span> { environment } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../environments/environment.development'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ChatService {
  supabase!: SupabaseClient;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseKey
    );
  }

  <span class="hljs-keyword">async</span> chatMessage(text: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.from(<span class="hljs-string">'chat'</span>).insert({ text });
      <span class="hljs-keyword">if</span> (error) {
        alert(error.message);
      }
    } <span class="hljs-keyword">catch</span> (error) {
      alert(error);
    }
  }

    <span class="hljs-keyword">async</span> listChat() {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase
        .from(<span class="hljs-string">'chat'</span>)
        .select(<span class="hljs-string">'*,users(*)'</span>);

      <span class="hljs-keyword">if</span> (error) {
        alert(error.message);
      }

      <span class="hljs-keyword">return</span> data;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> error;
    }
  }
}
</code></pre>
<p>To summarize the function above called <code>listChat()</code>:</p>
<ul>
<li><p>We fetch the chat messages from the <code>chat</code> table using the <code>from</code> clause.</p>
</li>
<li><p>Then we include the related user info by joining the users table with <code>(select(', users()'))</code>.</p>
</li>
<li><p>An alert message is shown if there's a Supabase error.</p>
</li>
<li><p>Finally, the fetched data is returned, an error is thrown if something goes wrong.</p>
</li>
</ul>
<p>Before you head to the <code>chat-component.ts</code> file to consume the <code>listChat()</code> service function, you need to create an interface which helps shape the structure of the array of objects returned from Supabase. This gives us type safety and consistency.</p>
<p>To set up the interface, create an <strong>interface</strong> folder within the <strong>app</strong> directory. Here you will create a file called <code>chat-response.ts</code>. Then create the structure below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Ichat {
  created_at: <span class="hljs-built_in">string</span>;
  editable: <span class="hljs-built_in">boolean</span>;
  id: <span class="hljs-built_in">string</span>;
  sender: <span class="hljs-built_in">string</span>;
  text: <span class="hljs-built_in">string</span>;
  users: {
    avatar_url: <span class="hljs-built_in">string</span>;
    id: <span class="hljs-built_in">string</span>;
    full_name: <span class="hljs-built_in">string</span>;
  };
}
</code></pre>
<p>Heading back to the <code>chat-component.ts</code>, import both the interface which was named <code>Ichat</code> as well as <code>signal</code> and <code>effect</code> from <code>@angular/core</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, effect, inject, signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { AuthService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/auth-service'</span>;
<span class="hljs-keyword">import</span> {
  FormBuilder,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/forms'</span>;
<span class="hljs-keyword">import</span> { ChatService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/chat-service'</span>;
<span class="hljs-keyword">import</span> { Ichat } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../interface/chat-response'</span>;
</code></pre>
<p>Next, create a variable called <code>chats</code>, which will hold the response from the Supabase client as a signal:</p>
<pre><code class="lang-typescript">  chats = signal&lt;Ichat[]&gt;([]);
</code></pre>
<p>With this, you can now create the function that fetches the chat array of objects from the Supabase dashboard:</p>
<pre><code class="lang-typescript">  onListChat() {
    <span class="hljs-built_in">this</span>.chat_service
      .listChat()
      .then(<span class="hljs-function">(<span class="hljs-params">res: Ichat[] | <span class="hljs-literal">null</span></span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(res);
        <span class="hljs-keyword">if</span> (res !== <span class="hljs-literal">null</span>) {
          <span class="hljs-built_in">this</span>.chats.set(res);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'No messages Found'</span>);
        }
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        alert(err.message);
      });
  }
</code></pre>
<p>To summarize the function above, we started by:</p>
<ul>
<li><p>Calling the <code>listChat()</code> function from the <code>ChatService</code> to fetch the chat messages.</p>
</li>
<li><p>If messages are returned, it updates the chats signal with the result, by using the <code>set()</code> method derived from signals.</p>
</li>
<li><p>In the event where no messages are returned, it logs <code>"No messages Found"</code> to the console.</p>
</li>
<li><p>If an error occurs, it shows an alert with the error message.</p>
</li>
</ul>
<p>We then call the <code>onListChat()</code> function within the constructor using the <code>effect()</code> function, which helps handle asynchronous operations.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    effect(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.onListChat();
    });
  }
</code></pre>
<p>When the application is saved, you can see the data in the console from the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749066359110/dedaa60a-c711-42d9-bac1-c9a130fcc4d1.png" alt="Screenshot of a chat application showing a message from Sharon Doe, along with developer tools displaying the message data in an array of object." class="image--center mx-auto" width="1912" height="868" loading="lazy"></p>
<p>You can now display the chat data in the HTML file of the page by getting rid of the placeholder text.</p>
<p>To do this, you can use the <code>@for</code> control flow in Angular as seen below:</p>
<pre><code class="lang-typescript">&lt;main&gt;
  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"container"</span>&gt;
    &lt;h3 <span class="hljs-keyword">class</span>=<span class="hljs-string">"mb-3"</span>&gt;Supa Chat &lt;button <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-secondary"</span> style=<span class="hljs-string">"float: right;"</span>&gt;Log
        out&lt;/button&gt;
    &lt;/h3&gt;
    &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"card"</span>&gt;
      &lt;div&gt;

        &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"col-12 col-lg-12 col-xl-12"</span>&gt;
          <span class="hljs-meta">@for</span> (msg <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.chats(); track msg) {
          &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"position-relative"</span>&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"chat-messages p-4"</span>&gt;
              &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"chat-message-left pb-4"</span>&gt;
                &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"me-5"</span>&gt;
                  &lt;img src={{msg?.users?.avatar_url}} <span class="hljs-keyword">class</span>=<span class="hljs-string">"rounded-circle mr-1"</span> alt=<span class="hljs-string">"image"</span> width=<span class="hljs-string">"40"</span> height=<span class="hljs-string">"40"</span>&gt;
                  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-muted small text-nowrap mt-2"</span>&gt;{{msg?.created_at | date: <span class="hljs-string">'M/d/yy, h:mm a'</span>}}&lt;/div&gt;
                &lt;/div&gt;
                &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex-shrink-1 bg-light rounded py-2 px-3 ml-3"</span>&gt;
                  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"font-weight-bold mb-1"</span>&gt;{{msg?.users?.full_name}}&lt;/div&gt;
                  {{msg?.text}}
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          } <span class="hljs-meta">@empty</span> {
          &lt;div&gt;No chats available&lt;/div&gt;
          }

          &lt;form [formGroup]=<span class="hljs-string">"chatForm"</span> (ngSubmit)=<span class="hljs-string">"onSubmit()"</span>&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex-grow-0 py-3 px-4 border-top"</span>&gt;
              &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"input-group"</span>&gt;
                &lt;input formControlName=<span class="hljs-string">"chat_message"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"form-control"</span> placeholder=<span class="hljs-string">"Type your message"</span>&gt;
                &lt;button [disabled]=<span class="hljs-string">"!chatForm.valid"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;Send&lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>From the code above, right above the <code>div</code> with the <code>position-relative</code> class, we declared the <code>@for (msg of this.chats(); track msg)</code> control flow, which does the following:</p>
<ul>
<li><p>Loops through the array returned by <code>this.chats()</code> which is the signal variable that was declared in the template.</p>
</li>
<li><p>Assigns each item in the array to the <code>msg</code> variable.</p>
</li>
<li><p>Tracks each item by identity <code>track msg</code> for DOM updates.</p>
</li>
</ul>
<p>Next, within the loop, you called the data in the appropriate HTML tag to display the image, the date the chat was created, the full name, and the chat message as well.</p>
<p>Finally, you created an <code>@empty</code> block which displays the message <code>No chats available</code> if there are no items in the array.</p>
<p>You should have the outcome below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749082870784/85184bf1-3cfe-4808-a149-64a6b925e097.png" alt="A chat interface displaying two messages from the user &quot;adedeji adesoga,&quot; dated June 4, 2025, with a text input box at the bottom." class="image--center mx-auto" width="1904" height="715" loading="lazy"></p>
<h2 id="heading-how-to-delete-data-in-the-angular-application">How to Delete Data in the Angular Application</h2>
<p>When creating the delete functionality, first you need to create a service function in the <code>chat-service.ts</code> file as seen below:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">async</span> deleteChat(id: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.from(<span class="hljs-string">'chat'</span>).delete().eq(<span class="hljs-string">'id'</span>, id);
    <span class="hljs-keyword">return</span> data;
  }
</code></pre>
<p>All the above function does is find the specific id provided from the parameter, and return the result of the delete operation.</p>
<p>Next, track the selected chat that was clicked from the array of listed chats and then pass the data down to your service.</p>
<p>To do this, first create a function within the service called <code>selectedChats()</code> which helps receive the data from the template:</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">public</span> savedChat = signal({});

 selectedChats(msg: Ichat) {
    <span class="hljs-built_in">this</span>.savedChat.set(msg);
  }
</code></pre>
<p>Above, we created the variable called <code>savedChat</code>. It’s declared as a signal that helps receive the object of the chat that we want to delete using the <code>set()</code> method.</p>
<p>You can now head to the <code>chat-component.ts</code> file to create the function that passed the data down to the <code>selectedChats()</code> function.</p>
<p>You can see this function below:</p>
<pre><code class="lang-typescript"> openDropDown(msg: Ichat) {
    <span class="hljs-built_in">console</span>.log(msg);
    <span class="hljs-built_in">this</span>.chat_service.selectedChats(msg);
  }
</code></pre>
<p>As you can see from the function above, once you bind it to the HTML element, it will make sure that you get the object of the specific chat that was clicked.</p>
<p>In our <code>chat-component.html</code> file, create a menu drop down that will help achieve this result as seen below:</p>
<pre><code class="lang-typescript">&lt;main&gt;
  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"container"</span>&gt;
    &lt;h3 <span class="hljs-keyword">class</span>=<span class="hljs-string">"mb-3"</span>&gt;Supa Chat &lt;button <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-secondary"</span> style=<span class="hljs-string">"float: right;"</span>&gt;Log
        out&lt;/button&gt;
    &lt;/h3&gt;
    &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"card"</span>&gt;
      &lt;div&gt;

        &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"col-12 col-lg-12 col-xl-12"</span>&gt;
          <span class="hljs-meta">@for</span> (msg <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.chats(); track msg) {
          &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"position-relative"</span>&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"chat-messages p-4"</span>&gt;
              &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"chat-message-left pb-4"</span>&gt;
                &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"me-5"</span>&gt;
                  &lt;img src={{msg?.users?.avatar_url}} <span class="hljs-keyword">class</span>=<span class="hljs-string">"rounded-circle mr-1"</span> alt=<span class="hljs-string">"image"</span> width=<span class="hljs-string">"40"</span> height=<span class="hljs-string">"40"</span>&gt;
                  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-muted small text-nowrap mt-2"</span>&gt;{{msg?.created_at | date: <span class="hljs-string">'M/d/yy, h:mm a'</span>}}&lt;/div&gt;
                &lt;/div&gt;
                &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex-shrink-1 bg-light rounded py-2 px-3 ml-3"</span>&gt;
                  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"font-weight-bold mb-1"</span>&gt;{{msg?.users?.full_name}}&lt;/div&gt;
                  {{msg?.text}}
                &lt;/div&gt;

                &lt;!-- Delete Modal Button Menu--&gt;
                &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"dropdown"</span>&gt;
                  &lt;span (click)=<span class="hljs-string">"openDropDown(msg)"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"mt-3 ms-5"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span> id=<span class="hljs-string">"dropdownMenuButton1"</span>
                    data-bs-toggle=<span class="hljs-string">"dropdown"</span> aria-expanded=<span class="hljs-string">"false"</span>&gt;
                    ...
                  &lt;/span&gt;
                  &lt;ul <span class="hljs-keyword">class</span>=<span class="hljs-string">"dropdown-menu"</span> aria-labelledby=<span class="hljs-string">"dropdownMenuButton1"</span>&gt;
                    &lt;li&gt;
                      &lt;a <span class="hljs-keyword">class</span>=<span class="hljs-string">"dropdown-item"</span> href=<span class="hljs-string">"#"</span> data-bs-toggle=<span class="hljs-string">"modal"</span> data-bs-target=<span class="hljs-string">"#exampleModal"</span>&gt;Delete&lt;/a&gt;
                    &lt;/li&gt;
                  &lt;/ul&gt;
                &lt;/div&gt;


              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
          } <span class="hljs-meta">@empty</span> {
          &lt;div&gt;No chats available&lt;/div&gt;
          }

          &lt;form [formGroup]=<span class="hljs-string">"chatForm"</span> (ngSubmit)=<span class="hljs-string">"onSubmit()"</span>&gt;
            &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"flex-grow-0 py-3 px-4 border-top"</span>&gt;
              &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"input-group"</span>&gt;
                &lt;input formControlName=<span class="hljs-string">"chat_message"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"form-control"</span> placeholder=<span class="hljs-string">"Type your message"</span>&gt;
                &lt;button [disabled]=<span class="hljs-string">"!chatForm.valid"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;Send&lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>From the above code, take note of the following: the comment with the text <code>&lt;!-- Delete Modal Button Menu--&gt;</code> is created within the <code>@for</code> control flow. This is essential because it allows the <code>openDropDown(msg)</code> to receive the right object as a parameter when the drop down menu is clicked. A quick look at the console will reveal this.</p>
<p>You can now create the delete modal component, which allows you to consume the delete service required for a chat to be deleted.</p>
<p>To create the delete component, use the command below:</p>
<pre><code class="lang-powershell">ng g component layout/modal<span class="hljs-literal">-component</span>
</code></pre>
<p>The design for the delete component is a Bootstrap 5 modal that looks like this:</p>
<pre><code class="lang-typescript">&lt;!-- Modal --&gt;
&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal fade"</span> id=<span class="hljs-string">"exampleModal"</span> tabindex=<span class="hljs-string">"-1"</span> aria-labelledby=<span class="hljs-string">"exampleModalLabel"</span> aria-hidden=<span class="hljs-string">"true"</span>&gt;
  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-dialog"</span>&gt;
    &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-content"</span>&gt;
      &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-header"</span>&gt;
        &lt;h5 <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-title"</span> id=<span class="hljs-string">"exampleModalLabel"</span>&gt;Modal title&lt;/h5&gt;
        &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn-close"</span> data-bs-dismiss=<span class="hljs-string">"modal"</span> aria-label=<span class="hljs-string">"Close"</span>&gt;&lt;/button&gt;
      &lt;/div&gt;
      &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-body"</span>&gt;
        Are really sure you want to <span class="hljs-keyword">delete</span> <span class="hljs-built_in">this</span> message?
      &lt;/div&gt;
      &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"modal-footer"</span>&gt;
        &lt;button <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-secondary"</span> data-bs-dismiss=<span class="hljs-string">"modal"</span>&gt;No&lt;/button&gt;
        &lt;button [attr.data-bs-dismiss]=<span class="hljs-string">"!this.dismiss() === true ? 'modal' : null"</span> (click)=<span class="hljs-string">"deleteChat()"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span>
          <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;Yes&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>If you paste the above code directly in your code editor, you’re going to get a host of errors because we have not created the <code>deleteChat()</code> function as well as the <code>dismiss()</code> signal variable in the template file. Let’s go ahead and do that.</p>
<p>The first step in setting up the <code>modal-componet.ts</code> file component is to import the appropriate modules as seen below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, effect, inject, signal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { ChatService } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/chat.service'</span>;
<span class="hljs-keyword">import</span> { Router } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
</code></pre>
<p>Next, inject the <code>chatservice</code>, <code>Angular router</code>, as well as the the <code>dismiss</code> variable which is a signal as seen below:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">private</span> chat_service = inject(ChatService);
  <span class="hljs-keyword">private</span> router = inject(Router);
  dismiss = signal(<span class="hljs-literal">false</span>);
</code></pre>
<p>With this you can now create the <code>deleteChat()</code> function as seen below:</p>
<pre><code class="lang-typescript">deleteChat() {
    <span class="hljs-keyword">const</span> id = (<span class="hljs-built_in">this</span>.chat_service.savedChat() <span class="hljs-keyword">as</span> { id: <span class="hljs-built_in">string</span> }).id;

    <span class="hljs-built_in">console</span>.log(id);

    <span class="hljs-built_in">this</span>.chat_service
      .deleteChat(id)
      .then(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">let</span> currentUrl = <span class="hljs-built_in">this</span>.router.url;

        <span class="hljs-built_in">this</span>.dismiss.set(<span class="hljs-literal">true</span>);

        <span class="hljs-built_in">this</span>.router
          .navigateByUrl(<span class="hljs-string">'/'</span>, { skipLocationChange: <span class="hljs-literal">true</span> })
          .then(<span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">this</span>.router.navigate([currentUrl]);
          });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(err);
        alert(err.message);
      });
  }
</code></pre>
<ul>
<li><p>The first thing we did under the <code>deleteChat()</code> method was to extract the <code>id</code> from the chat service.</p>
</li>
<li><p>This <code>id</code> is then passed into the <code>deleteChat()</code> method from our service which helps delete the specific chat that was selected.</p>
</li>
<li><p>Once the chat has been deleted, the current route gets reloaded to update the UI.</p>
</li>
</ul>
<p>To activate the modal, you need to import the Modal Component in the <code>chat-component.html</code> file (last line of code below:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>Supa Chat <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-secondary"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"float: right;"</span>&gt;</span>Log
        out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-12 col-lg-12 col-xl-12"</span>&gt;</span>
          @for (msg of this.chats(); track msg) {
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"position-relative"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-messages p-4"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"chat-message-left pb-4"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"me-5"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{{msg?.users?.avatar_url}}</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"rounded-circle mr-1"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"image"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"40"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"40"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-muted small text-nowrap mt-2"</span>&gt;</span>{{msg?.created_at | date: 'M/d/yy, h:mm a'}}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex-shrink-1 bg-light rounded py-2 px-3 ml-3"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"font-weight-bold mb-1"</span>&gt;</span>{{msg?.users?.full_name}}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                  {{msg?.text}}
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-comment">&lt;!-- Delete Modal Button Menu--&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dropdown"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">span</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"openDropDown(msg)"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-3 ms-5"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dropdownMenuButton1"</span>
                    <span class="hljs-attr">data-bs-toggle</span>=<span class="hljs-string">"dropdown"</span> <span class="hljs-attr">aria-expanded</span>=<span class="hljs-string">"false"</span>&gt;</span>
                    ...
                  <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dropdown-menu"</span> <span class="hljs-attr">aria-labelledby</span>=<span class="hljs-string">"dropdownMenuButton1"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dropdown-item"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">data-bs-toggle</span>=<span class="hljs-string">"modal"</span> <span class="hljs-attr">data-bs-target</span>=<span class="hljs-string">"#exampleModal"</span>&gt;</span>Delete<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>


              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          } @empty {
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>No chats available<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          }

          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> [<span class="hljs-attr">formGroup</span>]=<span class="hljs-string">"chatForm"</span> (<span class="hljs-attr">ngSubmit</span>)=<span class="hljs-string">"onSubmit()"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex-grow-0 py-3 px-4 border-top"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"input-group"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">formControlName</span>=<span class="hljs-string">"chat_message"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-control"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Type your message"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> [<span class="hljs-attr">disabled</span>]=<span class="hljs-string">"!chatForm.valid"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

<span class="hljs-comment">&lt;!-- modal --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">app-modal</span> /&gt;</span>
</code></pre>
<p><strong>NOTE</strong>: Don’t forget to import the <strong>ModalComponent</strong> file in the <code>chat-component.ts</code> file to avoid having any errors.</p>
<p>You have now implemented the ability to Insert, read, and delete data. The final implementation is to integrate the logout functionality.</p>
<h2 id="heading-how-to-implement-logout-functionality-in-the-angular-application">How to Implement Logout Functionality in the Angular Application</h2>
<p>Earlier in the tutorial, within the <code>auth-service.ts</code> file, you created a function called <code>signOut()</code> as seen below:</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">async</span> signOut() {
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.supabase.auth.signOut();
  }
</code></pre>
<p>In the <code>chat-component.ts</code> file, you will import and inject the <code>sigOut()</code> method. To do this, follow these steps:</p>
<ul>
<li><p>Import and inject the Angular router.</p>
</li>
<li><p>Import and inject the Authentication Service file</p>
</li>
<li><p>Create the <code>logOut()</code> function that consumes the <code>signOut()</code> service:</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> logOut() { 
<span class="hljs-built_in">this</span>.auth .signOut() .then(<span class="hljs-function">() =&gt;</span>
 { <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/login'</span>]); }) 
.catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
 alert(err.message);
 });
 }
</code></pre>
<ul>
<li>Finally in the <code>chat-component.html</code> file, within the button tag at the top of the page, call the <code>logout()</code> function using the <code>(click)</code> event handler:</li>
</ul>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-3"</span>&gt;</span>Supa Chat 
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> (<span class="hljs-attr">click</span>)=<span class="hljs-string">"logOut()"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-secondary"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"float: right;"</span>&gt;</span>Log Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
</code></pre>
<p>Once the Log Out button is clicked, the user gets navigated to the Login page and the user state gets reset in the browser.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you learned how to build a real-time chat application using Angular and Supabase. We covered the following key concepts:</p>
<ul>
<li><p>How to create database tables in Supabase</p>
</li>
<li><p>How to create triggers and functions in Supabase</p>
</li>
<li><p>How to use signals to manage state in an Angular</p>
</li>
<li><p>How to create authentication and authorization using Supabase and Google OAuth 2.0</p>
</li>
<li><p>How to work with Reactive forms in Angular</p>
</li>
</ul>
<p>and lots more.</p>
<p>You can access the full codebase by cloning the repository on <a target="_blank" href="https://github.com/desoga10/ng-chat-20">GitHub</a>.</p>
<p>If you found this article helpful, consider subscribing to my <a target="_blank" href="https://www.youtube.com/@TheCodeAngle">YouTube channel</a> where I share hands-on tutorials on modern web development technologies like JavaScript, HTML, CSS, Angular, Supabase, Firebase, React, Third party API and AI tools, and many more. Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Micro-Frontend Architecture Handbook ]]>
                </title>
                <description>
                    <![CDATA[ Over the years, in my role as a lead full-stack developer, solutions architect, and mentor, I’ve been immersed in the world of micro frontend architecture, working across different large-scale frontend projects where multiple teams, stacks, and deplo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/complete-micro-frontends-guide/</link>
                <guid isPermaLink="false">6842c120a3469a9ca728862b</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Andrew Maksimchenko ]]>
                </dc:creator>
                <pubDate>Fri, 06 Jun 2025 10:21:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748915817752/b35a8786-9aa7-46cd-a1d8-f82069470496.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the years, in my role as a lead full-stack developer, solutions architect, and mentor, I’ve been immersed in the world of micro frontend architecture, working across different large-scale frontend projects where multiple teams, stacks, and deployment pipelines had to coexist somehow.</p>
<p>As projects grew in complexity and teams worked in parallel across different stacks, it became clear that monolithic approaches couldn’t keep up. I needed practical tools that allowed easy cross-app interaction, independent deployability, better team autonomy, framework-agnosticism, and more. Some solutions worked elegantly in theory but struggled in real-world conditions. Others made things messier and more painful than helpful.</p>
<p>After diving deep into different paradigms—from iframes to Web Components, single-spa, Module Federation, Piral, Luigi, and hybrid setups—I even distilled my proven experience into a full-fledged online course on Udemy.</p>
<p>And today, in this comprehensive hands-on tutorial, I want to share my expertise and tell you more about micro-frontend architecture—method by method—with code, tradeoffs, visuals, and real-world insights.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-micro-frontends-for">What are Micro Frontends For?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-1-iframes-amp-cross-window-messaging">Method #1: Iframes &amp; Cross-Window Messaging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-2-web-components-custom-elements-shadow-dom">Method #2: Web Components (Custom Elements + Shadow DOM)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-3-single-spa-the-meta-framework-approach">Method #3: Single-SPA — The Meta-Framework Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-4-module-federation-sharing-code-at-runtime">Method #4: Module Federation - Sharing Code at Runtime</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-tools-amp-ecosystem-additions">Other Tools &amp; Ecosystem Additions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-are-micro-frontends-for">What are Micro Frontends For?</h2>
<p>In traditional frontend development, we often build single, monolithic apps—one codebase, one repo, one deployment pipeline, one team. It works great for small to medium projects, sometimes even for larger ones.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770222181/fb73c7ce-366f-4897-9ab7-b208c6e37cfa.png" alt="Monolith App Diagram - Three Features in React" class="image--center mx-auto" width="2218" height="1416" loading="lazy"></p>
<p>But challenges arise when:</p>
<ul>
<li><p>Your frontend codebase expands beyond 50+ components.</p>
</li>
<li><p>Multiple development teams need autonomy over different parts and tech stacks.</p>
</li>
<li><p>Different sections require varying deployment frequencies (weekly or monthly).</p>
</li>
<li><p>You need to integrate diverse frameworks, like combining React features with an Angular-based CMS.</p>
</li>
</ul>
<p>This is where micro frontends step in.</p>
<p>Micro frontends extend the principles of microservices to the frontend world. Instead of one big frontend app, you build independent frontend modules, each owned by a team, using its own tech stack, deployed separately, and integrated at runtime.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770253697/c78a8d84-a6a9-42af-90fd-423983c7ec77.png" alt="Micro-Frontends App Diagram - Three Apps in React, Angular, Vue" class="image--center mx-auto" width="2214" height="1424" loading="lazy"></p>
<p>Think of it like Lego blocks:</p>
<ul>
<li><p>Each block is similar to a self-contained micro frontend.</p>
</li>
<li><p>They plug into a shared layout or shell.</p>
</li>
<li><p>Each can evolve, update, or be replaced without affecting the others.</p>
</li>
</ul>
<p>For example, imagine that you’re building a modern e-commerce site, and here’s what your business side expects from you:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>Section</code></td><td><code>Team</code></td><td><code>Stack</code></td><td><code>Deployment</code></td></tr>
</thead>
<tbody>
<tr>
<td>Product Listing</td><td>Search Team</td><td>React</td><td>Weekly</td></tr>
<tr>
<td>Product Details</td><td>Catalog Team</td><td>Angular</td><td>Monthly</td></tr>
<tr>
<td>Cart &amp; Checkout</td><td>Checkout Team</td><td>Vue</td><td>Biweekly</td></tr>
<tr>
<td>CMS Pages</td><td>Marketing Team</td><td>Vanilla JS</td><td>Daily</td></tr>
</tbody>
</table>
</div><p>Each team wants autonomy, and with micro frontends, each of these sections becomes a separate app, loaded dynamically into a shell at runtime.</p>
<h3 id="heading-why-its-getting-popular">Why It’s Getting Popular?</h3>
<p>Here are a few things everyone considers:</p>
<ol>
<li><p><strong>Independent deployments</strong> – A little or no effort to coordinate every release.</p>
</li>
<li><p><strong>Team autonomy</strong> – Teams choose their own stack and tools on the project.</p>
</li>
<li><p><strong>Incremental upgrades</strong> – Migrate legacy apps piece by piece incrementally without the need to rewrite the whole app at once.</p>
</li>
<li><p><strong>Technical agnosticism</strong> – Vue, React, Angular? Doesn’t matter. They can all work together seamlessly at the same time in a single app.</p>
</li>
<li><p><strong>Better scalability</strong> – Parallelize work across teams to enable efficiency of delivery and scale at ease.</p>
</li>
</ol>
<p>Now let’s discover how we can bring this idea to life in our projects.</p>
<p>Nowadays, there are different ways to achieve that, but not all solutions are equal. The implementation method you choose will drastically affect:</p>
<ul>
<li><p>Developer experience</p>
</li>
<li><p>Bundle sizes and performance</p>
</li>
<li><p>SEO and accessibility</p>
</li>
<li><p>Runtime stability</p>
</li>
<li><p>Interoperability across stacks</p>
</li>
</ul>
<p>So let’s begin by exploring the oldest, but still surprisingly viable method.</p>
<h2 id="heading-method-1-iframes-amp-cross-window-messaging"><strong>Method #1: Iframes &amp; Cross-Window Messaging</strong></h2>
<p>You may ask, “Aren’t iframes bad?” They’re often misunderstood. While yes, iframes can feel clunky and isolated, they’re also the most secure and decoupled way to host micro frontends—especially when you don’t trust the team on the other side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770863603/9daefd01-22ac-413f-bf54-c339bb6e4e9e.png" alt="Micro-Frontend Method 1 - Iframes" class="image--center mx-auto" width="1772" height="964" loading="lazy"></p>
<h3 id="heading-what-is-an-iframe"><strong>What Is an IFRAME?</strong></h3>
<p>An <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe"><strong>iframe</strong></a> (inline frame) is an HTML element that allows you to embed another HTML page within your current webpage. The whole communication between apps is strictly based on events and delivered by means of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><strong>Post Message API.</strong></a></p>
<p>If you need to send data to another app, you simply call the <code>postMessage()</code> method on that element. On the other side, to receive a message, you just have to subscribe to the <code>message</code> event. That’s it.</p>
<h3 id="heading-real-world-example">Real-World Example</h3>
<p>Let’s see a simple example of two apps communicating with each other using <code>iframes</code> on two apps:</p>
<ul>
<li><p>The Main Web App</p>
</li>
<li><p>A Search App.</p>
</li>
</ul>
<p>Every iframe must be hosted somewhere to serve static content from it. It can be AWS Amplify, Digital Ocean, Heroku, GitHub Pages, or alike.</p>
<p>To help you out here, <a target="_blank" href="https://pages.github.com">here’s an official GitHub guideline</a> explaining how to host a website on their platform.</p>
<p>Let’s say you deployed a Search App on Github Pages and you were given this URL to host your app: <a target="_blank" href="https://search.example.com"><code>https://example.github.io</code></a>. Now let’s write some content for it.</p>
<p>Assuming that you want to post messages from the Search App to the Main Web App, and to subscribe to the incoming messages from it there. You can do it in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Initializing Search App...'</span>);

<span class="hljs-comment">// Subscribe to messages from outside the iframe (like Main Web App)</span>
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (event.data?.type === <span class="hljs-string">'init'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Main Web App passed userId:'</span>, event.data.userId);
  }
});

<span class="hljs-comment">// Simulate sending Search results back to Main Web App</span>
<span class="hljs-built_in">window</span>.parent.postMessage({
  <span class="hljs-attr">type</span>: <span class="hljs-string">'searchResult'</span>,
  <span class="hljs-attr">payload</span>: [<span class="hljs-string">'Item A'</span>, <span class="hljs-string">'Item B'</span>]
}, <span class="hljs-string">'*'</span>);
</code></pre>
<p>Here, you initialize the search app and set up two-way communication with a parent application (such as a main web app) using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><strong>Post Message API</strong></a>. You listen for incoming messages using the built-in <code>message</code> event. Once received, that message becomes available in the <code>event.data</code> object. Finally, you simulate sending data back to the parent by posting a <code>searchResult</code> message containing a list of items. This setup enables isolated iframe-based apps to communicate safely with the main shell application.</p>
<p>Then, in the DOM of the main web app<strong>,</strong> you need to include the iframe that will render the search app, specifying the URL to the hosted search app in this way:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span>
  <span class="hljs-attr">id</span>=<span class="hljs-string">"search-mfe"</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"https://example.github.io"</span>
  <span class="hljs-attr">style</span>=<span class="hljs-string">"width: 100%; height: 200px; border: none;"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>Styles were added here to ensure that the <code>iframe</code> displays seamlessly within the layout for a cleaner UI integration.</p>
<p>And now you can pass some content from the main web app down to the search app and get some messages from it. You can accomplish it in the main web app’s JavaScript code in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Initializing Main Web App...'</span>);

<span class="hljs-keyword">const</span> iframe = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'search-mfe'</span>);
iframe.onload = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Send message to child iframe (inputs)</span>
  iframe.contentWindow.postMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'init'</span>, <span class="hljs-attr">userId</span>: <span class="hljs-number">42</span> }, <span class="hljs-string">'*'</span>);
};

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-comment">// Receive data from the Search App (outputs)</span>
  <span class="hljs-keyword">if</span> (event.data?.type === <span class="hljs-string">'searchResult'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Received result from Search App: '</span>, event.data.payload);
  }
});
</code></pre>
<p>As you see, when the <code>iframe</code> loads, the <code>init</code> event is sent to the search app (the <code>type</code> can be anything you want, just ensure it matches the one that another app expects from you). And then, in the <code>message</code> event handler as before, you can receive the incoming messages from the search app, and do something with them.</p>
<p>Here are a few pros and cons to consider, along with popular use cases:</p>
<h3 id="heading-pros"><strong>✅ Pros:</strong></h3>
<ul>
<li><p><strong>Strong sandboxing</strong>: No shared memory, no shared styles.</p>
</li>
<li><p><strong>Zero dependency clashes</strong>: One iframe is equivalent to one environment.</p>
</li>
<li><p><strong>Perfect for legacy</strong>: Easy to wrap old apps in an iframe.</p>
</li>
<li><p><strong>Practical</strong> for micro-apps in PHP, Java, Razor (ASP.NET)</p>
</li>
</ul>
<h3 id="heading-cons"><strong>❌ Cons:</strong></h3>
<ul>
<li><p>Slow rendering</p>
</li>
<li><p>Difficult shared navigation</p>
</li>
<li><p>Inconsistent/complicated styling</p>
</li>
<li><p>Complex communication</p>
</li>
<li><p>Must be hosted somewhere</p>
</li>
</ul>
<h3 id="heading-popular-use-cases"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<ul>
<li><p>Embedding legacy dashboards (for example, old AngularJS or Java apps)</p>
</li>
<li><p>Secure cross-domain apps (for example, payments, 3rd party analytics)</p>
</li>
<li><p>Highly untrusted integrations</p>
</li>
<li><p>Embedded Ads</p>
</li>
</ul>
<p>But if you want a more fluid UX, shared components, and a smoother dev experience, you’ll want something better. That brings us to Web Components.</p>
<h2 id="heading-method-2-web-components-custom-elements-shadow-dom"><strong>Method #2: Web Components (Custom Elements + Shadow DOM)</strong></h2>
<blockquote>
<p>“What if you could ship a self-contained natively understood widget that works in any framework — React, Vue, Angular, or plain HTML?”</p>
</blockquote>
<p>That’s exactly what <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">Web Components</a> make possible. They’re natively built into the browser as an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">API</a>, you don’t need a framework or extra dependency. They allow you to create reusable, scalable, encapsulated UI elements that work just like native HTML tags.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748773939725/8b017162-96a8-449d-b9b8-5fe8ef382e91.png" alt="Micro-Frontend Method 2 - Web Components" class="image--center mx-auto" width="1974" height="1414" loading="lazy"></p>
<p>Moreover, you can easily use them as wrappers around any elements from other UI frameworks (React, Angular, Svelte, etc) and use your framework-based components as regular native DOM elements in any web application.</p>
<p>They are, in many ways, the ideal foundation for micro frontends.</p>
<p>A web component is made of:</p>
<ul>
<li><p><strong>Custom Element</strong> - defines your own HTML tag (&lt;user-profile&gt;) and behavior</p>
</li>
<li><p><strong>Shadow DOM</strong> – provides scoped, encapsulated styles and DOM structure</p>
</li>
<li><p><strong>HTML Template</strong> – brings reusable HTML blocks/fragments</p>
</li>
<li><p><strong>Slots</strong> – acts as placeholder areas for host content (used in content projection)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748772947093/6090d9bb-2c10-4a92-9ece-c5235b8382a2.png" alt="Micro-Frontend Method 2 - Web Components Key Blocks" class="image--center mx-auto" width="2740" height="1220" loading="lazy"></p>
<p>In web components<strong>,</strong> you have to sync the data (input/output) via:</p>
<ul>
<li><p><strong>Attributes</strong> (inputs):</p>
<ul>
<li><p>In Javascript: <code>element.setAttribute()</code>, <code>element.getAttribute()</code>, and so on.</p>
</li>
<li><p>In HTML: <code>&lt;element attr1=”value1” attr2=”value2”&gt;&lt;/element&gt;</code></p>
</li>
</ul>
</li>
<li><p><strong>Properties</strong> (inputs) – <code>element.someProp = value</code> (only Javascript)</p>
</li>
<li><p><strong>Custom Events</strong> (outputs) - <code>new CustomEvent('name', data)</code></p>
</li>
</ul>
<p>First, let me show you a basic implementation of a web component, and then you’ll learn how to leverage it for micro-frontends.</p>
<p>Assuming that you’re building a reusable product-tile component that must:</p>
<ul>
<li><p>Accept one input parameter – <code>“title”</code></p>
</li>
<li><p>Send an output event <code>"add-to-cart"</code> with this <code>“title”</code> to the outside world, when the component is mounted to the DOM.</p>
</li>
</ul>
<p>Here’s how this web component could look:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-tile.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductTile</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'title'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>
      <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
      <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
      <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
      shadow.innerHTML = <span class="hljs-string">`&lt;div id="title"&gt;&lt;/div&gt;`</span>;
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called when the custom element ProductTile is added to the DOM</span>
  connectedCallback() {
      <span class="hljs-comment">// When added to the DOM, read and render the title attribute</span>
      <span class="hljs-keyword">const</span> title = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'title'</span>) ?? <span class="hljs-string">'Unnamed Product'</span>;
      <span class="hljs-built_in">this</span>.updateTitle(title);

      <span class="hljs-comment">// Dispatch a custom event with the current title</span>
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'add-to-cart'</span>, {
          <span class="hljs-attr">detail</span>: { title },
          <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>,
      });

      <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called whenever observed attributes change.</span>
  <span class="hljs-comment">// In our case it's "title" only</span>
  attributeChangedCallback(name, oldValue, newValue) {
      <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'title'</span> &amp;&amp; oldValue !== newValue) {
          <span class="hljs-built_in">this</span>.updateTitle(newValue);
      }
  }

  <span class="hljs-comment">// Internal method to safely update the title content</span>
  updateTitle(title) {
      <span class="hljs-keyword">const</span> titleElem = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#title'</span>);
      titleElem.textContent = title;
  }
}

customElements.define(<span class="hljs-string">'product-tile'</span>, ProductTile);
</code></pre>
<p>Now, let me explain what’s happening here:</p>
<ul>
<li><p>First, you create a custom element class that extends from <code>HTMLElement</code> or its children. This gives you access to web component lifecycle hooks and DOM integration capabilities.</p>
</li>
<li><p>If you want to react to changes in input parameters (attributes), you have to define a static <code>observedAttributes()</code> getter that returns a list of attribute names to watch. In our case, we observe <code>“title”</code>.</p>
</li>
<li><p>Then, in the constructor:</p>
<ul>
<li><p>Call <code>super()</code> to properly inherit from <code>HTMLElement</code>.</p>
</li>
<li><p>Create a shadow DOM using <code>attachShadow({ mode: 'open' })</code>. This encapsulates your component’s internal DOM and styles. You can even use a <code>closed</code> mode here to add a higher level of isolation to the shadow DOM.</p>
</li>
<li><p>Then, populate the shadow DOM with minimal inner HTML—in this case, a <code>&lt;div&gt;</code> element that will later display the product title.</p>
</li>
</ul>
</li>
<li><p>When the component is added to the DOM, the built-in <code>connectedCallback()</code> lifecycle reaction runs:</p>
<ul>
<li><p>It reads the current value of the <code>"title"</code> attribute.</p>
</li>
<li><p>Updates the UI with an initial value in the <code>"title"</code> attribute.</p>
</li>
<li><p>Then it dispatches a custom event named <code>"add-to-cart"</code>, passing the <code>"title"</code> as detail down to it. The events are <code>bubbles: true</code> and <code>composed: true</code>, so that parent elements or host apps outside the shadow DOM can subscribe to it and catch it.</p>
</li>
</ul>
</li>
<li><p>When the title attribute changes at runtime, another built-in lifecycle reaction named <code>attributeChangedCallback()</code> runs automatically:</p>
<ul>
<li><p>It checks the new value and updates the <code>"title"</code> display accordingly.</p>
</li>
<li><p>This enables reactive behavior in the component—similar to input bindings in UI frameworks.</p>
</li>
</ul>
</li>
<li><p>Finally, you register the component globally using <code>customElements.define()</code> method (it’s available in the global <code>window</code> object), giving it:</p>
<ul>
<li><p>A tag name of <code>&lt;product-tile&gt;</code> that can be used anywhere in HTML.</p>
</li>
<li><p>A <code>reference</code> to the custom element you previously created to associate one with another.</p>
</li>
</ul>
</li>
</ul>
<p>Ultimately, here’s how you can use this component in your apps, which will work in vanilla JS, React, Angular, Svelte, Vue, whatever UI framework you choose:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">product-tile</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Coffee Mug"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">product-tile</span>&gt;</span>
</code></pre>
<p>And then you can listen to the <code>"add-to-cart"</code> event from inside <code>ProductTile</code> component like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> elem = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'product-tile'</span>);
elem.addEventListener(<span class="hljs-string">'add-to-cart'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Add to cart!'</span>, e.detail);
});
</code></pre>
<p>As you see, no <code>ReactDOM.render</code>, no <code>NgModule</code>, no extra glue. Everything is entirely native, pure <strong>JavaScript</strong> code that browsers understand.</p>
<p>And now, due to the Shadow DOM and other Web Components’ features, you can easily wrap and embed any web app written in a different framework into the Shadow Tree that will isolate your app entirely and won’t allow its layout or styles to leak out.</p>
<p>Alternatively, if you decide to publish it as a separate npm package (for example, <code>@webcomp/product-tile</code>), you can even dynamically import and mount the Web Component like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span>(<span class="hljs-string">'@webcomp/product-tile'</span>).then(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Now &lt;product-tile&gt; is defined — you can create and use it</span>
  <span class="hljs-keyword">const</span> elem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'product-tile'</span>);
  elem.setAttribute(<span class="hljs-string">'title'</span>, <span class="hljs-string">'Wireless Mouse'</span>);
  <span class="hljs-built_in">document</span>.body.appendChild(elem);
});
</code></pre>
<p>Or load from CDN or any hosting provider:</p>
<pre><code class="lang-jsx">&lt;script type=<span class="hljs-string">"module"</span> src=<span class="hljs-string">"https://example.github.io/product-tile.js"</span>&gt;&lt;/script&gt;
</code></pre>
<p>It’s simple, clean, and independent.</p>
<p>But you’re not here just for that, right? :) Now, let’s learn the real power of Web Components in a micro-frontends world!</p>
<h3 id="heading-micro-frontends-with-web-components"><strong>Micro-Frontends with Web Components</strong></h3>
<p>Imagine that you’ve built a Video Player in React—or perhaps want to reuse one from another team. Now the question is: How can you make this React-based player usable in any other frontend application, regardless of its underlying framework, using Web Components?</p>
<p>Let’s figure it out!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748785841227/e58d9ffd-3098-4652-ae52-a55ab218c8fd.png" alt="Micro-Frontend Method 2 - Web Components - Real World Example" class="image--center mx-auto" width="2066" height="1320" loading="lazy"></p>
<p>Let’s say, this video player:</p>
<ul>
<li><p>Accepts <code>src</code> and <code>controls</code> as inputs</p>
</li>
<li><p>Emits events: <code>play</code> and <code>pause</code> as outputs</p>
</li>
<li><p>Can be used in any app via <code>&lt;magic-player&gt;</code> in this way:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">magic-player</span>
    <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.example.com/video.mp4"</span>
    <span class="hljs-attr">controls</span>=<span class="hljs-string">"true"</span>
  &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">magic-player</span>&gt;</span>
</code></pre>
</li>
</ul>
<p>Now let’s get to implementation!</p>
<p><strong>🔹</strong> <strong>Step #1: Include your React player in the project</strong></p>
<p>Here, you can play around with any React component of your choice, to be honest, or you can just use a simple React Video Player like the one below:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ReactVideoPlayer.jsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ReactVideoPlayer</span>(<span class="hljs-params">{ src, controls, onPlay, onPause }</span>) </span>{
  <span class="hljs-keyword">return</span> (
      <span class="hljs-comment">// HTML5 video element with full width and controls enabled</span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span>
      <span class="hljs-attr">controls</span>=<span class="hljs-string">{controls}</span>  {/* <span class="hljs-attr">Enable</span> / <span class="hljs-attr">Disable</span> <span class="hljs-attr">controls</span> */}
      <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{onPlay}</span>      {/* <span class="hljs-attr">Callback</span> <span class="hljs-attr">for</span> <span class="hljs-attr">play</span> <span class="hljs-attr">event</span> */}
      <span class="hljs-attr">onPause</span>=<span class="hljs-string">{onPause}</span>    {/* <span class="hljs-attr">Callback</span> <span class="hljs-attr">for</span> <span class="hljs-attr">pause</span> <span class="hljs-attr">event</span> */}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{src}</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video/mp4"</span> /&gt;</span>
      Your browser does not support the video tag.
    <span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>🔹</strong> <strong>Step #2: Create the Web Component Wrapper</strong></p>
<p>Now, you need to create a Web Component wrapper around this React player app by mounting it into the shadow DOM of a custom element in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.js</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p>Then you need to add inputs and outputs like so:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.js</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'src'</span>, <span class="hljs-string">'controls'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }

  <span class="hljs-comment">// Helper-like method to dispatch native-like events (our outputs)</span>
  <span class="hljs-comment">// In our case, it will be triggered for "onPlay" and "onPause" events</span>
  dispatch(eventName, detail = {}) {
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(eventName, {
      detail,            <span class="hljs-comment">// Pass custom data ("onPlay" or "onPause")</span>
      <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// Allow event to bubble up</span>
      <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>     <span class="hljs-comment">// Allow it to cross the Shadow DOM boundary</span>
    });
    <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p>And lastly, add two built-in lifecycle reactions to render a React video player app when the page loads and every time the inputs change:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.jsx</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'src'</span>, <span class="hljs-string">'controls'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadow.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }

  <span class="hljs-comment">// Helper-like method to dispatch native-like events (our outputs)</span>
  <span class="hljs-comment">// In our case, it will be triggered for "onPlay" and "onPause" events</span>
  dispatch(eventName, detail = {}) {
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(eventName, {
      detail,            <span class="hljs-comment">// Pass custom data ("onPlay" or "onPause")</span>
      <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// Allow event to bubble up</span>
      <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>     <span class="hljs-comment">// Allow it to cross the Shadow DOM boundary</span>
    });
    <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called when the custom element &lt;magic-player&gt; is added to the DOM</span>
  connectedCallback() {
    <span class="hljs-built_in">this</span>.render();
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called whenever observed attributes change.</span>
  <span class="hljs-comment">// In our case it's "src" and "controls"</span>
  attributeChangedCallback() {
    <span class="hljs-built_in">this</span>.render();
  }

  <span class="hljs-comment">// Render the React player inside the container</span>
  render() {
    <span class="hljs-keyword">const</span> src = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'src'</span>);
    <span class="hljs-keyword">const</span> controls = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'controls'</span>) === <span class="hljs-string">'true'</span>;
    <span class="hljs-keyword">const</span> mount = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#react-video-player'</span>);

    ReactDOM.createRoot(mount).render(
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ReactVideoPlayer</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">{src}</span>
        <span class="hljs-attr">controls</span>=<span class="hljs-string">{controls}</span>
        <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{()</span> =&gt;</span> this.dispatch('play')}
        onPause={() =&gt; this.dispatch('pause')}
      /&gt;</span>
    );
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p><strong>🔹</strong> <strong>Step #3: Connect your React-Player to any UI framework:</strong></p>
<p>Then, in the main web app (whatever UI framework you’re using there). We put our newly created React video player wrapper in any place in the DOM, passing down initial attributes (inputs) to it:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Use your new React-based player anywhere! --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">magic-player</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.example.com/movie.mp4"</span>
  <span class="hljs-attr">controls</span>=<span class="hljs-string">"true"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">magic-player</span>&gt;</span>
</code></pre>
<p>And then you can easily subscribe to the custom events (outputs) from inside the React app:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Listen to native-style events from the custom element</span>
<span class="hljs-keyword">const</span> magicPlayer = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'magic-player'</span>);
magicPlayer.addEventListener(<span class="hljs-string">'play'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Video has started playing!'</span>);
});

magicPlayer.addEventListener(<span class="hljs-string">'pause'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Video has been paused.'</span>);
});
</code></pre>
<p>That’s it! Now, try to accomplish the same with a different <strong>UI framework</strong>!</p>
<h3 id="heading-pros-1"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Framework-agnostic:</strong> Works in React, Angular, Vue, Svelte, or even plain HTML — no rewrites needed</p>
</li>
<li><p><strong>Natively supported by browsers:</strong> No need for external libraries or frameworks — just HTML, JS, and CSS.</p>
</li>
<li><p>No extra configuration or hosting needed as in iframes. But still, components can be published to npm/CDNs and reused across multiple apps.</p>
</li>
<li><p><strong>Intuitive &amp; easy communication:</strong> Expose native DOM attributes as inputs and native custom events as outputs.</p>
</li>
<li><p><strong>SSR-friendly with hydration:</strong> It supports serialization, declarative shadow DOM, and can be server-rendered and hydrated, especially using modern tools.</p>
</li>
<li><p><strong>Supports Accessibility</strong> (ARIA attributes and roles).</p>
</li>
</ul>
<h3 id="heading-cons-1"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Integration Difficulties</strong>: If you want to bridge two apps in different technical stacks, you need to properly manage their communication in a custom element wrapper and its shadow DOM.</p>
</li>
<li><p><strong>Limited Support for old Browsers</strong>: If you need compatibility with legacy browsers like Internet Explorer 10, Web Components need a polyfill. But here’s a popular repository with all polyfills for Web Components: <a target="_blank" href="https://github.com/webcomponents/polyfills">https://github.com/webcomponents/polyfills</a></p>
</li>
<li><p><strong>Global State Isolation</strong>: There’s no built-in way to share state across components. You’ll need to implement your own global bus or event bridge using <code>CustomEvents</code> or alike.</p>
</li>
</ul>
<h3 id="heading-popular-use-cases-1"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<ul>
<li><p>Reusable Design systems &amp; UI libraries</p>
</li>
<li><p>Micro frontends inside framework apps</p>
</li>
<li><p>Legacy integration to modern stack and vice versa</p>
</li>
<li><p>Cross-team component delivery</p>
</li>
<li><p>CDN-based plug-and-play UIs</p>
</li>
</ul>
<p>The Web Components API has many more possibilities and power. So, if you want, you can go deeper and advance your knowledge by passing any available free course on freeCodeCamp or passing the one I’ve built myself around this technique on Udemy.</p>
<p>Now let’s move on!</p>
<h2 id="heading-method-3-single-spa-the-meta-framework-approach"><strong>Method #3: Single</strong>-SPA — The Meta-<strong>Framework Approach</strong></h2>
<blockquote>
<p>“What if instead of embedding micro frontends as Web Components or iframes, we had a system that orchestrated multiple SPAs together in one layout?”</p>
</blockquote>
<p>That’s what <a target="_blank" href="https://single-spa.js.org/">single-spa</a> is all about. It’s not a rendering library, it’s a runtime JavaScript router and orchestrator for micro frontends.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748788736898/90800e32-f8d0-4fc5-aedb-e7ce8d753c4c.png" alt="Micro-Frontend Method 3 - Single SPA" class="image--center mx-auto" width="3024" height="1457" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://single-spa.js.org/">https://single-spa.js.org</a></p>
</blockquote>
<h3 id="heading-what-is-single-spa"><strong>What Is</strong> single-spa<strong>?</strong></h3>
<p>single-spa (Single Page Application) lets you build and run multiple independent SPAs (React, Vue, Angular, and so on) inside one webpage. Each SPA is responsible for part of the UI and is loaded dynamically depending on the current route.</p>
<p>In short, it’s a <strong>framework</strong> that:</p>
<ul>
<li><p>Loads your micro frontends when needed</p>
</li>
<li><p>Mounts/unmounts them cleanly</p>
</li>
<li><p>Coordinates routing and lifecycles</p>
</li>
<li><p>Supports different frameworks in the same app.</p>
</li>
</ul>
<h3 id="heading-real-life-example"><strong>Real-Life Example</strong></h3>
<p>Let’s say you have this route breakdown:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>Path</code></td><td><code>Micro Frontend App</code></td><td><code>Stack</code></td><td><code>App Name</code></td></tr>
</thead>
<tbody>
<tr>
<td>/products</td><td>Product Listing App</td><td>React</td><td><code>@shop/products</code></td></tr>
<tr>
<td>/checkout</td><td>Checkout App</td><td>Vue</td><td><code>@shop/checkout</code></td></tr>
<tr>
<td>/account</td><td>Account Dashboard</td><td>Angular</td><td><code>@shop/account</code></td></tr>
</tbody>
</table>
</div><p>Each one is a <strong>fully independent SPA</strong>, and single-spa loads them as needed.</p>
<p><strong>🔹</strong> <strong>Step #1:</strong> single-spa <strong>installation</strong></p>
<p>First, you need to install the single-spa as a dependency for your project:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a new project (if it's not yet)</span>
npm init

<span class="hljs-comment"># Install Single SPA</span>
npm install single-spa systemjs
</code></pre>
<p>Notice that we also installed the <code>systemjs</code> package. This package is responsible for the dynamic runtime module loading that makes Single-SPA work seamlessly. It uses <code>SystemJS</code> as a module loader to allow micro frontends to be:</p>
<ol>
<li><p>Loaded at runtime</p>
</li>
<li><p>Independently deployed</p>
</li>
<li><p>Framework-agnostic</p>
</li>
<li><p>Lazy-loaded only when needed</p>
</li>
</ol>
<p>Now you need to implement each micro-app. For instance, let’s see how the <code>@shop/products</code> app written in React could be managed.</p>
<p><strong>🔹 Step #2: Project Structure</strong></p>
<p>The project structure for each micro app can look like this:</p>
<pre><code class="lang-apache"><span class="hljs-attribute">shop</span>/products/
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">root</span>.component.jsx
│   └── <span class="hljs-attribute">index</span>.single-spa.js
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html
├── <span class="hljs-attribute">package</span>.json
└── <span class="hljs-attribute">webpack</span>.config.js
</code></pre>
<p><strong>🔹 Step #3: Root Micro App Component</strong></p>
<p>The <code>root.component.jsx</code> file represents the root of the React app that will be mounted to the main DOM using single-spa. Here’s a simple example:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/root.component.jsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>', <span class="hljs-attr">border:</span> '<span class="hljs-attr">1px</span> <span class="hljs-attr">solid</span> #<span class="hljs-attr">ccc</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>🛍 Product Micro App<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a micro frontend powered by React + Single-SPA!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>🔹</strong> <strong>Step #4: Set Up Lifecycle Hooks</strong></p>
<p>Also, each Micro App in single-spa requires an entry point with at least three core functions/lifecycle hooks. For that purpose, you will need a separate file, which you can name as <code>index.single-spa.js</code> and it will provide the implementation of those hooks, like:</p>
<ul>
<li><p><code>bootstrap()</code> - Called when the micro app is launched by the main app (Shell) before mounting to the DOM</p>
</li>
<li><p><code>mount()</code> - Called when the app is attached to the host in the DOM</p>
</li>
<li><p><code>unmount()</code> - Called when the app is removed/detached from the DOM</p>
</li>
</ul>
<p>And here’s an example of what they could look like:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/index.single-spa.js</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> Root <span class="hljs-keyword">from</span> <span class="hljs-string">'./root.component.jsx'</span>;

<span class="hljs-comment">// Hold the React root instance for reuse</span>
<span class="hljs-keyword">let</span> root = <span class="hljs-literal">null</span>;

<span class="hljs-comment">// Called once when the micro frontend is first initialized</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve();
}

<span class="hljs-comment">// Called every time the route matches and the app should appear</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mount</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'product-container'</span>) || createContainer();
    root = ReactDOM.createRoot(container);
    root.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Root</span> /&gt;</span></span>);
  });
}

<span class="hljs-comment">// Called when the route no longer matches (cleanup)</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">unmount</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (root) {
      root.unmount();
    }
  });
}

<span class="hljs-comment">// Create a container div if it doesn't exist</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createContainer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> div = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
  div.id = <span class="hljs-string">'product-container'</span>;
  <span class="hljs-built_in">document</span>.body.appendChild(div);
  <span class="hljs-keyword">return</span> div;
}
</code></pre>
<p>As you see, you have to resolve a Promise in all lifecycle hooks and ensure the React app is mounted and unmounted properly based on the React best practices.</p>
<p><strong>🔹</strong> <strong>Step #5: Configuring Webpack for SystemJS</strong></p>
<p>Also, each micro-app in single-spa needs a separate configuration. For that, you will include a <code>webpack.config.js</code> file, specifying how to build the app (<code>output</code>), where to host it (<code>publicPath</code>), and so on.</p>
<p>Since single-spa uses the <code>SystemJS</code> package, the <code>libraryTarget</code> will be <code>system</code> for all micro apps.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">externals</span>: {
    <span class="hljs-attr">react</span>: <span class="hljs-string">'React'</span>,
    <span class="hljs-string">'react-dom'</span>: <span class="hljs-string">'ReactDOM'</span>,
  },
  <span class="hljs-attr">output</span>: {
    <span class="hljs-attr">filename</span>: <span class="hljs-string">'products.js'</span>,
    <span class="hljs-attr">libraryTarget</span>: <span class="hljs-string">'system'</span>, <span class="hljs-comment">// SystemJS-compatible format</span>
    <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'http://localhost:8500/'</span>, <span class="hljs-comment">// Host location of this micro app</span>
  },
};
</code></pre>
<p>This app will be hosted on the <a target="_blank" href="http://localhost:8500"><code>localhost:8500</code></a>. For production, you will have to use any suitable hosting provider (like the ones described in the iframes section).</p>
<p><strong>🔹</strong> <strong>Step #6: Registering the Micro App in Root-Config</strong></p>
<p>Next, it’s time to register a new micro-app in the Singla-SPA root config. Here’s how you can do it:</p>
<p>Create a <code>root-config.js</code> file in the root of the project and fill it with this content:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// root-config.js (host shell)</span>
<span class="hljs-keyword">import</span> { registerApplication, start } <span class="hljs-keyword">from</span> <span class="hljs-string">'single-spa'</span>;

registerApplication({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'@shop/products'</span>,
  <span class="hljs-attr">app</span>: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@shop/products'</span>),
  <span class="hljs-attr">activeWhen</span>: [<span class="hljs-string">'/products'</span>],
});

start(); <span class="hljs-comment">// Initializes routing and micro app lifecycles</span>
</code></pre>
<p>First, you have to register the application, and then you start it to enable routing and the micro app lifecycle. The registration for other micro apps will look the same.</p>
<p><strong>Note</strong>: <code>System.import()</code> is part of <code>SystemJS</code>, used by default in single-spa for loading remote apps.</p>
<p>Also, single-spa comes with so-called "Parcels" – a lower-level construct in comparison to applications. They’re essentially self-contained pieces of UI that you can dynamically mount anywhere. Think of them like “mini microfrontends” or reusable widgets that don’t control routing:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Example</span>
mountParcel(SomeParcelComponent, { <span class="hljs-attr">domElement</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'micro-app'</span>) });
</code></pre>
<p>You’d use them when:</p>
<ul>
<li><p>You don’t want the parcel to own a route.</p>
</li>
<li><p>You need to inject a micro frontend dynamically inside another one.</p>
</li>
<li><p>You want encapsulated logic (like a widget) embedded within a larger app.</p>
</li>
</ul>
<p>In all other cases, prefer the usage of a <code>registerApplication(...)</code> function.</p>
<p><strong>🔹</strong> <strong>Step #7: Adding Micro App to SystemJS Import Map</strong></p>
<p>The last step is to register the micro app in <code>SystemJS</code>. For that, in your root <code>index.html</code> file, you need to add the following two scripts:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- public/index.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Micro Frontend Shell<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/products"</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> |
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/checkout"</span>&gt;</span>Checkout<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Import maps handled by bundler or injected at runtime --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"systemjs-importmap"</span>&gt;</span><span class="javascript">
    {
      <span class="hljs-string">"imports"</span>: {
        <span class="hljs-string">"@shop/root-config"</span>: <span class="hljs-string">"http://localhost:9000/root-config.js"</span>,
        <span class="hljs-string">"@shop/products"</span>: <span class="hljs-string">"http://localhost:8500/products.js"</span>,
        <span class="hljs-comment">// other micro apps</span>
      }
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Start the root-config application --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    System.import(<span class="hljs-string">'@shop/root-config'</span>);
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>First, you have to add a script with an import map declaration. As you see, it represents a JSON where:</p>
<ul>
<li><p>Each key is the micro app name and</p>
</li>
<li><p>Each value is the URL where the main JS file (from the bundle) actually lives</p>
</li>
</ul>
<p>Note that we’ve added the <code>@shop/root-config</code> here to the import map to tell <code>SystemJS</code> where to fetch the main JavaScript file for the main/shell app so it knows how to resolve and execute <code>System.import('@shop/root-config')</code> properly.</p>
<p>Secondly, you include another script to start the main / shell application. It executes the JS file you just mapped in the import map above. Treat it as the real “boot” of your shell app:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  System.import(<span class="hljs-string">'@shop/root-config'</span>);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>That’s it! Now go ahead and try doing the same with other micro-apps in Vue (Checkout App) and Angular (Account Dashboard).</p>
<p>Here’s a simple diagram illustrating this connection:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748789553598/4729600f-54d7-4d72-97e7-462093cf08b5.png" alt="Micro-Frontend Method 3 - Single SPA - Real World Example" class="image--center mx-auto" width="1232" height="882" loading="lazy"></p>
<p>Now that you’ve registered and integrated your first micro app, you might be wondering if this approach right for you. Let’s quickly look at the benefits and limitations of using single-spa in production.</p>
<h3 id="heading-pros-2"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Built-in Routing &amp; Lifecycles</strong> - No need to reinvent navigation or mounting logic</p>
</li>
<li><p><strong>Cross-framework support</strong> - React, Vue, Angular can all co-exist</p>
</li>
<li><p><strong>Fine-grained loading</strong> - Only load the active app (lazy and efficient)</p>
</li>
<li><p><strong>Flexible project structure</strong> - can be monorepo or polyrepo</p>
</li>
<li><p><strong>Good CLI tooling -</strong> create and link MFEs with create-single-spa &amp; helpers</p>
</li>
</ul>
<h3 id="heading-cons-2"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Complex learning curve</strong> - Lifecycle APIs and <code>SystemJS</code> can be intimidating</p>
</li>
<li><p><strong>Configurations</strong> <strong>can get verbose</strong> – Managing multiple registries, import maps, deployment URLs, and lifecycle wrappers across apps adds setup overhead</p>
</li>
<li><p><strong>Shared state is manual</strong> - You must implement custom global state solutions</p>
</li>
<li><p><strong>Hard to SSR</strong> - Designed for full client-side rendering</p>
</li>
<li><p><strong>More boilerplate</strong> - Each app needs wrappers for lifecycles, routing, and so on.</p>
</li>
<li><p><strong>Global styles leak -</strong> No default encapsulation like Shadow DOM</p>
</li>
</ul>
<p>And a few popular <strong>use cases</strong> for it:</p>
<h3 id="heading-popular-use-cases-2"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<p>You can use single-spa when:</p>
<ul>
<li><p>You want a central router managing all micro frontends</p>
</li>
<li><p>Teams are using different frameworks</p>
</li>
<li><p>You prefer full SPA experiences over isolated widgets</p>
</li>
<li><p>You don’t mind some boilerplate for orchestration</p>
</li>
<li><p>You’re okay with a purely client-side setup</p>
</li>
</ul>
<p>Let’s move on!</p>
<h2 id="heading-method-4-module-federation-sharing-code-at-runtime"><strong>Method #4: Module Federation - Sharing Code at Runtime</strong></h2>
<blockquote>
<p>“What if your micro frontends could load each other’s components, modules, or libraries at runtime — without iframes, without import maps, and without repackaging?”</p>
</blockquote>
<p>That’s exactly what <a target="_blank" href="https://module-federation.io/">Module Federation</a>, introduced in <a target="_blank" href="https://webpack.js.org/blog/2020-10-10-webpack-5-release/">Webpack 5</a>, makes possible. It’s fairly new and it allows multiple, separately built and deployed applications to share modules in real-time, via the browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748789750463/ad976d48-f564-4e94-a3ca-c18e9612dc55.png" alt="ad976d48-f564-4e94-a3ca-c18e9612dc55" class="image--center mx-auto" width="2772" height="1020" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://module-federation.io/">https://module-federation.io/</a></p>
</blockquote>
<p>With Module Federation, you can:</p>
<ul>
<li><p>Import components across independent builds</p>
</li>
<li><p>Share React, Vue, or any dependency</p>
</li>
<li><p>Version-control exposed modules</p>
</li>
<li><p>Ship independently, yet consume each other</p>
</li>
</ul>
<p>Module Federation is what makes micro frontends in a single cohesive layout truly feel like one app.</p>
<p>Now let’s see it in action!</p>
<h3 id="heading-real-life-example-1"><strong>Real-Life Example</strong></h3>
<p>Let’s assume that you have to build two self-contained apps:</p>
<ul>
<li><p>Main / Host app (shell) — loads components from others (let’s say it’s in React)</p>
</li>
<li><p>Remote app (product-app) — exposes components written also in React to others</p>
</li>
</ul>
<p>Module Federation allows you to export these components without publishing them to NPM or wrapping them as a Web Component. Instead, the host app will load the component directly at runtime from the compiled JavaScript bundle.</p>
<p>Here’s how the project structure could look:</p>
<p><strong>Product App:</strong></p>
<pre><code class="lang-apache"><span class="hljs-attribute">product</span>-app/                ← Remote Micro Frontend
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html          ← Mount point for optional local test render
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">ProductTile</span>.jsx     ← Component to expose
│   └── <span class="hljs-attribute">index</span>.js            ← Optional: local entry point
├── <span class="hljs-attribute">webpack</span>.config.js       ← Exposes Product App
├── <span class="hljs-attribute">package</span>.json
└── .<span class="hljs-attribute">babelrc</span> / .gitignore / etc
</code></pre>
<p>Note, that <code>webpack.config.js</code> must be at the root level, same as <code>package.json</code>, so <code>Webpack</code> can locate it automatically.</p>
<p><strong>Main / Host App (shell):</strong></p>
<pre><code class="lang-apache"><span class="hljs-attribute">host</span>-app/                     
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html        ← Mount point
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">App</span>.jsx           ← Mounts ProductTile from remote
│   └── <span class="hljs-attribute">bootstrap</span>.js      ← App entry point
├── <span class="hljs-attribute">webpack</span>.config.js     ← Loads remotes via Module Federation
└── <span class="hljs-attribute">package</span>.json
</code></pre>
<p>You can keep them both in a monorepo or host them in entirely different repos.</p>
<p>🔹 <strong>Step #0: Initiate projects (Host + Product Apps)</strong></p>
<p>If you know how to do it, you can set up two separate React applications yourself for the Host App and one for the Remote (Product App), or initialize them in this way:</p>
<pre><code class="lang-bash">npm init
npm install react react-dom
</code></pre>
<p><strong>🔹</strong> <strong>Step #1: Install Webpack 5 + dependencies (Host + Product Apps)</strong></p>
<p>Before you do anything federation-related, both the host and remote apps must be set up with Webpack 5 and its plugins. Go ahead and run this in both projects:</p>
<pre><code class="lang-bash">npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
</code></pre>
<p>A few notes about these packages:</p>
<ul>
<li><p><code>webpack + webpack-cli</code> — Core bundler and CLI</p>
</li>
<li><p><code>webpack-dev-server</code> — Local server for hot reload + module exposure</p>
</li>
<li><p><code>html-webpack-plugin</code> — Automatically injects your bundles into HTML</p>
</li>
<li><p>Optional but common: You can add <code>Babel</code>, <code>React preset</code>, <code>loaders</code>, and so on, for <code>JSX</code>/<code>TSX</code> support later.</p>
</li>
</ul>
<p>This setup gives you a foundation. From here, you can add module federation to connect apps together.</p>
<p><strong>🔹</strong> <strong>Step #2: Create the Remote App (Product App)</strong></p>
<p>Let’s start with the remote app, the one exposing a React component to be consumed by others.</p>
<p>Here’s a simple <code>ProductTile</code> React component (of course, you can implement yours):</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/src/ProductTile.jsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProductTile</span>(<span class="hljs-params">{ title }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">border:</span> '<span class="hljs-attr">1px</span> <span class="hljs-attr">solid</span> #<span class="hljs-attr">aaa</span>', <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>🛍 {title}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>A <code>ProductTile</code> component supplies a prop – <code>“title”</code> – and renders it.</p>
<p>Now let’s expose this component to other apps, not just render it locally.</p>
<p><strong>🔹</strong> <strong>Step #3: Configure Webpack in the Remote App (Product App)</strong></p>
<p>This will be done utilizing module federation, which you must enable in <code>webpack.config.js</code> file. Here’s how it can be done. At the very top of the file, you will need to import these packages:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/webpack.config.js</span>

<span class="hljs-keyword">const</span> HtmlWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'html-webpack-plugin'</span>);
<span class="hljs-keyword">const</span> ModuleFederationPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>).container.ModuleFederationPlugin;
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
</code></pre>
<ul>
<li><p><code>HtmlWebpackPlugin</code> – Handles HTML generation and script injection.</p>
</li>
<li><p><code>ModuleFederationPlugin</code> – The core Webpack plugin that lets you expose and consume modules at runtime</p>
</li>
</ul>
<p>Then, define the actual config in <code>module.exports</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/webpack.config.js</span>

<span class="hljs-keyword">const</span> HtmlWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'html-webpack-plugin'</span>);
<span class="hljs-keyword">const</span> ModuleFederationPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>).container.ModuleFederationPlugin;
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">entry</span>: <span class="hljs-string">'./src/index.js'</span>,                         <span class="hljs-comment">// Entry file to the product app</span>
  <span class="hljs-attr">mode</span>: <span class="hljs-string">'development'</span>,                             <span class="hljs-comment">// Must be production if you go live</span>
  <span class="hljs-attr">devServer</span>: {
    <span class="hljs-attr">port</span>: <span class="hljs-number">3001</span>                                     <span class="hljs-comment">// Product app runs on this port</span>
  },
  <span class="hljs-attr">output</span>: {
    <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'auto'</span>,                            <span class="hljs-comment">// Required for dynamic federation</span>
  },
  <span class="hljs-attr">plugins</span>: [
    <span class="hljs-keyword">new</span> ModuleFederationPlugin({
      <span class="hljs-attr">name</span>: <span class="hljs-string">'productApp'</span>,                         <span class="hljs-comment">// Internal name of the remote app</span>
      <span class="hljs-attr">filename</span>: <span class="hljs-string">'remoteEntry.js'</span>,                 <span class="hljs-comment">// Entry file others will load</span>
      <span class="hljs-attr">exposes</span>: {
        <span class="hljs-string">'./ProductTile'</span>: <span class="hljs-string">'./src/ProductTile.jsx'</span>, <span class="hljs-comment">// Expose this module</span>
      },
      <span class="hljs-attr">shared</span>: {                                   <span class="hljs-comment">// Shared packages if needed</span>
        <span class="hljs-attr">react</span>: { <span class="hljs-attr">singleton</span>: <span class="hljs-literal">true</span> },
        <span class="hljs-string">'react-dom'</span>: { <span class="hljs-attr">singleton</span>: <span class="hljs-literal">true</span> },
      },
    }),
    <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
      <span class="hljs-attr">template</span>: <span class="hljs-string">'./public/index.html'</span>,
    }),
  ],
};
</code></pre>
<p>Now it’s time to use the product app in the main/host app:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// host-app/src/App.jsx</span>

<span class="hljs-keyword">import</span> React, { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-comment">// Dynamically import ProductTile from the remote</span>
<span class="hljs-keyword">const</span> RemoteProductTile = React.lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'productApp/ProductTile'</span>));

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">2rem</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>📦 Host App<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">div</span>&gt;</span>Loading product tile...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>}&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">RemoteProductTile</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Bluetooth Speaker"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>In React, you can use the <code>React.lazy()</code> function to dynamically import the federated module. It returns a promise that React renders as soon as it’s ready.</p>
<p>That’s it. There’s nothing related to the module federation in the <code>bootstrap.js</code> and <code>index.html</code> files, but regular setup, so you can put whatever you want there:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// host-app/src/bootstrap.js</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;

<span class="hljs-keyword">const</span> root = createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>));
root.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
</code></pre>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- host-app/public/index.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Host App<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"root"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>And lastly, you can launch the host app:</p>
<pre><code class="lang-bash">npx webpack serve
</code></pre>
<p>That’s it!</p>
<p>Here are a few advantages and limitations of Module Federation, along with popular use cases.</p>
<h3 id="heading-pros-3"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Runtime Integration</strong> – Import remote components after both apps are built</p>
</li>
<li><p><strong>Independent Deployment</strong> – Teams can ship apps on separate pipelines</p>
</li>
<li><p><strong>Code Sharing</strong> – Share common libraries (React, lodash) to reduce duplication</p>
</li>
<li><p><strong>No iframes or wrappers</strong> – Native component integration, not isolated like Web Components</p>
</li>
<li><p><strong>No import maps needed</strong> – Webpack handles all the resolution logic</p>
</li>
<li><p><strong>Works across frameworks –</strong> Can be used in React, Angular, Vue, even Web Components</p>
</li>
</ul>
<h3 id="heading-cons-3"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Tied to Webpack</strong> – <strong>Federation</strong> is Webpack-specific (Vite/Rollup alternatives exist but are not native)</p>
</li>
<li><p><strong>Initial setup is complicated</strong> – Requires per-app Webpack configuration and shared dependency coordination</p>
</li>
<li><p><strong>Runtime failures are possible –</strong> If the remote is down, the host may break unless you handle fallbacks</p>
</li>
<li><p><strong>Version mismatch risks</strong> – Shared libs (like React) must be tightly versioned and aligned</p>
</li>
<li><p><strong>No automatic SSR</strong> – Requires custom hydration logic for federated components</p>
</li>
</ul>
<h3 id="heading-popular-use-cases-3"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<p>Use <strong>Module Federation</strong> when:</p>
<ul>
<li><p>You want to build a platform composed of independently deployed apps</p>
</li>
<li><p>You need runtime module loading (not just widgets)</p>
</li>
<li><p>You want to share design systems or UI libraries across apps</p>
</li>
<li><p>Your team is federating complex app sections, not just components</p>
</li>
<li><p>You want to avoid loading dependencies multiple times across apps</p>
</li>
</ul>
<h2 id="heading-other-tools-amp-ecosystem-additions"><strong>Other Tools &amp; Ecosystem Additions</strong></h2>
<p>While iframes, Web Components, single-spa, and Module Federation are the major players in the micro-frontend arena, there’s a growing ecosystem of alternative tools and strategies. They don’t always serve as full micro-frontend methods, but still solve important pieces of the puzzle. Let’s walk through some of the less prominent, yet practical solutions that are worth your attention.</p>
<h3 id="heading-import-maps-native-es-modules"><strong>Import Maps + Native ES Modules</strong></h3>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap">Import Maps</a> allow you to define where modules are loaded from, directly in the browser. Combined with native ES module support, they enable zero-build micro frontend setups.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"importmap"</span>&gt;</span><span class="javascript">
{
  <span class="hljs-string">"imports"</span>: {
    <span class="hljs-string">"ui-library/"</span>: <span class="hljs-string">"https://cdn.example.com/ui/v1.2.3/"</span>,
    <span class="hljs-string">"square"</span>: <span class="hljs-string">"./modules/shapes/square.js"</span>
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>You might’ve noticed that it looks similar to what single-spa + <code>SystemJS</code> does.</p>
<p><strong>Use it when</strong>:</p>
<ul>
<li><p>You want to dynamically load shared libraries (like design systems)</p>
</li>
<li><p>You’re building federated apps without bundlers</p>
</li>
<li><p>You’re targeting modern browsers only</p>
</li>
</ul>
<h3 id="heading-piral-micro-frontends-as-pluggable-portals"><strong>Piral: Micro Frontends as Pluggable Portals</strong></h3>
<p><a target="_blank" href="https://piral.io/">Piral</a> is a specialized framework for building portal-based micro frontends. It provides a structured environment where micro apps (called pilets) can be plugged into a central shell (the Piral instance).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748797958786/125cdd57-0d2d-4d23-a320-028b081ee989.png" alt="125cdd57-0d2d-4d23-a320-028b081ee989" class="image--center mx-auto" width="3008" height="1068" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://piral.io/">https://piral.io/</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Routing</p>
</li>
<li><p>Layout orchestration</p>
</li>
<li><p>Shared state</p>
</li>
<li><p>Module loading</p>
</li>
<li><p>Authentication hooks</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li><p>Enterprise-scale portals</p>
</li>
<li><p>Apps with lots of features teams</p>
</li>
<li><p>Admin dashboards or CMS-heavy UIs</p>
</li>
</ul>
<h3 id="heading-luigi-micro-frontends-sap-style-shells"><strong>Luigi: Micro Frontends + SAP-style Shells</strong></h3>
<p><a target="_blank" href="https://luigi-project.io/">Luigi</a> is a microfrontend framework built by SAP to enable consistent layout shells with side navigation, top bars, permissions, and more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798177808/16380085-a4fc-4cc9-9fe2-b44821f9feef.png" alt="16380085-a4fc-4cc9-9fe2-b44821f9feef" class="image--center mx-auto" width="3010" height="1472" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://luigi-project.io/">https://luigi-project.io/</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Config-driven app registration</p>
</li>
<li><p>Automatic route activation</p>
</li>
<li><p>Role-based access control (RBAC)</p>
</li>
<li><p>Seamless iframe integration with a shell</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li><p>Intranet tools</p>
</li>
<li><p>Cloud admin panels</p>
</li>
<li><p>Productized dashboards</p>
</li>
</ul>
<h3 id="heading-open-components"><strong>Open Components</strong></h3>
<p><a target="_blank" href="https://github.com/opencomponents/oc">OpenComponents</a> is a framework-agnostic way to build self-contained microservices with UI logic, registered to a central registry.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798238923/6406ef71-4dde-47bc-8d2b-9476593afdd5.png" alt="6406ef71-4dde-47bc-8d2b-9476593afdd5" class="image--center mx-auto" width="3022" height="1204" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://github.com/opencomponents/oc">https://github.com/opencomponents/oc</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Server-rendered or client-rendered</p>
</li>
<li><p>REST-like model for UI consumption</p>
</li>
<li><p>Great CDN + registry story</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li>Used when your company treats UI as deployable microservices, just like APIs.</li>
</ul>
<h3 id="heading-bit-meet-a-composable-architecture">Bit: Meet a composable architecture</h3>
<p><a target="_blank" href="https://bit.dev/">Bit</a> isn’t a micro frontend framework per se, but a component-driven development and distribution platform. It organizes source code into composable components, empowering to build reliable, scalable applications in the era of AI.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798402542/9fdf7de4-cc1d-41b5-9709-be824c8ffe41.png" alt="9fdf7de4-cc1d-41b5-9709-be824c8ffe41" class="image--center mx-auto" width="3024" height="1126" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://bit.dev/">https://bit.dev</a></p>
</blockquote>
<p>Use it alongside Web Components or Module Federation to supercharge reuse. If you want to practice, they have an <a target="_blank" href="https://bit.dev/blog/mastering-micro-frontends-with-module-federation-and-bit-ljn4ruah/">Official Guide</a> on how to master Micro-Frontends with Module Federation.</p>
<p>It’s a great addition when:</p>
<ul>
<li><p>You want to publish reusable components across teams</p>
</li>
<li><p>You need to manage versions, ownership, and discovery</p>
</li>
<li><p>You’re aiming for component-first delivery, not app-first</p>
</li>
</ul>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Micro frontends offer immense power, but that power comes with architectural responsibility.</p>
<p>Each method we explored solves a different kind of problem:</p>
<ul>
<li><p>IFrames are secure, but come with complex communication and high isolation.</p>
</li>
<li><p>Web Components are native, framework-agnostic, dependency-free, and perfect for reusable UI Kits</p>
</li>
<li><p>single-spa shines when you need orchestration and multiple SPAs under one shell.</p>
</li>
<li><p>Module Federation is the go-to for runtime code sharing and independent deployment.</p>
</li>
<li><p>And tools like Import Maps, Piral, Luigi, and others fill in the gaps, each in their own way.</p>
</li>
</ul>
<p>There’s no one-size-fits-all solution here, but with the right match for your team structure and product strategy, you can build apps that scale across teams, tech stacks, and time.</p>
<hr>
<p>If you liked this guide, feel free to repost and share it with your friends, colleagues, and social network.</p>
<p>If you want to take your micro-frontend skills to a new level, especially around Web Components, I invite you to check out my best-selling Udemy course called <a target="_blank" href="https://www.udemy.com/course/web-components-api/?couponCode=HERO_START">“Web Components: The Ultimate Guide from Zero to Hero“</a>.</p>
<p>And of course, if you have questions, feedback, or need help with your micro frontend setup, feel free to reach out to me on my social media such as <a target="_blank" href="https://www.linkedin.com/in/andrewmaksimchenko/">LinkedIn</a> / <a target="_blank" href="https://x.com/avmax19">X</a> / <a target="_blank" href="https://t.me/codelikeandrew">Telegram</a>. I’m always happy to chat, connect, and help other devs build amazing things! 💚</p>
<p>Let’s build the IT future we could be proud of! 💪🏼 Thanks for reading — and happy decoupling! 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
