<?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" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Sachin Patel on Medium]]></title>
        <description><![CDATA[Stories by Sachin Patel on Medium]]></description>
        <link>https://medium.com/@sachin88?source=rss-103dbd6e9269------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*tSwn07roHolcsBzv4vlqwg.jpeg</url>
            <title>Stories by Sachin Patel on Medium</title>
            <link>https://medium.com/@sachin88?source=rss-103dbd6e9269------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 01:38:04 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@sachin88/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Frontend Performance Optimization: A Practical Guide to Faster Web Pages]]></title>
            <link>https://javascript.plainenglish.io/frontend-performance-optimization-a-practical-guide-to-faster-web-pages-c7a8d34fdbb5?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/c7a8d34fdbb5</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[performance-optimization]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Sat, 24 Oct 2026 16:27:49 GMT</pubDate>
            <atom:updated>2026-10-24T16:27:49.810Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QQ4nnpiIzXU-FVqVkubw4A.png" /></figure><p>Most frontend performance work isn’t about making <strong>everything</strong> load faster. It’s about making sure the browser spends its first few seconds on <strong>what the user actually needs first</strong>.</p><p>Images, fonts, third-party scripts, iframes, JavaScript and animations can all compete for network bandwidth and CPU time. Some are critical. Many aren’t.</p><p>A useful performance strategy is therefore:</p><blockquote><strong><em>Load, execute and render the things users need first. Defer everything else until it becomes relevant.</em></strong></blockquote><p>This playbook covers a practical approach to images, LCP, third-party code, JavaScript, layout stability and accessibility.</p><h3>1. Start With Measurement, Not Optimizations</h3><p>Before changing code, first understand what is actually slowing the page down.</p><p>Useful tools include:</p><ul><li>Lighthouse for lab-based performance checks</li><li>Chrome DevTools Network panel and waterfall</li><li>Performance panel for long tasks and main-thread activity</li><li>JavaScript Coverage to identify code that is loaded but unused</li><li>Core Web Vitals, particularly LCP, CLS and INP</li><li>Real-user monitoring (RUM) for production data</li></ul><h3>Using AI to Analyze Performance Data</h3><p>Performance reports can contain a large amount of information: network requests, JavaScript execution, long tasks, LCP breakdowns, unused code, third-party resources and layout shifts. Manually going through every signal can make it difficult to distinguish the important problems from the noise.</p><p>AI tools can be useful as an additional analysis layer.</p><p>A practical workflow is to provide an AI tool with performance artifacts such as Lighthouse reports, Chrome DevTools traces, network waterfalls, performance profiles or exported Web Vitals data. The AI can then help summarize the findings, identify patterns, connect related issues and highlight areas that deserve deeper investigation.</p><p>For example, instead of simply looking at a Lighthouse warning such as <strong>“Reduce unused JavaScript,”</strong> an AI-assisted analysis can help connect that finding with the application structure:</p><pre>Lighthouse<br>   ↓<br>Unused JavaScript<br>   ↓<br>Which bundle?<br>   ↓<br>Which component imported it?<br>   ↓<br>Is that component needed on initial load?<br>   ↓<br>Can it be dynamically imported?</pre><p>The same approach can be used to cross-reference multiple signals. For example, an LCP regression combined with the network waterfall may reveal that a third-party script, font or non-critical image is competing with the LCP resource.</p><p>AI should not replace the underlying measurements or browser tooling. Its most useful role is to <strong>reduce the analysis effort and surface potential gaps faster</strong>. The proposed diagnosis should still be verified against the actual trace, network behavior and production data before making an optimization.</p><p>A useful mental model is:</p><blockquote><strong><em>Use browser tools to collect evidence; use AI to help interpret the evidence; use engineering judgment to decide what to change.</em></strong></blockquote><p>A useful investigation loop is:</p><pre>Measure<br>  ↓<br>Analyze the evidence<br>  ↓<br>Form a hypothesis<br>  ↓<br>Make one targeted change<br>  ↓<br>Measure again<br>  ↓<br>Keep or revert</pre><p>The goal isn’t to optimize based on a single Lighthouse recommendation. It’s to connect multiple pieces of evidence, form a hypothesis, validate the change, and then decide whether the improvement is worth keeping.</p><h3>Use Engineering Judgment</h3><p>Not every performance recommendation is worth implementing. An optimization may improve one metric while adding complexity, increasing maintenance cost or regression risk, or negatively affecting the user experience elsewhere.</p><p>Performance work should therefore be treated as a trade-off exercise. Consider the <strong>impact, implementation effort, complexity, and risk</strong> before making a change. Some improvements may be worth shipping immediately, while others may have too little benefit to justify the cost.</p><p>It is also important to validate the result after the change. If an optimization does not produce the expected improvement — or introduces unexpected side effects — <strong>revert the change rather than optimizing for the sake of optimization</strong>.</p><p>The goal is not to implement every recommendation from Lighthouse, DevTools, or an AI analysis. The goal is to make <strong>high-value, low-risk improvements that meaningfully improve the user experience</strong>.</p><p>The more useful question is:</p><blockquote><strong><em>What is competing with the user’s first meaningful experience?</em></strong></blockquote><p>For example, a page may download a large amount of JavaScript, but if that JavaScript belongs to a lightbox the user hasn’t opened, it shouldn’t necessarily be part of the <strong>initial loading cost</strong>.</p><h3>2. The Mental Model: The Browser Has Limited Attention</h3><p>A useful way to think about frontend performance is to imagine that the browser has a limited amount of bandwidth and CPU available during the first few seconds.</p><pre>First viewport<br>                         │<br>          ┌──────────────┼──────────────┐<br>          ↓              ↓              ↓<br>         LCP        Interaction       Layout<br>          │              │              │<br>       Images           JS           Dimensions<br>       Fonts          Widgets        Skeletons</pre><p>Every unnecessary resource can compete with something more important.</p><p>This leads to a simple principle:</p><blockquote><strong><em>Don’t just ask whether something is fast. Ask whether it needs to happen now.</em></strong></blockquote><p>That principle can guide most frontend performance decisions.</p><h3>3. Images: Prioritize Only What Matters</h3><p>Images are often one of the easiest places to find unnecessary work.</p><h3>What to check</h3><ul><li>Lighthouse’s “Properly size images” audit</li><li>Offscreen image loading</li><li>Network waterfall</li><li>Image dimensions and variants</li><li>Which element is actually reported as LCP</li><li>How carousel and slider images are mounted</li></ul><p>A useful way to categorize images is:</p><ol><li><strong>The LCP image</strong> — important immediately</li><li><strong>Other visible images</strong> — potentially important, but not necessarily critical</li><li><strong>Offscreen images</strong> — wait until they’re needed</li></ol><p>The important part isn’t simply “lazy load images.”</p><p>It’s deciding <strong>which image deserves priority</strong>.</p><h3>3.1 Identify the Actual LCP Image</h3><p>A common mistake is treating multiple images as equally important.</p><p>For example, a hero image and the first image of every card may all be marked as high priority.</p><p>That can cause multiple large images to compete for bandwidth even though only one ultimately becomes the LCP element.</p><p>Instead:</p><blockquote><strong><em>Identify the actual LCP resource and prioritize that resource rather than everything that happens to appear near the top of the page.</em></strong></blockquote><h3>3.2 Understand loading vs fetchPriority</h3><p>These mechanisms solve different problems. They influence resource loading, but they control <strong>different things</strong>.</p><h4><strong>&quot;loading&quot; controls timing.</strong></h4><p>It tells the browser whether a resource needs to be loaded as part of the initial page experience or whether its loading can be deferred until it is needed.</p><p><strong>loading=&quot;lazy&quot;</strong><br>Allows the browser to defer loading until the resource is needed. Use it for offscreen or below-the-fold images.</p><p><strong>loading=&quot;eager&quot;</strong><br>Tells the browser not to intentionally defer loading. Use it for important images that are immediately visible, such as the hero/LCP image.</p><h4><strong>&quot;fetchPriority&quot; controls importance.</strong></h4><p>It tells the browser how important a resource is relative to other resources that are competing for network or browser resources.</p><p><strong>fetchPriority=&quot;high&quot;</strong><br>Signals that a resource is particularly important compared with other requests. Typically useful for the LCP image.</p><p><strong>fetchPriority=&quot;low&quot;</strong><br>Signals that a resource is less important and can take a lower priority. Useful for non-critical images that might otherwise compete with more important resources.</p><p><strong>For example:</strong></p><pre>&lt;Image<br>  src={hero}<br>  priority<br>  fetchPriority=&quot;high&quot;<br>  sizes=&quot;100vw&quot;<br>/&gt;</pre><p>The exact implementation depends on the framework and image component, but the principle is the same.</p><h3>Avoid high priority everywhere</h3><p>Making every first-screen image high priority simply recreates the original problem.</p><p>If everything is important, nothing is meaningfully prioritized.</p><p>fetchPriority is a hint, not a guarantee, and it doesn&#39;t replace lazy loading.</p><h3>3.3 Serve the Right Image</h3><p>Another common problem is downloading an image that is much larger than the viewport requires.</p><p>For example, a mobile device may download a desktop-sized image even though only a fraction of its pixels are displayed.</p><p>Use responsive image techniques such as srcset, sizes, or framework-level image optimization to select an appropriate resource.</p><p>The general principle:</p><blockquote><strong><em>Don’t optimize only the loading behavior. Optimize the payload itself.</em></strong></blockquote><p>A perfectly prioritized 1.5 MB image is still a 1.5 MB image.</p><h3>3.4 Don’t Eagerly Load Every Carousel Slide</h3><p>A carousel may contain five or ten images, while the user initially sees only one.</p><p>For list-card carousels:</p><ul><li>The first image doesn’t automatically become a “hero”.</li><li>Additional slides can be loaded on demand.</li><li>Avoid mounting every slide up front when users are unlikely to view them.</li></ul><p>For a lightbox or full-screen gallery, the trade-off is different.</p><p>Users may immediately jump to another image, so aggressively delaying every slide can make interaction feel slow.</p><p>The important distinction is:</p><blockquote><strong><em>Performance optimization depends on the interaction model.</em></strong></blockquote><p>A list-card carousel and a full-screen gallery should not necessarily use the same loading strategy.</p><h3>3.5 Don’t Tie Loading to the Active Slide</h3><p>A subtle issue can occur when lazy/eager behavior is tied directly to the currently active slide.</p><p>When the user swipes, the image may be remounted or treated as a new resource.</p><p>That can result in flashes and, in some cases, additional downloads.</p><p>Instead:</p><blockquote><strong><em>Tie loading decisions to the initial rendering strategy, not simply to which slide happens to be active.</em></strong></blockquote><h3>3.6 Don’t Animate What Users Can’t See</h3><p>Animated loading placeholders can consume CPU even when the associated content isn’t visible.</p><p>A better approach is:</p><ul><li>Use a static placeholder by default.</li><li>Start animation only when the content is close to the viewport.</li><li>Don’t animate hidden slides.</li><li>Respect prefers-reduced-motion.</li></ul><p>Not every loading state needs to consume CPU.</p><h3>4. LCP: Clear the Runway</h3><p>LCP is a race.</p><p>The browser has to download resources, process them, execute JavaScript and eventually paint the element that becomes the LCP candidate.</p><p>Anything unnecessary competing during that period can make the finish line harder to reach.</p><p>Look at everything loading around the time of the LCP element:</p><ul><li>Fonts</li><li>Third-party scripts</li><li>Iframes</li><li>Additional images</li><li>JavaScript execution</li></ul><p>The key question is:</p><blockquote><strong><em>Does this resource need to happen before the user sees the primary content?</em></strong></blockquote><p>If the answer is no, look for a way to defer it.</p><h3>4.1 Fonts</h3><p>Preloading a font tells the browser:</p><blockquote><em>“This resource is important. Start downloading it immediately.”</em></blockquote><p>That’s useful when the font is genuinely critical.</p><p>It’s less useful when that same bandwidth could be used by the LCP resource.</p><p>Consider:</p><ul><li>Keeping preload for the primary UI font where it is genuinely needed.</li><li>Removing unnecessary preload from secondary or decorative fonts.</li><li>Using font-display: swap where appropriate.</li></ul><p>The general rule:</p><blockquote><strong><em>Preload is not a performance badge. Preload means “this resource is important enough to compete for early bandwidth.”</em></strong></blockquote><p>Use it deliberately.</p><h3>5. Third-Party Code: Defer Without Breaking the Experience</h3><p>Third-party widgets are particularly interesting because their JavaScript is often outside your direct control.</p><p>Chat widgets, analytics libraries, maps and support tools are common examples.</p><p>A chat widget might initially follow this pattern:</p><pre>Page starts<br>    ↓<br>Chat SDK starts loading<br>    ↓<br>Parse + execute third-party JS<br>    ↓<br>LCP competes with chat</pre><p>Instead, defer non-critical third-party code:</p><pre>Page starts<br>    ↓<br>Critical page content<br>    ↓<br>window.load<br>    ↓<br>Browser idle time<br>    ↓<br>Chat SDK</pre><p>A client-only dynamic import combined with window.load and idle scheduling can be used for this pattern.</p><p>Conceptually:</p><pre>window.addEventListener(&quot;load&quot;, () =&gt; {<br>  requestIdleCallback(loadChat, {<br>    timeout: 5000,<br>  });<br>});</pre><p>The exact timeout is application-specific.</p><p>The important principle is:</p><blockquote><strong><em>Defer third-party work opportunistically, but don’t defer it indefinitely.</em></strong></blockquote><p>There is also an important UX constraint.</p><p>Waiting until the user clicks the chat launcher before loading anything may make the launcher unavailable.</p><p>A hybrid strategy can work better:</p><pre>Normal path<br>Page → load → idle → Chat</pre><pre>User clicks early<br>Page → click → Chat immediately</pre><p>This pattern is useful for functionality that should eventually be available but isn’t part of the critical rendering path.</p><h3>6. Iframes: Don’t Let a Separate Document Block the Page</h3><p>An iframe isn’t just another element.</p><p>It represents another document with its own resources and execution.</p><p>Maps, videos and embedded third-party experiences can therefore become surprisingly expensive.</p><p>A common approach is:</p><pre>Page navigation<br>      ↓<br>Critical content renders<br>      ↓<br>Page load / idle<br>      ↓<br>Observe placeholder<br>      ↓<br>User approaches content<br>      ↓<br>Set iframe src</pre><p>The iframe should also have a sized placeholder so that loading it doesn’t introduce layout shift.</p><p>There should also be an explicit escape hatch:</p><blockquote><strong><em>If the user asks for the content, load it immediately.</em></strong></blockquote><p>This creates a useful general pattern:</p><p><strong>Defer by default, bypass the delay when the user expresses intent.</strong></p><h3>7. JavaScript: If It’s Not Open, Don’t Ship It</h3><p>JavaScript has multiple costs:</p><pre>Download<br>   ↓<br>Parse<br>   ↓<br>Compile<br>   ↓<br>Execute<br>   ↓<br>Hydrate / Render</pre><p>Reducing initial JavaScript can therefore reduce more than network transfer. It can also reduce parsing, execution and main-thread work.</p><p>A useful rule is:</p><blockquote><strong><em>If the user hasn’t requested a piece of UI, don’t make the browser pay for it during initial rendering.</em></strong></blockquote><h3>7.1 Overlays and Heavy Interactive UI</h3><p>Lightboxes, drawers and detail panels are good candidates for dynamic imports.</p><p>Instead of loading them with the initial page:</p><pre>Initial page<br>    ↓<br>Small loading shell<br>    ↓<br>User opens gallery<br>    ↓<br>Dynamic import<br>    ↓<br>Gallery mounts</pre><p>The first interaction pays the loading cost, but subsequent opens are cheap because the chunk is cached.</p><p>The broader principle:</p><blockquote><strong><em>Move cost from initial page load to the interaction that actually needs the feature.</em></strong></blockquote><h3>7.2 Don’t Build Skeletons With Heavy Components</h3><p>Some loading states can accidentally require much of the same component machinery as the final UI.</p><p>For simple layouts, CSS is often enough.</p><pre>.skeleton {<br>  display: grid;<br>  grid-template-columns: repeat(3, 1fr);<br>}</pre><p>The principle:</p><blockquote><strong><em>A loading state shouldn’t require a large UI component just to draw boxes.</em></strong></blockquote><h3>7.3 Below-the-Fold Sections</h3><p>Below-the-fold content doesn’t always need to be part of the initial JavaScript cost.</p><p>Consider:</p><ul><li>Splitting expensive variants into separate chunks.</li><li>Dynamically importing sliders and other heavy interactive components.</li><li>Keeping important first-fold and SEO content available synchronously.</li></ul><h3>7.4 Don’t Animate During Hydration</h3><p>Decorative animations are another form of work.</p><p>If an animation doesn’t communicate something important to the user, there’s often little reason to run it during initial hydration.</p><p>For example, a wishlist animation makes sense after the user clicks “Add to wishlist.”</p><p>It doesn’t need to run every time the page hydrates.</p><blockquote><strong><em>Prefer interaction-driven animation over startup animation.</em></strong></blockquote><h3>8. CLS: Reserve Space Before Content Arrives</h3><p>Performance isn’t only about speed.</p><p>A page that appears quickly but jumps around while loading is still a poor experience.</p><p>Common sources of CLS include:</p><ul><li>Layout changes after hydration</li><li>Images without predictable dimensions</li><li>Iframes appearing without reserved space</li><li>Loading states that don’t match the final layout</li><li>Asynchronous content being inserted into existing layouts</li></ul><h3>8.1 Make the Server and Client Agree</h3><p>Responsive rendering can introduce a subtle problem.</p><p>For example:</p><pre>Server HTML<br>Desktop layout<br>      ↓<br>Hydration<br>      ↓<br>Client detects mobile<br>      ↓<br>Remount / layout change</pre><p>This can create layout shifts and unnecessary rendering work.</p><p>Where appropriate, use server-side device hints or other stable inputs so that the server HTML and first client render make consistent layout decisions.</p><p>The broader lesson:</p><blockquote><strong><em>Avoid rendering one layout and immediately replacing it with another after hydration.</em></strong></blockquote><h3>8.2 A Loader Is Not the Layout</h3><p>A skeleton can technically reserve space while still producing a poor experience if its geometry doesn’t match the final content.</p><p>For example:</p><pre>Skeleton<br>┌──────────────┐<br>│              │<br>└──────────────┘</pre><pre>Actual card<br>┌─────────────────────┐<br>│                     │<br>│                     │<br>└─────────────────────┘</pre><p>Both occupy space, but the transition can still cause visual movement.</p><p>Prefer:</p><ul><li>aspect-ratio boxes</li><li>Grid structures matching the final layout</li><li>Placeholder dimensions matching real content</li></ul><p>The goal isn’t simply:</p><blockquote><em>“Reserve some space.”</em></blockquote><p>It’s:</p><blockquote><em>**Reserve the right space.</em></blockquote><h3>8.3 Iframes Need a Box Before They Need a src</h3><p>For maps and similar content, render a sized placeholder first.</p><pre>┌────────────────────────┐<br>│                        │<br>│       Placeholder      │<br>│                        │<br>└────────────────────────┘<br>           ↓<br>        iframe loads<br>           ↓<br>┌────────────────────────┐<br>│                        │<br>│       Actual content   │<br>│                        │<br>└────────────────────────┘</pre><p>The iframe can then fade in without changing the surrounding layout.</p><h3>9. Common Approaches That Don’t Work Well</h3><p>Performance optimization often involves trying an approach that sounds reasonable and discovering that it creates a different problem.</p><p>Some common examples:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xDROLvls1ihy_0ehI0D4vg.png" /></figure><p>The goal isn’t to find a clever optimization.</p><p>It’s to find the simplest strategy that improves the user experience <strong>without creating a new problem somewhere else</strong>.</p><h3>10. Performance Without Accessibility Regressions</h3><p>Performance optimizations shouldn’t come at the cost of usability or accessibility.</p><p>Common checks include:</p><ul><li>Add accessible labels to icon-only buttons.</li><li>Remove redundant aria-labels where visible text already describes the control.</li><li>Use role=&quot;presentation&quot; for purely structural elements where appropriate.</li><li>Use role=&quot;status&quot; for loading states where users need to be informed of asynchronous changes.</li><li>Use inert when content should remain mounted but temporarily unavailable for interaction.</li><li>Respect prefers-reduced-motion for animated loading states.</li></ul><p>The goal is simple:</p><blockquote><strong><em>Make the browser do less work without making the experience less understandable.</em></strong></blockquote><h3>11. Measure the Result</h3><p>Performance work is difficult to evaluate without measurement.</p><p>Always compare the state before and after meaningful changes.</p><p>For example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CqXzbo5iX0cvvwLlUc-y6Q.png" /></figure><p>If exact numbers cannot be shared, relative improvements are still useful.</p><p>For example:</p><blockquote><em>LCP improved by approximately </em><em>[X]%, while the initial JavaScript payload decreased by approximately </em><em>[Y]%.</em></blockquote><p>The important part is connecting an engineering change to a measurable outcome.</p><h3>12. A Practical Frontend Performance Checklist</h3><p>These aren’t universal rules. They’re useful defaults to start with and then validate through measurement.</p><h3>Images</h3><ul><li>Identify the actual LCP image.</li><li>Give the LCP resource preferential treatment.</li><li>Don’t make every above-the-fold image high priority.</li><li>Serve an appropriately sized image for the viewport.</li><li>Lazy-load genuinely offscreen images.</li><li>Don’t mount every carousel slide unnecessarily.</li></ul><h3>Third-party code</h3><ul><li>Keep third-party scripts out of the critical rendering path where possible.</li><li>Prefer load → idle → interaction over loading everything immediately.</li><li>Give user intent an escape hatch: if the user asks for something, load it immediately.</li></ul><h3>JavaScript</h3><ul><li>Dynamically import expensive UI that users don’t need immediately.</li><li>Don’t mount overlays, drawers or galleries before they’re needed.</li><li>Keep loading states lightweight.</li><li>Avoid unnecessary work during hydration.</li><li>Prefer interaction-driven animation.</li></ul><h3>Layout</h3><ul><li>Reserve space before asynchronous content arrives.</li><li>Make skeleton geometry match the final UI.</li><li>Keep server and client rendering decisions aligned.</li><li>Avoid remounting content unnecessarily.</li></ul><h3>Measurement</h3><ul><li>Measure before optimizing.</li><li>Identify the actual bottleneck instead of optimizing by assumption.</li><li>Change one meaningful thing at a time.</li><li>Validate the result again.</li><li>Use both lab measurements and real-user data when possible.</li></ul><h3>Conclusion</h3><p>The biggest frontend performance improvements often come from one simple question:</p><blockquote><strong><em>Does this need to happen now?</em></strong></blockquote><p>The hero image probably does. The fifth carousel image probably doesn’t.</p><p>The product content does. The unopened lightbox doesn’t.</p><p>The user’s requested map does. The third-party widget probably doesn’t need to compete with LCP.</p><p>The goal isn’t to make every resource load as quickly as possible.</p><p>It’s to make the <strong>right resources load, execute and render at the right time</strong>.</p><blockquote><strong><em>Don’t try to make everything happen faster. Make the right things happen first.</em></strong></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7a8d34fdbb5" width="1" height="1" alt=""><hr><p><a href="https://javascript.plainenglish.io/frontend-performance-optimization-a-practical-guide-to-faster-web-pages-c7a8d34fdbb5">Frontend Performance Optimization: A Practical Guide to Faster Web Pages</a> was originally published in <a href="https://javascript.plainenglish.io">JavaScript in Plain English</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cursor Isn’t the Problem. Your Design Tokens Are]]></title>
            <link>https://medium.com/@sachin88/how-we-fixed-design-tokens-to-make-cursor-generate-reliable-ui-code-74d699e72e38?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/74d699e72e38</guid>
            <category><![CDATA[token-design]]></category>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[ai-code-generation]]></category>
            <category><![CDATA[figma]]></category>
            <category><![CDATA[cursor-ai]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Tue, 06 Jan 2026 06:50:37 GMT</pubDate>
            <atom:updated>2026-01-06T17:21:44.698Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*twXFRTOrDGPY_Jdkh__bgg.png" /><figcaption>Figma MCP + Cursor IDE</figcaption></figure><p>We introduced <a href="https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server"><strong>Figma MCP with Cursor</strong></a> into our workflow to generate frontend code directly from Figma design nodes. While the setup worked functionally, the generated UI code was inconsistent — particularly around CSS variable usage.</p><p>The root cause wasn’t the AI tooling.</p><p>It was inconsistent design token and CSS variable naming.</p><blockquote>To get started with Figma MCP, follow the official <a href="https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server"><strong>Figma MCP Server guide</strong></a> provided by Figma.</blockquote><h3>The Core Issue: Token Drift</h3><p>Over time, our system evolved like this:</p><h4>In Figma</h4><pre>color.primary.500<br>color.brand.main<br>color.blue.600</pre><h4>In Frontend CSS</h4><pre>--color-primary<br>--brand-blue<br>--blue-600</pre><p>Many of these variables represented the same raw color value but were exposed through different semantic token names across design and frontend. Humans could reason about this ambiguity; Figma MCP could not.</p><p>When Figma MCP attempted to generate code from a design node, inconsistent or overlapping token references prevented deterministic name resolution, resulting in unpredictable CSS variable output or fallback to raw values.</p><h3>Why This Breaks Figma MCP</h3><ul><li>Figma MCP relies on <strong>explicit design token references</strong>, not inferred intent or value matching.</li><li>It resolves CSS variables through name-based mapping between design tokens and frontend variables.</li><li>When multiple semantic tokens reference the same value, MCP cannot deterministically choose one.</li><li>Misalignment between token names and CSS variables breaks reliable mapping.</li></ul><p>In such cases, MCP emits inconsistent variables or falls back to raw values — an issue that must be fixed at the design system level, not through prompts.</p><h3>The Fix: Make Tokens Deterministic</h3><h4>Step 1: Separate Raw Tokens From Semantic Tokens</h4><p>We restructured our tokens to follow a strict hierarchy.</p><p><strong>Raw tokens (single source of truth):</strong></p><pre>color.raw.blue.600 = #2563EB</pre><p><strong>Semantic tokens (reference raw tokens):</strong></p><pre>color.action.primary = {color.raw.blue.600}<br>color.link.default   = {color.raw.blue.600}</pre><p>Rules we enforced:</p><ul><li>One raw value per token</li><li>Semantic tokens must reference raw tokens</li><li>No duplicate semantics for the same intent</li></ul><p>This eliminated ambiguity at the design layer.</p><h4>Step 2: Align Frontend CSS Variables With Tokens</h4><p>Next, frontend variables were aligned one-to-one with Figma tokens.</p><pre>:root {<br>  --color-raw-blue-600: #2563eb;<br><br>  --color-action-primary: var(--color-raw-blue-600);<br>  --color-link-default: var(--color-raw-blue-600);<br>}</pre><p>Key principles:</p><ul><li>CSS variable names mirror Figma token names — Figma design tokens are the source of truth</li><li>No frontend-only aliases</li><li>Semantic intent is preserved in naming</li></ul><p>We used a Figma plugin — <a href="https://www.figma.com/community/plugin/1522660227130874197/token-sniff">Token Sniff</a> to extract tokens directly into CSS to avoid manual drift.</p><h4>Step 3: Use Figma MCP After Alignment</h4><p>Once design tokens and frontend CSS variables are fully aligned, you can start using Figma MCP with Cursor for code generation.</p><ul><li>MCP could reliably identify the correct token</li><li>Cursor generated predictable variable usage</li><li>No more random or legacy CSS variable names</li></ul><p>The AI output became stable because the system became deterministic.</p><h3>What This Enables</h3><ul><li><strong>Deterministic CSS variable</strong> resolution during Figma MCP–based UI code generation.</li><li><strong>Consistent AI-generated UI output</strong> across components and files.</li><li><strong>Lower post-generation cleanup cost</strong> due to predictable token usage.</li><li><strong>Stronger contract between design and frontend</strong> via shared token naming.</li><li><strong>Scalable AI-assisted workflows</strong> without relying on prompt heuristics.</li></ul><h3>Key Takeaways</h3><ul><li>Figma MCP resolves variables by token name, not by value or intent.</li><li>Design token ambiguity directly results in unstable AI output.</li><li>Frontend CSS variables must align one-to-one with design tokens.</li><li>Token structure is system infrastructure, not a styling concern.</li><li>Reliable AI code generation depends more on system design than prompts.</li></ul><h3>Conclusion</h3><p>Figma MCP and Cursor didn’t require better prompts. They required a cleaner design system.</p><p>By enforcing strict token hierarchies and aligning frontend variables with Figma design tokens, we turned AI-generated UI code from “sometimes useful” into something we can reliably trust in production.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=74d699e72e38" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Syncing Data Across Browser Tabs with the BroadcastChannel API]]></title>
            <link>https://medium.com/@sachin88/syncing-data-across-browser-tabs-with-the-broadcastchannel-api-de26f61529fb?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/de26f61529fb</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[broadcast-channel-api]]></category>
            <category><![CDATA[javascript-tips]]></category>
            <category><![CDATA[web-performance]]></category>
            <category><![CDATA[user-experience]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Sun, 31 Aug 2025 08:45:41 GMT</pubDate>
            <atom:updated>2025-08-31T08:45:41.621Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qfrIfr5HuSv9yvve" /><figcaption>Photo by <a href="https://unsplash.com/@justinmin27?utm_source=medium&amp;utm_medium=referral">Justin Min</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The <strong>BroadcastChannel API</strong> in JavaScript makes it easy to sync data and state across multiple browser tabs or windows in real time.</p><p>When building modern web applications, it’s common for users to open multiple tabs of the same app. Think of scenarios like a shopping cart, logging out, or changing themes — the changes in one tab should instantly reflect in all other open tabs.</p><p>While developers often use localStorage with the storage event to handle this, there’s a much cleaner, modern approach: the <strong>BroadcastChannel API</strong>.</p><p>In this post, we’ll explore what the <strong>BroadcastChannel API</strong> is, how it works, when to use it, and how to implement it in a React/Next.js app.</p><h3>What is the BroadcastChannel API?</h3><p>The <strong>BroadcastChannel API</strong> is a simple browser API that enables communication between different browsing contexts — that means tabs, windows, iframes, or even workers — as long as they share the same origin (protocol, host, and port).</p><p>In other words, it lets you <strong>broadcast messages across multiple tabs</strong> of the same app without relying on hacks like localStorage or server-side websockets.</p><h3>How it works</h3><p>You create a channel with a unique name:</p><pre>const channel = new BroadcastChannel(&quot;my-app&quot;);</pre><p>Any tab can send a message:</p><pre>channel.postMessage({ type: &quot;LOGOUT&quot; });</pre><p>Other tabs listening to the same channel receive the message:</p><pre>channel.onmessage = (event) =&gt; {<br>  console.log(event.data); // { type: &quot;LOGOUT&quot; }<br>};</pre><p>That’s it — no polling, no storage tricks, just direct communication between tabs.</p><h3>Why use BroadcastChannel API?</h3><p>Here are a few <strong>real-world use cases</strong>:</p><ol><li><strong>Authentication Sync</strong><br> If a user logs out in one tab, all other open tabs should log out instantly.</li><li><strong>Shopping Cart Updates</strong><br> Add an item to the cart in one tab, and other tabs show the updated cart without refreshing.</li><li><strong>Theme or Settings Sync</strong><br> Toggle dark mode in one tab and see the change across all tabs.</li><li><strong>Collaborative Dashboards</strong><br> Keep multiple open dashboards in sync with real-time data changes.</li></ol><h3>Implementing BroadcastChannel in Next.js</h3><p>Let’s walk through a small <strong>example</strong> in Next.js to demonstrate syncing logout across tabs.</p><h3>Step 1: Create a broadcast utility</h3><pre>// utils/broadcast.ts<br>export const initBroadcast = (name: string) =&gt; {<br>  const channel = new BroadcastChannel(name);<br>  return channel;<br>};</pre><h3>Step 2: Setup listener in _app.tsx</h3><pre>// pages/_app.tsx<br>import { useEffect } from &quot;react&quot;;<br>import { initBroadcast } from &quot;@/utils/broadcast&quot;;<br><br>function MyApp({ Component, pageProps }) {<br>  useEffect(() =&gt; {<br>    const bc = initBroadcast(&quot;my-app&quot;);<br><br>    bc.onmessage = (event) =&gt; {<br>      if (event.data.type === &quot;LOGOUT&quot;) {<br>        // Example: redirect to login<br>        window.location.href = &quot;/login&quot;;<br>      }<br>    };<br><br>    return () =&gt; bc.close();<br>  }, []);<br><br>  return &lt;Component {...pageProps} /&gt;;<br>}<br><br>export default MyApp;</pre><h3>Step 3: Trigger an action from any tab</h3><pre>// components/LogoutButton.tsx<br>import React from &quot;react&quot;;<br><br>export default function LogoutButton() {<br>  const handleLogout = () =&gt; {<br>    const bc = new BroadcastChannel(&quot;my-app&quot;);<br><br>    bc.postMessage({ type: &quot;LOGOUT&quot; });<br>    bc.close();<br>  };<br><br>  return &lt;button onClick={handleLogout}&gt;Logout&lt;/button&gt;;<br>}</pre><p>Now, when you click <strong>Logout</strong> in one tab, every open tab of your app will receive the LOGOUT message and redirect to the login page.</p><h3>Things to Know</h3><ul><li><strong>Browser Support</strong>: The BroadcastChannel API is supported in all modern browsers (Chrome, Firefox, Safari, Edge). Internet Explorer is not supported.</li><li><strong>Same Origin</strong>: Tabs must share the same origin (protocol + host + port).</li><li><strong>Performance</strong>: It’s lightweight and faster than localStorage events since it doesn’t write to disk.</li></ul><p>If you need to support older browsers, you can provide a fallback using localStorage and the storage event.</p><h3>Final Thoughts</h3><p>The BroadcastChannel API is a powerful yet underused tool for creating <strong>seamless multi-tab experiences</strong> in web applications. Whether it’s syncing authentication state, updating shopping carts, or reflecting settings across tabs, BroadcastChannel provides a <strong>cleaner and more efficient</strong> solution than traditional hacks.</p><p>Whether you’re building with Next.js, React, or even plain JavaScript projects, the BroadcastChannel API can help deliver a smoother and more consistent experience across all open tabs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de26f61529fb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Level Up Your Coding Workflow with Cursor: A Developer’s Guide to AI Coding Assistants]]></title>
            <link>https://medium.com/@sachin88/level-up-your-coding-workflow-with-cursor-a-developers-guide-to-ai-coding-assistants-e28d21fd3968?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/e28d21fd3968</guid>
            <category><![CDATA[cursor-ai]]></category>
            <category><![CDATA[prompt-engineering]]></category>
            <category><![CDATA[ai-code-editor]]></category>
            <category><![CDATA[coding-productivity]]></category>
            <category><![CDATA[ai-assisted-coding]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Thu, 10 Apr 2025 00:31:42 GMT</pubDate>
            <atom:updated>2025-04-10T01:39:52.141Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1Z4lQGkb_KuZs7r_" /><figcaption>Photo by <a href="https://unsplash.com/@remyloz?utm_source=medium&amp;utm_medium=referral">Remy_Loz</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Whether you’re enhancing current features, working with legacy files, refactoring spaghetti code, or simply trying to improve code quality, AI can act as a powerful assistant — <strong>but only if you know how to guide it well</strong>.</p><p>AI-powered code editors are reshaping the way developers write, refactor, and understand code. While many demos highlight how AI can spin up entire projects from scratch, this post takes a more practical, real-life perspective: <strong>how to use AI effectively within an existing codebase</strong>.</p><p>This isn’t an in-depth tutorial or theoretical guide. Instead, it’s a collection of <strong>quick, actionable tips</strong> based on my real-world usage of <a href="https://www.cursor.so/">Cursor IDE</a> over the past few months. If you’re just getting started with AI coding tools, these learnings will help you get more value out of the experience and integrate AI smoothly into your development workflow.</p><h3>1. Building New Components (The Smart Way)</h3><p>When working in a mature or large codebase, adding new components is part of everyday life. AI can significantly reduce the time required — <strong>if</strong> you give it the right instructions.</p><h4>A. Break down the requirement into small, manageable tasks</h4><p>Don’t dump the entire feature description in one go. Instead, isolate smaller responsibilities like layout, state management, logic, and API integration.</p><p><strong>For example:</strong></p><ul><li><strong>First prompt:</strong> “Create the base JSX layout for a product grid with dummy data.”</li><li><strong>Follow-up:</strong> “Add filtering logic based on selected categories.”</li><li><strong>Then:</strong> “Integrate this with the fetchProducts API and handle loading/error states.”</li></ul><p><strong>Why this helps:</strong> When you chunk things down, AI understands context better and delivers results that are easier to validate. You also avoid bloated or irrelevant output.</p><h4>B. Use clear, precise prompts with well-defined expectations</h4><p>AI performs best when you give it all the context upfront: component name, expected props, how it should behave, and how it should look.</p><p><strong>Example prompt:</strong></p><blockquote><em>“Create a React component named </em><em>ProductGrid that takes in a list of products and available categories as props. Render a grid of products and display a dropdown filter to filter by category.&quot;</em></blockquote><h4>C. Iterate with follow-up prompts</h4><p>Once AI returns something useful, keep building on it:</p><ul><li>“Add skeleton loading and an error message.”</li><li>“Make the layout responsive on mobile screens.”</li><li>“Add accessibility attributes for screen readers.”</li><li>“Convert this to TypeScript with proper types.”</li></ul><p>Think of the AI like a junior developer: give it small tasks, review its output, and guide it to the finish line.</p><h3>2. Working with Existing Code</h3><p>Let’s face it — most developers spend far more time maintaining and updating existing code than writing fresh features. AI can be your best friend in this space if you scope it correctly.</p><h4>A. Adding new logic or functions</h4><p>If the new logic is independent, you can prompt AI directly.</p><p><strong>Example prompt:</strong></p><blockquote><em>“Write a helper function that takes a price number and returns a formatted currency string. It should return a fallback message for invalid input.”</em></blockquote><h4>B. Modifying existing methods</h4><p>This is where things get tricky. Broad prompts can confuse the model and lead to unintended changes.</p><p><strong>Poor prompt:</strong></p><blockquote><em>“Update handleSubmit to include validations.”</em></blockquote><p><strong>Better prompt:</strong></p><blockquote><em>“Within handleSubmit, add a condition to validate that the email ends with ‘.org’. If not, set an error message and prevent form submission.”</em></blockquote><p>By narrowing the focus, you reduce the chances of AI altering unrelated logic.</p><h4>C. Refactor large files one step at a time</h4><p>Instead of feeding AI an entire file:</p><ul><li>Highlight a single function or block</li><li><strong>Prompt: </strong>“Refactor this function to improve readability and add inline comments.”</li><li><strong>Follow-up:</strong> “Split this into two smaller pure functions.”</li><li><strong>Follow-up:</strong> “Move the API call into a separate utility module.”</li></ul><p><strong>Why this works:</strong></p><ul><li>Reduces confusion for AI and you</li><li>Easier to track and test changes</li><li>Keeps your code stable while improving it incrementally</li></ul><h3>3. Enhancing &amp; Refactoring Code</h3><p>Some of the most immediate value from AI comes from helping you polish existing code — making it cleaner, faster, and easier to understand.</p><h4>A. Optimize for performance or readability</h4><ul><li>“Refactor to use early returns to avoid deep nesting.”</li><li>“Extract the date formatting logic into a utility.”</li><li>“Rewrite this callback using async/await.”</li></ul><h4>B. Improve code documentation</h4><ul><li>“Add inline comments to explain the logic in this useEffect.”</li><li>“Generate a short summary for this file that I can include in the README.”</li></ul><h4>C. Improve naming conventions</h4><ul><li>“Suggest more descriptive names for these variables and functions based on their purpose.”</li></ul><h3>4. Chat With AI Like a Design Partner</h3><p>AI isn’t just a code writer. It’s an idea generator, rubber duck, and architectural sounding board — <strong>if you treat it that way</strong>.</p><h4>A. Use it to brainstorm solutions</h4><blockquote><em>“Should I debounce search input using useEffect, a custom hook, or lodash.debounce?”</em></blockquote><p>AI will often give you a helpful breakdown:</p><ul><li>Pros and cons of each approach</li><li>When to use useCallback or useMemo</li><li>Code snippets and common pitfalls</li></ul><h4>B. Explore architecture and trade-offs</h4><blockquote><em>“Should I use Redux or Context API for global state in a mid-sized React app?” “Should I do client-side filtering or call the API on each filter change?”</em></blockquote><h4>C. Break down work like a product manager</h4><blockquote><em>“I need to build a multi-step form. What are the logical steps to implement it in React?”</em></blockquote><h4>D. Explore better ways to improve old code</h4><blockquote><em>“Is there a cleaner pattern for handling async form submission with loading states?”</em></blockquote><p>You’re not just solving problems — you’re learning, improving, and designing smarter.</p><h3>Prompting Cheatsheet</h3><p>Here’s a quick reference guide to help you prompt more effectively:</p><p><strong>Generate new code:</strong><br>Include structure, input/output expectations, and purpose.</p><p><strong>Refactor/optimize:</strong><br>Be specific: “Make this more readable”, “Remove unnecessary nesting”, “Extract side effects.”</p><p><strong>Understand code:</strong><br>Ask: “What does this hook do?”, “Explain this function step by step.”</p><p><strong>Improve naming:</strong><br>“Suggest more expressive identifiers for these variables.”</p><p><strong>Explore design:</strong><br>“What’s a more modern or testable way to approach this?”</p><p>🚫 <strong>Bad Prompt:</strong></p><blockquote><em>“Do this for me.”</em></blockquote><p>✅ <strong>Good Prompt:</strong></p><blockquote><em>“Write a custom React hook that handles fetch requests with loading and error states.”</em></blockquote><p>Want to sharpen your prompting skills? Try this free <a href="https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/">prompt engineering course</a> which helped me articulate prompts.</p><h3>Summary</h3><p>Using AI in your code editor isn’t about replacing developers — it’s about <strong>amplifying your productivity</strong>. With clear communication, tight scope, and thoughtful iteration, you can:</p><ul><li>Ship features faster</li><li>Improve readability and maintainability</li><li>Onboard new teammates more efficiently</li><li>Learn and grow through real-time collaboration</li></ul><p>Treat AI like a trusted pair programmer — one that helps you brainstorm, write, review, and refactor code. Once you develop that habit, it becomes a seamless part of your development flow.</p><p>The future of coding isn’t solo — it’s <strong>collaborative</strong>, and AI is right there with you on the journey.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e28d21fd3968" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Prevent Build-Time Errors: Using Husky to Run ESLint on Staged Files]]></title>
            <link>https://medium.com/@sachin88/prevent-build-time-errors-using-husky-to-run-eslint-on-staged-files-c0e098645dfb?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/c0e098645dfb</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[git-hooks]]></category>
            <category><![CDATA[husky-git-hook]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Mon, 24 Feb 2025 13:24:38 GMT</pubDate>
            <atom:updated>2025-02-24T13:24:38.693Z</atom:updated>
            <content:encoded><![CDATA[<p>Linting issues can slow down development, often surfacing during builds or CI/CD runs. Catching them earlier saves time and improves workflow efficiency, reducing unnecessary debugging efforts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2wW5x4bqTwq9DhRx" /><figcaption>Photo by <a href="https://unsplash.com/@lwdzl?utm_source=medium&amp;utm_medium=referral">Jack Dong</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>Using <strong>Husky</strong> to run <strong>ESLint</strong> on staged files before committing ensures code quality and consistency. Husky enables Git hooks, letting us run scripts before commits. Combined with <strong>lint-staged</strong>, only staged files get linted, optimizing performance and reducing unnecessary computations.</p><p>This guide walks through setting up Husky to automatically lint staged files using ESLint, preventing errors from entering the repository and ensuring cleaner commits.</p><h3>What is Husky?</h3><p>Before diving into the setup, let’s first explore Husky and its capabilities. Husky is a Git hook tool that automates tasks before commits and pushes, ensuring code quality and consistency. Apart from running ESLint on staged files, it can:</p><ul><li><strong>Run Tests Before Commit</strong> — Prevents broken code by executing unit or integration tests.</li><li><strong>Enforce Code Formatting</strong> — Runs Prettier or other formatters to maintain style consistency.</li><li><strong>Prevent Bad Commits</strong> — Blocks commits with linting errors, security vulnerabilities, or missing dependencies.</li><li><strong>Automate Dependency Checks</strong> — Ensures packages are installed before committing or pushing.</li><li><strong>Enforce Commit Message Standards</strong> — Uses commitlint to maintain a structured commit history.</li></ul><h3>Step 1: Install Dependencies</h3><p>To begin, install <strong>Husky</strong> and <strong>lint-staged</strong> as development dependencies:</p><pre>npm install --save-dev husky<br>npm install --save-dev lint-staged</pre><p>These tools enforce linting rules at pre-commit, ensuring only formatted, error-free code gets committed, thus reducing the number of build-time failures.</p><h3>Step 2: Enable Husky in the Project</h3><p>Initialize Husky by running the following command:</p><pre>npx husky install</pre><p>This creates the .husky/ directory, which will store all Git hooks configured for the repository.</p><h3>Step 3: Configure Husky Pre-Commit Hook</h3><p>If you have already executed npx husky install, a <strong>pre-commit</strong> hook might already exist in the .husky/ directory, containing npm test as its default content. To modify it for linting staged files, replace its content with the following:</p><p><strong>File: </strong><strong>.husky/pre-commit</strong></p><pre>npm run lint-staged</pre><p>This command ensures that <strong>lint-staged</strong> runs whenever you attempt to commit changes.</p><p>To make sure the pre-commit script runs correctly, grant execution permissions:</p><pre>chmod ug+x .husky/*</pre><h3>Step 4: Set Up Lint-Staged</h3><h3>4.1 Configuring package.json</h3><h4>Understanding the prepare Script</h4><p>The prepare script in package.json ensures that Husky is correctly installed and configured, but it also includes a conditional check:</p><pre>{<br>   ...<br>   &quot;prepare&quot;: &quot;node -e \&quot;if (process.env.NODE_ENV !== &#39;production&#39;){process.exit(1)} \&quot; || husky install | chmod ug+x .husky/*&quot;<br>}</pre><p><strong>Breakdown:</strong></p><ol><li><strong>Check Environment</strong>: The node -e command runs an inline Node.js script to check if NODE_ENV is set to production. If it&#39;s not, the script exits with a failure status (1).</li><li><strong>Install Husky</strong>: The || operator ensures that if the previous command fails, husky install runs, setting up Git hooks.</li><li><strong>Ensure Execution Permissions</strong>: The | chmod ug+x .husky/* command ensures that Husky scripts are executable.</li></ol><p>This setup prevents Husky from running in production environments while maintaining proper permissions for pre-commit hooks.</p><p>Inside your package.json, define a script for running lint-staged:</p><p><strong>File: </strong><strong>package.json</strong></p><pre>{<br>    &quot;scripts&quot;: {<br>        &quot;lint-staged&quot;: &quot;lint-staged&quot;,<br>        &quot;prepare&quot;: &quot;node -e \&quot;if (process.env.NODE_ENV !== &#39;production&#39;){process.exit(1)} \&quot; || husky install | chmod ug+x .husky/*&quot;<br>    }<br>}</pre><p>This ensures that Husky is correctly installed and configured.</p><h3>4.2 Configuring .lintstagedrc.json</h3><p>Define which files should be linted using a <strong>lint-staged</strong> configuration file:</p><p><strong>File: </strong><strong>.lintstagedrc.json</strong></p><pre>{<br>   &quot;*.{js,tsx,ts}&quot;: [&quot;eslint --ignore-path .gitignore --fix&quot;, &quot;eslint --ignore-path .gitignore&quot;]<br>}</pre><p>This configuration ensures that JavaScript and TypeScript files are automatically formatted and checked before being committed.</p><p>Alternatively, if you’re working with <strong>Next.js</strong>, you can use a .lintstagedrc.js configuration for <strong>lint-staged</strong>.</p><h4>Configuring .lintstagedrc.js</h4><p>If you’re not using the <strong>Next.js</strong>, you can skip this configuration.</p><p><strong>File: </strong><strong>.lintstagedrc.js</strong></p><pre>// Ref: https://nextjs.org/docs/pages/api-reference/config/eslint#running-lint-on-staged-files<br><br>const path = require(&#39;path&#39;);<br><br>const buildEslintCommand = (filenames) =&gt;<br>  `next lint --fix --file ${filenames.map((f) =&gt; path.relative(process.cwd(), f)).join(&#39; --file &#39;)}`;<br><br>module.exports = {<br>  &#39;*.{js,jsx,ts,tsx}&#39;: [buildEslintCommand],<br>};</pre><p>This setup allows Next.js projects to run <strong>next lint</strong> only on staged files, optimizing performance.</p><h3>Step 5: Testing the Setup</h3><p>To verify everything works correctly, follow these steps:</p><p><strong>1. Stage a file for commit:</strong></p><blockquote>To produce <strong>ESLint</strong> error, you can import any component (from your repo) in a file and keep it unused.</blockquote><pre>git add yourfile.js</pre><p><strong>2. Attempt to commit:</strong></p><pre>git commit -m &quot;Test Husky linting&quot;</pre><p><strong>3. Check if ESLint runs on staged files:</strong></p><ul><li>If linting errors exist, the commit will be blocked until the issues are fixed.</li><li>If no errors exist, the commit will proceed.</li></ul><h3>Step 6: Troubleshooting</h3><p>If you run into issues, try the following troubleshooting steps:</p><ul><li>Ensure Husky is installed:</li></ul><pre>npx husky install</pre><ul><li>Check if the .husky/pre-commit script has execution permissions:</li></ul><pre>ls -l .husky/</pre><ul><li>Manually run lint-staged to debug errors:</li></ul><pre>npx lint-staged</pre><ul><li>If you are using Next.js, confirm that the correct ESLint configuration is being picked up.</li></ul><h3>Conclusion</h3><p>Setting up <strong>Husky</strong> with <strong>ESLint</strong> and <strong>lint-staged</strong> helps enforce code quality by catching linting errors before they reach the repository. This ensures that only properly formatted, error-free code gets committed, improving overall project maintainability and developer productivity.</p><p>By integrating this workflow into your project, you can prevent build-time errors and maintain a consistent coding standard across your team.</p><h3>Further Reading</h3><ul><li><a href="https://typicode.github.io/husky/#/">Husky Documentation</a></li><li><a href="https://eslint.org/docs/latest/user-guide/configuring/">ESLint Configuration Guide</a></li><li><a href="https://nextjs.org/docs/pages/api-reference/config/eslint">Next.js ESLint Setup</a></li></ul><p>Happy coding! 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c0e098645dfb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stripe Payment  Integration — A Breakdown of the Implementation | Part 2]]></title>
            <link>https://medium.com/@sachin88/stripe-paymentelement-integration-a-breakdown-of-the-implementation-fc228c7448ed?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc228c7448ed</guid>
            <category><![CDATA[payments]]></category>
            <category><![CDATA[payment-gateway]]></category>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[payment-processing]]></category>
            <category><![CDATA[payment-systems]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Tue, 11 Feb 2025 17:47:26 GMT</pubDate>
            <atom:updated>2025-02-11T17:47:26.023Z</atom:updated>
            <content:encoded><![CDATA[<h3>Stripe Payment Integration — A Breakdown of the Implementation | Part 2</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*f6D-MUm_C1m3bLRd" /><figcaption>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In the previous post — <a href="https://medium.com/@sachin88/stripe-payment-integration-key-concepts-payment-flow-dea79de50845">Stripe Payment Integration — Key Concepts &amp; Payment Flow | Part 1</a>, we covered the <strong>key concepts and payment flow</strong> of Stripe’s PaymentElement. Now, we’ll <strong>walk through the implementation details of an already built integration</strong>, breaking down the core steps involved in handling payments.</p><h3>Introduction</h3><p>This guide will explain how the client and server interact, how PaymentIntents are created and confirmed, and how transactions are processed securely. By the end, you’ll have a clear understanding of how this integration works and how you can adapt it to your own application.</p><p>We will be referring these two apps to learn about the stripe integration.</p><ul><li><a href="https://github.com/sachinpatel88/starter-online-store/tree/stripe-card-wallets-integration">Frontend</a><a href="https://github.com/sachinpatel88/starter-online-store/tree/stripe-card-wallets-integration"> Ap</a>p</li><li><a href="https://github.com/sachinpatel88/starter-online-store-server/tree/stripe-api-endpoints">Backend App</a></li></ul><h3>Stripe Implementation Breakdown</h3><p>In this section, we will look into each of the integration steps we had explored with the help of flow diagram in prvious post.</p><h4><strong>Step 1: Customer Initiates Payment:</strong></h4><p>Upon running the development server on the provided branch, you will see the following web app. Clicking the “Buy Now” button initiate the payment intent and redirects the user to a payment page where they can proceed with the payment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kIlYE3hjsE_8fyWsZtXhPQ.png" /></figure><h4>Step 2: <strong>Client Sends Order Details to Server:</strong></h4><p>On the <strong>client-side</strong>, clicking the “Buy Now” button will post the Product ID to our server-side API endpoint /create-payment-intent.</p><p><strong>Next:</strong> This end point generates a <strong>client_secret</strong> on server and returns the PaymentIntentId to client.</p><blockquote>Here, PaymentIntentId is an ID generated on server to map an order (client_secret) which is initiated to make a payment.</blockquote><pre>const handleSubmit = async (event) =&gt; {<br>    event.preventDefault()<br><br>    try {<br>      // Create a payment intent on the server<br>      const data = await fetch(<br>        `${import.meta.env.VITE_API_ENDPOINT}/create-payment-intent`,<br>        {<br>          method: &quot;POST&quot;,<br>          headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },<br>          body: JSON.stringify({ item: { id: product.id } }),<br>        }<br>      ).then((res) =&gt; res.json())<br><br>      if (data?.intentId) {<br>        navigate(`/order/payment/${data?.intentId}`)<br>        return<br>      }<br><br>      if (error) {<br>        console.error(&quot;Payment failed:&quot;, error)<br>        alert(&quot;Payment failed. Please try again.&quot;)<br>      } else if (paymentIntent.status === &quot;succeeded&quot;) {<br>        console.log(&quot;Payment succeeded:&quot;, paymentIntent)<br>        alert(&quot;Payment successful! Thank you for your purchase.&quot;)<br>      }<br>    } catch (error) {<br>      console.error(&quot;Error during payment:&quot;, error)<br>      alert(&quot;Something went wrong. Please try again.&quot;)<br>    }<br>  }</pre><h4>Step 3:<strong> Server Creates PaymentIntent:</strong></h4><p>On the server side, the /create-payment-intent endpoint follows three main steps, which are explained in detail after the code snippet below.</p><pre>app.post(&quot;/create-payment-intent&quot;, async (req, res) =&gt; {<br>  const { item } = req.body<br>  <br>  // Step 3.1: Fetch product details from id<br>  const productData = await db.getProductDetails(item?.id)<br><br>  // Check if the product data is found<br>  if (!productData) {<br>    res.status(500).send({<br>      paymentIntent: &quot;Data not found&quot;,<br>    })<br>    return<br>  }<br><br>  // Step 3.2: Create a PaymentIntent with the order amount and currency<br>  const paymentIntent = await stripeAPI.paymentIntents.create({<br>    amount: productData?.amount * 100,<br>    currency: productData?.currency?.toLowerCase(),<br>    // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.<br>    automatic_payment_methods: {<br>      enabled: true,<br>    },<br>  })<br><br>  // Step 3.3: Generate Intent Id and store it along with client_secret<br>  const intentId = await db.addBookingIntent(paymentIntent.client_secret)<br><br>  res.send({<br>    intentId,<br>  })<br>})</pre><p><strong>Step 3.1:</strong> First, The server fetches product details from the database based on the received Product ID.</p><blockquote>It’s recommended to use database-stored price-sensitive information instead of relying on client-side inputs, as these can be susceptible to tampering.</blockquote><p><strong>Step 3.2:</strong> Create the paymentIntent using Stripe API <strong>`paymentIntents.create()`</strong> with the Order details fetched in previous step. On success, this API returns the `<strong>client_secret</strong>`.</p><p><strong>Step 3.3:</strong> We will store this `<strong>client_secret</strong>` in database, mapped to a <strong>UUID</strong>. This <strong>UUID</strong> acts as an <strong>intent ID</strong>, allowing us to retrieve the `<strong>client_secret</strong>` in subsequent steps.</p><pre>const addBookingIntent = async (clientSecret) =&gt; {<br>  // Alternatively you can call db.write() explicitely later<br>  // to write to db.json<br>  const intentId = uuidv4()<br><br>  db.data.bookingIntents.push({ id: intentId, clientSecret })<br>  await db.write()<br><br>  return intentId<br>}</pre><h4><strong>Step 4: Client Displays Payment Form:</strong></h4><p>After receiving the Payment Intent ID from the previous step, the user is redirected to the payment page at /order/payment/${data?.intentId}.</p><p>On the payment page, the client makes a request to the server via the /get-payment-intent API endpoint to retrieve the client_secret using the Payment Intent ID.</p><p>Once the client_secret is received, the Elements provider from the react-stripe-js module is initialized. The PaymentElement component is then rendered, displaying the payment form for the user.</p><pre>// src/pages/order/payment.jsx<br>// Render block of &lt;PaymentPage ... /&gt;<br><br>const [clientSecret, setClientSecret] = useState()<br><br>useEffect(() =&gt; {<br>  if (clientSecret) return<br><br>  // Get the client_secret as soon as the page loads<br>  fetch(`${import.meta.env.VITE_API_ENDPOINT}/get-payment-intent`, {<br>    method: &quot;POST&quot;,<br>    headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },<br>    body: JSON.stringify({ intentId: params.intentId }),<br>  })<br>    .then((res) =&gt; res.json())<br>    .then((data) =&gt; setClientSecret(data.clientSecret))<br>}, [])<br><br>...<br><br>{!clientSecret ? &quot;Loading&quot; : &quot;&quot;}<br>{clientSecret &amp;&amp; (<br>    &lt;Elements stripe={stripePromise} options={options}&gt;<br>      &lt;PaymentForm intentId={params.intentId} /&gt;<br>    &lt;/Elements&gt;<br>)}<br><br>...<br><br>// src/pages/order/payment.jsx<br>// Render block of `&lt;PaymentForm ... /&gt;` component<br><br>return (<br>    &lt;&gt;<br>      &lt;h1&gt;Payment details&lt;/h1&gt;<br>      &lt;form onSubmit={handleSubmit}&gt;<br>        &lt;PaymentElement /&gt;<br><br>        &lt;button<br>          disabled={!stripe}<br>          style={{<br>            marginTop: &quot;18px&quot;,<br>          }}<br>        &gt;<br>          Submit<br>        &lt;/button&gt;<br>      &lt;/form&gt;<br>      {error}<br>    &lt;/&gt;<br>  )</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0cl1I4TxZY5MqbiCyK9VPA.png" /></figure><h4><strong>Step 5: Customer Finalises Payment:</strong></h4><p>At this stage, the customer enters their card details or selects a wallet payment option. The client then confirms the PaymentIntent using stripe.confirmPayment(). To securely transmit the payment information collected through the Payment Element to the Stripe API, the Elements instance must be accessed, as it is required to work with the stripe.confirmPayment method.</p><pre>// `&lt;PaymentForm ... /&gt;` component - src/pages/order/payment.jsx<br>const handleSubmit = async (event) =&gt; {<br>    // We don&#39;t want to let default form submission happen here,<br>    // which would refresh the page.<br>    event.preventDefault()<br><br>    if (!stripe || !elements) {<br>      // Stripe.js hasn&#39;t yet loaded.<br>      // Make sure to disable form submission until Stripe.js has loaded.<br>      return<br>    }<br><br>    const result = await stripe.confirmPayment({<br>      //`Elements` instance that was used to create the Payment Element<br>      elements,<br>      confirmParams: {<br>        return_url: `${import.meta.env.VITE_BASE_URL}/order/status/${intentId}`,<br>      },<br>    })<br><br>    if (result.error) {<br>      // Show error to your customer (for example, payment details incomplete)<br>      setError(result.error.message)<br>      console.log(result.error.message)<br>    } else {<br>      setError(&quot;&quot;)<br>      // Your customer will be redirected to your `return_url`. For some payment<br>      // methods like iDEAL, your customer will be redirected to an intermediate<br>      // site first to authorize the payment, then redirected to the `return_url`.<br>    }<br>  }<br><br>  return (<br>    &lt;&gt;<br>      &lt;h1&gt;Payment details&lt;/h1&gt;<br>      &lt;form onSubmit={handleSubmit}&gt;<br>        ...<br>      &lt;/form&gt;<br>      {error}<br>    &lt;/&gt;<br>  )</pre><h4><strong>Step 6: Stripe Processes Payment:</strong></h4><p>Once the customer submits their payment details, Stripe takes over the process. Stripe first verifies the provided payment information and checks if authentication is required.</p><p>If Strong Customer Authentication (SCA) is needed (such as 3D Secure for European transactions), Stripe prompts the customer to complete the authentication process. After successful authentication (if required), Stripe attempts to charge the customer’s payment method.</p><p>Depending on the result, the payment can either be <strong>successful, require further action, or fail</strong> due to insufficient funds, incorrect details, or other reasons.</p><h4><strong>Step 7: Server Verifies Payment Status:</strong></h4><p>After Stripe processes the payment, it sends a webhook event (payment_intent.succeeded, payment_intent.payment_failed, or payment_intent.requires_action) to the backend server. The server listens for these webhook notifications to confirm the final payment status.</p><ul><li>If the webhook event is <strong>payment_intent.succeeded</strong>, the server marks the order as paid and updates the database.</li><li>If the event is <strong>payment_intent.requires_action</strong>, the server notifies the frontend that additional authentication (e.g., 3D Secure) is needed.</li><li>If the event is <strong>payment_intent.payment_failed</strong>, the server logs the error and provides details on why the payment was unsuccessful.</li></ul><blockquote>Note: We haven’t implemented the webhook in our integration. Here, we have sample code from the stripe guide on <a href="https://docs.stripe.com/webhooks#example-endpoint"><strong>setting up the webhook</strong></a>.</blockquote><pre>// This example uses Express to receive webhooks<br>const express = require(&#39;express&#39;);<br>const app = express();<br><br>// Match the raw body to content type application/json<br>// If you are using Express v4 - v4.16 you need to use body-parser, not express, to retrieve the request body<br>app.post(&#39;/webhook&#39;, express.json({type: &#39;application/json&#39;}), (request, response) =&gt; {<br>  const event = request.body;<br><br>  // Handle the event<br>  switch (event.type) {<br>    case &#39;payment_intent.succeeded&#39;:<br>      const paymentIntent = event.data.object;<br>      // Then define and call a method to handle the successful payment intent.<br>      // handlePaymentIntentSucceeded(paymentIntent);<br>      break;<br>    case &#39;payment_method.attached&#39;:<br>      const paymentMethod = event.data.object;<br>      // Then define and call a method to handle the successful attachment of a PaymentMethod.<br>      // handlePaymentMethodAttached(paymentMethod);<br>      break;<br>    // ... handle other event types<br>    default:<br>      console.log(`Unhandled event type ${event.type}`);<br>  }<br><br>  // Return a response to acknowledge receipt of the event<br>  response.json({received: true});<br>});<br><br>app.listen(4242, () =&gt; console.log(&#39;Running on port 4242&#39;));</pre><h4><strong>Step 8: Client Updates Customer:</strong></h4><p>Once the server processes the webhook event, it sends the final payment status back to the client (frontend). The frontend updates the UI accordingly:</p><ul><li>If the payment was <strong>successful</strong>, the customer sees a confirmation message and may be redirected to a success page.</li><li>If additional action is required (e.g., authentication), the frontend prompts the customer to complete it.</li><li>If the payment <strong>fails</strong>, the UI displays an error message with options to retry or use a different payment method.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l_rp6AoQIPvzMfyu9-HOyA.png" /></figure><h3>Verify the transaction on Dashboard</h3><p>Here’s the transaction list from the Stripe Dashboard, showcasing two different payment methods — one processed using the <strong>PayNow Wallet</strong> and the other using a <strong>Card</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3AENW7DhS3UepifOFYF0BQ.png" /></figure><h3>Conclusion</h3><p>In this post, we broke down the implementation of <strong>Stripe PaymentElement</strong> by walking through the key integration steps on both the client and server sides. We explored how <strong>PaymentIntents</strong> are created and managed, how to securely handle payment details, and how Stripe dynamically offers supported payment methods to users.</p><p>By understanding this integration, you now have a solid foundation for implementing <strong>a seamless and secure payment flow</strong> in your own application. Whether you’re handling <strong>card payments, wallets, or local payment methods</strong>, Stripe’s PaymentElement provides a flexible and user-friendly solution to optimize your checkout experience.</p><p>If you’re looking to extend this setup, consider exploring <strong>subscription billing, advanced fraud prevention, or post-payment actions</strong> to further enhance your Stripe integration. 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fc228c7448ed" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stripe Payment Integration — Understanding Key Concepts & Payment Flow | Part 1]]></title>
            <link>https://medium.com/@sachin88/stripe-payment-integration-key-concepts-payment-flow-dea79de50845?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/dea79de50845</guid>
            <category><![CDATA[payment-solutions]]></category>
            <category><![CDATA[payment-gateway]]></category>
            <category><![CDATA[payment-systems]]></category>
            <category><![CDATA[stripe-integration]]></category>
            <category><![CDATA[stripe]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Tue, 11 Feb 2025 17:46:49 GMT</pubDate>
            <atom:updated>2025-02-11T17:46:49.993Z</atom:updated>
            <content:encoded><![CDATA[<h2>Stripe Payment Integration — Key Concepts &amp; Payment Flow | Part 1</h2><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DFgnTwvGwWEmv-8E" /><figcaption>Photo by <a href="https://unsplash.com/@firmbee?utm_source=medium&amp;utm_medium=referral">Firmbee.com</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Stripe provides a powerful and flexible payment processing system, but choosing the right integration approach can sometimes feel overwhelming. In this post, we’ll break down the <strong>key concepts</strong> of Stripe’s payment system, walk through the <strong>payment flow diagram</strong>, and explain the process of <strong>setting up a Stripe account</strong> and <strong>enabling wallet payments</strong> to improve the checkout experience.</p><blockquote>Stripe payment processing system is a vast topic which provides every possible solution related to payments. It won’t be possible to cover everything in these two blog posts. So we are limiting our scope to some foundation concepts and an integration guide with a common use-case.</blockquote><h3>Introduction</h3><p>In this post, we’ll explore how to identify right payment method for your need with brief overview of the available payment integration options, followed by an in-depth look at the Stripe payment flow for <strong>PaymentElement</strong> and integration steps with short description.</p><p>By the end of this guide, you’ll have a overall understanding of how Stripe’s <a href="https://docs.stripe.com/payments/payment-element">PaymentElement</a> works and be ready to move on to the implementation phase. <a href="https://medium.com/@sachin88/stripe-paymentelement-integration-a-breakdown-of-the-implementation-fc228c7448ed">In next post</a>, we’ll look into the implementation steps based on flow diagram discussed in this post.</p><h3>Choosing the Right Stripe Payment Integration</h3><p>Stripe provides a range of <a href="https://docs.stripe.com/payments/online-payments#compare-features-and-availability">payment integration options</a> designed to meet different business needs, which can sometimes make choosing the right approach feel overwhelming. The first step in the integration process is to <a href="https://docs.stripe.com/plan-integration/get-started/planning-considerations#structure-stripe-accounts">define the currencies and countries</a> you want to support.</p><p>Once that’s decided, Stripe’s documentation offers detailed tables to help you identify the <a href="https://docs.stripe.com/payments/payment-methods/payment-method-support">available payment methods</a> for each region. Based on your requirement, If you’re looking to integrate <strong>PaymentElement</strong> for a seamless checkout experience, this guide will walk you through everything you need to know.</p><h3>Payment Integration Options</h3><p>Let’s look at each payment methods and its use cases.</p><h4>Payment Links</h4><p>If you’re looking for a simple way to accept payments without building a separate website or application, <a href="https://docs.stripe.com/payments/no-code"><strong>Stripe Payment Links</strong></a> can be a great option.</p><p>They allow you to create a shareable link that customers can use to make payments or subscribe to services. You can distribute these links across multiple channels, including social media, emails, or directly on your website, making it a flexible and hassle-free payment solution.</p><h4>Checkout</h4><p><a href="https://docs.stripe.com/payments/checkout/build-integration"><strong>Checkout</strong></a> provides a low-code payment integration with a customizable payment form. You can embed it directly on your website, redirect users to a Stripe-hosted payment page, or create a tailored checkout experience using Stripe Elements. It supports both one-time payments and subscriptions while offering over 40 local payment methods.</p><h4>Billing (Subscription)</h4><p><a href="https://docs.stripe.com/subscriptions"><strong>Billing</strong></a> simplifies subscription management and invoicing. It automates recurring payments, enables custom pricing plans, and manages billing cycles, including trials and renewals — making it ideal for businesses offering subscription-based services.</p><h4>Invoicing</h4><p><a href="https://docs.stripe.com/invoicing"><strong>Invoicing</strong></a> allows you to quickly create and send professional invoices without writing any code. You can generate invoices directly from the Stripe Dashboard or use the Invoicing API to automate payment collection and reconciliation.</p><h4>Payment Element</h4><p>The <strong>Payment Element</strong> is a web-based UI component that supports over <strong>40 payment methods</strong>, handles input validation, and manages errors seamlessly. It can be used on its own or combined with other elements in your web app’s frontend for a smooth checkout experience.</p><p>Since we will be implementing the custom flow, we will be using the <a href="https://docs.stripe.com/payments/payment-intents"><strong>Payment Intents API</strong></a><strong>.</strong> It is essential for handling dynamic payment flows. It tracks the entire payment lifecycle — from creation to checkout — and ensures compliance with authentication requirements, such as Strong Customer Authentication (SCA), when necessary.</p><h3>Key concepts</h3><ol><li><strong>PaymentIntent</strong><br>A <strong>PaymentIntent</strong> represents a single transaction attempt, managing the entire payment lifecycle. It supports authentication, handles required actions, and ensures compliance with Strong Customer Authentication (SCA).</li><li><strong>PaymentElement</strong><br>The <strong>PaymentElement</strong> is a prebuilt UI component that allows users to enter payment details securely. It supports over <strong>40+ payment methods</strong>, dynamically adjusting based on the customer’s location and selected currency.</li><li><strong>Client Secret</strong><br>The <strong>client_secret</strong> is a unique key generated when creating a <strong>PaymentIntent</strong>. It is passed securely to the frontend to confirm and finalize the payment using <strong>stripe.confirmPayment()</strong>.</li><li><strong>Webhook Events</strong><br>Stripe sends <strong>webhook events</strong> to notify the server about important payment updates, such as success, failure, or disputes. This allows the backend to react accordingly, such as updating order status or triggering email notifications.</li><li><strong>Wallet Payments</strong><br><strong>Apple Pay, Google Pay, and Link</strong> are digital wallets that allow users to <strong>pay with saved cards</strong> for a frictionless checkout experience. Enabling these methods can significantly improve conversion rates, especially on mobile devices.</li><li><strong>Dynamic Payment Method Display</strong><br>Stripe automatically <strong>shows or hides payment methods</strong> based on the user’s <strong>location, currency, and device</strong>. This ensures that customers only see payment options that are relevant and supported for their region.</li><li><strong>3D Secure Authentication (SCA)</strong><br>Some transactions require <strong>Strong Customer Authentication (SCA)</strong> under regulations like <strong>PSD2</strong> in Europe. Stripe automatically handles 3D Secure (3DS) authentication by prompting users to verify their identity through OTP or biometric verification.</li><li><strong>Test and Live Modes</strong><br>Stripe provides <strong>two environments</strong>: 1) <strong>Test mode</strong> allows developers to simulate transactions using test cards without real charges. 2) <strong>Live mode</strong> processes actual payments with real customer data.</li><li><strong>Refunds and Disputes</strong><br>Merchants can issue <strong>full or partial refunds</strong> directly from the Stripe Dashboard or API. If a customer disputes a charge, Stripe provides tools to submit evidence and manage chargebacks effectively.</li><li><strong>Customer Object</strong><br>The <strong>Customer Object</strong> helps businesses store user details, including saved payment methods, billing information, and past transactions. This is useful for recurring payments and personalized user experiences.</li><li><strong>Fraud Prevention (Radar)</strong><br>Stripe <strong>Radar</strong> is a built-in fraud detection system that automatically analyzes transactions, detecting fraudulent patterns and reducing chargeback risks.</li><li><strong>Payment Statuses</strong><br>Stripe categorizes payment statuses into:</li></ol><ul><li><strong>Succeeded</strong>: Payment was processed successfully.</li><li><strong>Requires Action</strong>: Additional authentication (like 3D Secure) is needed.</li><li><strong>Failed</strong>: Payment was declined or unsuccessful.</li></ul><blockquote>Some of the concepts are not used in our blog posts. However, we still have covered those in this list.</blockquote><h3>How Custom Payment works?</h3><p>Now, we have overall idea about the key concepts and payment methods, let’s see how custom payment works with Stripe.</p><p>The diagram illustrates the flow of a payment using Stripe’s PaymentIntent API, involving four key participants: <strong>Customer</strong>, <strong>Client</strong>, <strong>Server</strong>, and <strong>Stripe</strong>.</p><figure><img alt="Stripe Custom Payment Flow" src="https://cdn-images-1.medium.com/max/1024/1*XOATKn7g_QwQCNKjFuKi4Q.png" /></figure><p>Here’s a concise explanation of each step in the client-server interaction for creating a PaymentIntent before collecting payment details:</p><p><strong>1. Customer Initiates Payment:</strong> The customer selects a product and navigates to the payment page.</p><p><strong>2. Client Sends Order Details to Server:</strong> The client sends product details (e.g., product ID, quantity) to the server via an API request.</p><p><strong>3. Server Creates PaymentIntent:</strong> The server creates a PaymentIntent using Stripe’s API and returns the `client_secret` to the client.</p><blockquote><a href="https://docs.stripe.com/payments/payment-intents"><strong>Payment Intents API</strong></a> is essential for handling dynamic payment flows. It tracks the entire payment lifecycle — from creation to checkout — and ensures compliance with authentication requirements, such as Strong Customer Authentication (SCA), when necessary.</blockquote><p><strong>4. Client Displays Payment Form:</strong> The client uses Stripe’s PaymentElement to display a form for entering payment details.</p><p><strong>5. Customer Finalises Payment:</strong> The customer submits payment details, and the client confirms the PaymentIntent using `stripe.confirmPayment()`.</p><p><strong>6. Stripe Processes Payment:</strong> Stripe processes the payment, handles authentication, and attempts to charge the customer.</p><p><strong>7. Server Verifies Payment Status:</strong> The server receives a webhook from Stripe with the payment status (e.g., success or failure).</p><p><strong>8. Client Updates Customer:</strong> The client notifies the customer of the payment status, such as success or retry options.</p><h3>Setting up Stripe Account and Payment Methods</h3><h4>Create an Account on Stripe</h4><p>You can first start with <a href="https://dashboard.stripe.com/register">creating an account</a> on Stripe. It will ask for Business and back related information. However, all the information is not required in case you are running the Test Mode.</p><h4>Enable Payment methods</h4><p>To configure payment methods, visit the <strong>Payment Methods</strong> section in the <a href="https://dashboard.stripe.com/settings/payment_methods">Stripe Dashboard</a>. Here, you’ll find a list of available payment options — some, like <strong>cards and commonly used methods</strong>, are enabled by default.</p><p>Even if multiple payment methods are enabled, Stripe intelligently determines the available options based on the customer’s <strong>currency, location, and any applicable restrictions</strong>. This dynamic offering helps improve conversion rates. For example, <strong>Apple Pay</strong> won’t appear for customers in India, while <strong>GrabPay</strong> is only available in <strong>Singapore and Malaysia</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dkuU5ur6m4xrct9fjUERdA.png" /></figure><h3>Conclusion</h3><p>In this post, we explored the <strong>key concepts</strong> of Stripe’s payment system, walked through the <strong>payment flow diagram</strong>, and covered the steps to <strong>create a Stripe account</strong> and <strong>enable wallet payments</strong> for a seamless checkout experience. Understanding these fundamentals is crucial before diving into the actual implementation.</p><p>In the next post — <a href="https://medium.com/@sachin88/stripe-paymentelement-integration-a-breakdown-of-the-implementation-fc228c7448ed">Understanding Stripe Payment Integration — A Breakdown of the Implementation | Part 2</a>, we’ll take this knowledge and walk through the <strong>step-by-step integration</strong> of Stripe’s <strong>PaymentElement</strong>. Using the flow diagram discussed here, we’ll set up both the <strong>client-side and server-side</strong> to securely handle payments and complete transactions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dea79de50845" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Foundation using Vite: Integrating Front-End and Back-End with Ease]]></title>
            <link>https://medium.com/@sachin88/building-a-foundation-integrating-front-end-and-back-end-with-ease-073a4f0248bb?source=rss-103dbd6e9269------2</link>
            <guid isPermaLink="false">https://medium.com/p/073a4f0248bb</guid>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[vites]]></category>
            <category><![CDATA[expressjs]]></category>
            <category><![CDATA[web-app-development]]></category>
            <dc:creator><![CDATA[Sachin Patel]]></dc:creator>
            <pubDate>Thu, 16 Jan 2025 16:16:34 GMT</pubDate>
            <atom:updated>2025-02-24T13:17:40.253Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>Set up your web applications effortlessly with our integrated foundation using Vite, React and Express.</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*93FsqaaYYV2ZwVcg" /><figcaption>Photo by <a href="https://unsplash.com/@brice_cooper18?utm_source=medium&amp;utm_medium=referral">Brice Cooper</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Getting started with web app development can often feel overwhelming, especially when tackling the challenge of setting up a solid project structure. This guide aims to simplify the process by helping you create a foundational setup for web applications, enabling you to build with ease and minimal effort.</p><h3>What We’ll Cover</h3><ol><li>Prerequisites</li><li>Front-end Setup</li><li>Back-end Setup</li><li>Integrating Front-end with Back-end</li></ol><h3>1. Prerequisites</h3><p>In this guide, we’ll build a web app named <strong>“starter-online-store”</strong> using the following tech stack:</p><ul><li><strong>Build tool</strong>: <a href="https://vite.dev/guide/#scaffolding-your-first-vite-project">Vite</a> — A fast and modern build tool that helps you quickly set up and develop web projects.</li><li><strong>Front-end</strong>: <a href="https://react.dev">ReactJS</a> — A popular JavaScript library for building user interfaces, especially for single-page applications.</li><li><strong>Back-end</strong>: <a href="http://expressjs.com">ExpressJS</a> — A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.</li></ul><h3>2. Front-end Setup</h3><p>In this section, we will set up the front-end of our web app by creating a <strong>React</strong> application using <strong>Vite</strong>. This will provide us with a fast and efficient development environment to build our user interface.</p><h4>Step 1: Create a React Project with Vite</h4><p>Ensure you have <strong>Node.js (v18+ or v20+)</strong> installed. Then, use the following command to scaffold a React project with Vite:</p><pre>npm create vite@latest starter-online-store -- --template react</pre><p>Follow the prompts during installation. After the setup, run these commands to initialize the project:</p><pre>cd stripe-client-app<br>npm install<br>npm run dev</pre><p>Upon successful setup, your terminal will display a local development URL (e.g., <a href="http://localhost:5173"><strong>localhost:5173</strong></a>). Visit this URL to view your project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/572/0*z93e-LDCwYpKjWJm" /></figure><h4>Step 2: Install Additional Dependencies</h4><p>To enhance your React app, install the following modules:</p><h4>2.1. Error Handling</h4><p>Install the <strong>react-error-boundary</strong> library for handling errors:</p><pre>npm install react-error-boundary</pre><blockquote><em>Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.</em></blockquote><p>Wrap your <strong>&lt;App /&gt;</strong> component with the <strong>&lt;ErrorBoundary&gt;</strong> component in <strong>src/main.jsx</strong>:</p><pre>...<br>createRoot(document.getElementById(&quot;root&quot;)).render(<br>  &lt;StrictMode&gt;<br>    &lt;ErrorBoundary&gt;<br>      &lt;App /&gt;<br>    &lt;/ErrorBoundary&gt;<br>  &lt;/StrictMode&gt;<br>)</pre><h4>2.2. Routing</h4><p>Install <strong>react-router-dom</strong> for handling routing in your app:</p><pre>npm install react-router-dom</pre><blockquote><em>This library enables you to match URLs to components and navigate between pages.</em></blockquote><h4>Step 3: Add a Sample Product to the Home Page</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/0*T_hBHSnuk_Pva52b" /></figure><h4>3.1. Create a Page Component</h4><p>Add a <strong>pages</strong> folder in the <strong>src</strong> directory and create a file named <strong>home.jsx</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/572/0*OpMc_xhN87doMXET" /></figure><p>Add the following content in <strong>home.jsx</strong> which we created in last step.</p><pre>const Home = () =&gt; {<br>  const handleSubmit = async (event) =&gt; {<br>    event.preventDefault()<br><br>    console.log(&quot;Handle Buy Now click&quot;)<br>  }<br><br>  return (<br>      &lt;div className=&quot;product-container&quot;&gt;<br>        &lt;h2&gt;Wireless Headphones&lt;/h2&gt;<br>        &lt;img<br>          src=&quot;wireless-headphone.webp&quot;  // Place this file in {YOUR_PROJECT}/public/ folder<br>          alt=&quot;Light house&quot;<br>          className=&quot;product-image&quot;<br>        /&gt;<br>        &lt;p&gt;<br>          High-quality wireless headphones with noise cancellation and 20-hour<br>          battery life.<br>        &lt;/p&gt;<br>        &lt;div className=&quot;product-price-cta&quot;&gt;<br>          &lt;p style={{ fontSize: &quot;24px&quot; }}&gt;$19.99&lt;/p&gt;<br><br>          &lt;button onClick={handleSubmit}&gt;Buy Now&lt;/button&gt;<br>        &lt;/div&gt;<br>      &lt;/div&gt;<br>  )<br>}<br><br>export { Home }</pre><h4>3.2. Update Your App.jsx File</h4><p>Replace the content of <strong>src/App.jsx</strong> with:</p><pre>import { Route, Routes } from &quot;react-router-dom&quot;<br>import { Home } from &quot;./pages/home&quot;<br><br>import &quot;./App.css&quot;<br><br>const routes = [<br>  {<br>    path: &quot;/&quot;,<br>    element: &lt;Home /&gt;,<br>  },<br>]<br><br>function App() {<br>  return (<br>    &lt;Routes&gt;<br>      {routes.map(({ path, element }, index) =&gt; (<br>        &lt;Route path={path} element={element} key={index} /&gt;<br>      ))}<br>    &lt;/Routes&gt;<br>  )<br>}<br><br>export default App</pre><p>You can directly use this <a href="https://github.com/sachinpatel88/starter-online-store/tree/master">GitHub project</a> to get started!</p><h3>Back-end Setup</h3><p>Our backend will use <strong>Express.js</strong>, a minimalist web framework for <strong>Node.js</strong>. It provides a robust set of features for building web and mobile applications efficiently.</p><h4>Step 1: Initialize the Node.js Project and Install Dependencies</h4><p>1. Navigate to your project folder in the terminal.</p><p>2. Initialize the project by running the following command:</p><pre>npm init</pre><p><em>Follow the prompts to configure your </em><strong><em>package.json</em></strong><em> file.</em></p><p>3. Install the <strong>Express.js</strong> framework:</p><pre>npm install express --save</pre><h4>Step 2: Configure ES Module Support</h4><p>To use modern <strong>ES Modules</strong> instead of the older CommonJS syntax, you need to update the package.json file by adding &quot;type&quot;: &quot;module&quot;. This change allows you to use the latest JavaScript module features, which are more efficient and easier to manage.</p><p>Additionally, you should add &quot;start-server&quot;: &quot;node index.js&quot; under the <strong>&quot;scripts&quot;</strong> section. This script provides a convenient way to start your server with a simple command, making it easier to run and manage your application.</p><pre>{<br>  &quot;name&quot;: &quot;online-store-server&quot;,<br>  ...<br>  &quot;type&quot;: &quot;module&quot;,<br>  ...<br>  &quot;scripts&quot;: {<br>    &quot;start-server&quot;: &quot;node index.js&quot;<br>  }<br>}</pre><h4>Step 3: Create the Server</h4><ol><li>At the root level of your project folder, create a new file named <strong>index.js</strong>.</li><li>Add the following code to set up a basic Express server:</li></ol><pre>import express from &quot;express&quot;;<br><br>const app = express();<br><br>app.get(&quot;/&quot;, (req, res) =&gt; {<br>  res.send(&quot;Hello World!&quot;);<br>});<br><br>app.listen(4242, () =&gt; {<br>  console.log(`Example app listening on port 4242`);<br>});</pre><h4>Step 4: Install Additional Dependencies</h4><p>We’ll also install two essential modules for future use. Open terminal and install following two modules.</p><p>1. <strong>cors</strong>: Middleware for enabling Cross-Origin Resource Sharing.</p><pre>npm install cors</pre><p>2. <strong>dotenv</strong>: Utility to load environment variables from a .env file into <strong>process.env</strong>.</p><pre>npm install dotenv</pre><h4>Step 5: Update the Server File</h4><p>Now, let’s update the <strong>server.js</strong> file to include the newly installed modules. Here’s the revised content:</p><pre>import express from &quot;express&quot;;<br>import &quot;dotenv/config&quot;;<br>import cors from &quot;cors&quot;;<br><br>const app = express();<br><br>// Enable CORS<br>app.use(cors());<br><br>// Define a sample route<br>app.get(&quot;/&quot;, (req, res) =&gt; {<br>  res.send(&quot;Hello World!&quot;);<br>});<br><br>// Start the server<br>app.listen(4242, () =&gt; {<br>  console.log(`Server is running on http://localhost:4242`);<br>});</pre><h4>Step 6: Start the Server</h4><p>Run the following command to start your server:</p><pre>npm run start-server</pre><p>You should see the server running successfully. Open your browser and visit <a href="http://localhost:4242"><strong>localhost:4242</strong></a>. You should see the “ <strong>Hello World!</strong>” message displayed.</p><h3>Integrating Front-End with Back-End: Fetching Product Details</h3><p>Now that we have our front-end and back-end set up, let’s take the next step: connecting the two. We will create an API endpoint on the server to return product details and fetch this data from the front-end to display it dynamically.</p><h4>Step 1: Create an API Endpoint on the Server</h4><ol><li>Open the index.js file in your back-end project.</li><li>Add a new route that returns product details. Update the file as follows:</li></ol><pre> import express from &quot;express&quot;;<br> import &quot;dotenv/config&quot;;<br> import cors from &quot;cors&quot;;<br><br> const app = express();<br><br> // Enable CORS<br> app.use(cors());<br><br> // Product data<br> const products = [<br>   {<br>     id: 1,<br>     name: &quot;Wireless Headphones&quot;,<br>     description: &quot;High-quality wireless headphones with noise cancellation and 20-hour battery life.&quot;,<br>     price: 99.99,<br>     image: &quot;/wireless-headphone.webp&quot;, // Place this image in the public folder of your FE project<br>   },<br> ];<br><br> // API endpoint to fetch product details<br> app.get(&quot;/api/products&quot;, (req, res) =&gt; {<br>   res.json(products);<br> });<br><br> // Sample route<br> app.get(&quot;/&quot;, (req, res) =&gt; {<br>   res.send(&quot;Hello World!&quot;);<br> });<br><br> // Start the server<br> app.listen(4242, () =&gt; {<br>   console.log(`Server is running on http://localhost:4242`);<br> });</pre><p>This endpoint (/api/products) will return a JSON response containing the product data.</p><h4>Step 2: Update the Front-End to Consume the API</h4><ol><li>Open the src/pages/home.jsx file in your front-end project.</li><li>Replace the hardcoded product data with an API call to fetch the product details. Update the file as follows:</li></ol><pre> import { useEffect, useState } from &quot;react&quot;;<br><br> const Home = () =&gt; {<br>   const [product, setProduct] = useState(null);<br><br>   useEffect(() =&gt; {<br>     const fetchProduct = async () =&gt; {<br>       try {<br>         const response = await fetch(&quot;http://localhost:4242/api/products&quot;);<br>         const data = await response.json();<br>         setProduct(data[0]); // Assuming one product for simplicity<br>       } catch (error) {<br>         console.error(&quot;Error fetching product data:&quot;, error);<br>       }<br>     };<br><br>     fetchProduct();<br>   }, []);<br><br>   const handleSubmit = (event) =&gt; {<br>     event.preventDefault();<br>     console.log(&quot;Handle Buy Now click&quot;);<br>   };<br><br>   return (<br>     &lt;div className=&quot;product-container&quot;&gt;<br>       {product ? (<br>         &lt;&gt;<br>           &lt;h2&gt;{product.name}&lt;/h2&gt;<br>           &lt;img<br>             src={product.image}<br>             alt={product.name}<br>             className=&quot;product-image&quot;<br>           /&gt;<br>           &lt;p&gt;{product.description}&lt;/p&gt;<br>           &lt;div className=&quot;product-price-cta&quot;&gt;<br>             &lt;p style={{ fontSize: &quot;24px&quot; }}&gt;${product.price}&lt;/p&gt;<br>             &lt;button onClick={handleSubmit}&gt;Buy Now&lt;/button&gt;<br>           &lt;/div&gt;<br>         &lt;/&gt;<br>       ) : (<br>         &lt;p&gt;Loading product details...&lt;/p&gt;<br>       )}<br>     &lt;/div&gt;<br>   );<br> };<br><br> export { Home };</pre><h4>Step 3: Test the Integration</h4><p>1. Re-start your back-end server:<br>Press ^ C on Mac / Ctrl + C on Windows, then run following command:</p><pre>npm run start-server</pre><p>2. Open your browser and navigate to <a href="http://localhost:5173/">http://localhost:5173</a>. You should see the product details dynamically fetched from the back-end and displayed on the front-end.</p><p>By following this integration, you’ve connected your front-end with your back-end, enabling your application to fetch and display dynamic content. This approach can be extended to handle more complex data flows and API integrations in the future.</p><h3>Conclusion</h3><p>In this article, we’ve built a solid foundation for developing a full-stack web application by setting up the front-end and back-end from scratch. We started with a modern front-end stack using React and Vite, ensuring a fast and efficient development experience. On the back-end, we leveraged Express.js to create a lightweight and scalable server, integrating essential modules like cors and dotenv to handle cross-origin requests and environment configurations seamlessly.</p><p><em>Originally published at </em><a href="https://web-lab.hashnode.dev/integrating-front-end-and-back-end-with-ease"><em>https://web-lab.hashnode.dev</em></a><em> on January 16, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=073a4f0248bb" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>