<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Misha</title>
    <description>The latest articles on Forem by Misha (@topcat).</description>
    <link>https://forem.com/topcat</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F642227%2Fabcda903-65ad-4a0a-922d-856616c1787a.jpg</url>
      <title>Forem: Misha</title>
      <link>https://forem.com/topcat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/topcat"/>
    <language>en</language>
    <item>
      <title>Overengineering OpenGraph image generation on Vercel</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Wed, 30 Jul 2025 10:54:51 +0000</pubDate>
      <link>https://forem.com/topcat/overengineering-opengraph-image-generation-on-vercel-5fh</link>
      <guid>https://forem.com/topcat/overengineering-opengraph-image-generation-on-vercel-5fh</guid>
      <description>&lt;p&gt;The OpenGraph protocol is a certified OG - literally. Been globally adopted for well over a decade, powering link previews across the entire internet. But it’s 2025 now, and being a real engineer means using Vercel and Next for everything. So today, I’m going to show you how to max out &lt;code&gt;next/og&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’re gonna build dynamic OG images using bleeding &lt;em&gt;edge&lt;/em&gt; serverless tech, in various aspect ratios (I'll explain why), and squeeze it all into Vercel’s 2MB function limit. because why not overengineer a metadata preview?&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Image Generation
&lt;/h2&gt;

&lt;p&gt;We’re using &lt;code&gt;next/og&lt;/code&gt;, which is powered by &lt;code&gt;satori&lt;/code&gt; under the hood.&lt;/p&gt;

&lt;p&gt;What you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s open source.&lt;/li&gt;
&lt;li&gt;It turns JSX into SVG using a limited subset of CSS.&lt;/li&gt;
&lt;li&gt;It works out of the box in Next.js on Vercel.&lt;/li&gt;
&lt;li&gt;Like many OSS tools from Vercel, it only works seamlessly on Vercel (unless you have extra time to set it up elsewhere - which I don't).&lt;/li&gt;
&lt;li&gt;Overall, it’s the best option if you’re already comfortable with JSX and CSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with the simplest working version: &lt;a href="https://github.com/mlshv/overengineered-og-image-generation/commit/d43e0eb2807fb7901bb3bab9af94757b746bc861" rel="noopener noreferrer"&gt;link to commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What it includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic user data via a URL &lt;code&gt;id&lt;/code&gt; param.&lt;/li&gt;
&lt;li&gt;A custom font with two variations.&lt;/li&gt;
&lt;li&gt;A page with meta tags all wired up correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste &lt;a href="https://overengineered-og-image.vercel.app/og-pages/1" rel="noopener noreferrer"&gt;this link&lt;/a&gt; into a messenger or social app - you’ll see the preview:&lt;/p&gt;

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

&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can style inline or with Tailwind. I prefer Tailwind.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;tw="..."&lt;/code&gt; instead of &lt;code&gt;className="..."&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;gap&lt;/code&gt; utility doesn’t work - &lt;code&gt;satori&lt;/code&gt; doesn’t support it yet. &lt;a href="https://github.com/vercel/satori/issues/615#issue-2304347269" rel="noopener noreferrer"&gt;Issue here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Easy enough. Let’s get weirder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Different Images for Different Platforms
&lt;/h2&gt;

&lt;p&gt;The standard OG image size is 1200x630 - a 1.91:1 ratio. But standards are boring, and no platform actually sticks to them.&lt;/p&gt;

&lt;p&gt;Different platforms render OG images differently. In fixed-width UIs (like messengers or social feeds), vertical and square images often show up bigger than horizontal ones.&lt;/p&gt;

&lt;p&gt;Check this out:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryv27x1t4hgmx7kowri7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryv27x1t4hgmx7kowri7.png" alt="Telegram square example" width="800" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Is it worth the effort? If you want to squeeze everything out of your funnel, then maybe. I’d suggest testing a few designs, playing with aspect ratios, and actually measuring the impact on conversion.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1.91:1&lt;/strong&gt; - the standard; every platform should support this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1:1.91&lt;/strong&gt; - iMessage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4:5&lt;/strong&gt; - Instagram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1:1&lt;/strong&gt; - Slack, Telegram.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the implementation: &lt;a href="https://github.com/mlshv/overengineered-og-image-generation/commit/d498d09e37c3f9c5c8cff4825401527892e717af" rel="noopener noreferrer"&gt;link to commit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Key ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;User-Agent&lt;/code&gt; header to detect the platform and choose an aspect ratio.&lt;/li&gt;
&lt;li&gt;Each ratio gets its own JSX component.&lt;/li&gt;
&lt;li&gt;I use &lt;code&gt;route.tsx&lt;/code&gt; instead of &lt;code&gt;opengraph-image.tsx&lt;/code&gt; to parse the &lt;code&gt;aspect_ratio&lt;/code&gt; URL search param.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Try pasting &lt;a href="https://overengineered-og-image.vercel.app/og-pages-ua/1" rel="noopener noreferrer"&gt;this link&lt;/a&gt; into iMessage, Telegram/Slack, or Instagram DMs. Should look different depending on where you paste it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Fixing Missing OG Images on Social Platforms
&lt;/h2&gt;

&lt;p&gt;If generation takes too long, the preview might fail to load. Platforms don’t wait.&lt;/p&gt;

&lt;p&gt;Two ways to fix it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed it up&lt;/strong&gt;&lt;br&gt;
Most of the delay is network IO - fetching from DB or storage. Cut that down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Warm the Vercel Edge Network cache&lt;/strong&gt;&lt;br&gt;
You can pre-render OG images by pinging the endpoint ahead of time. Just know you'll have to render &lt;em&gt;every variant&lt;/em&gt;, because you'll never know which one will be actually used. Still, it works.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example: for a referral invite page, call &lt;code&gt;curl "${YOUR_PAGE_URL}/opengraph-image?aspect_ratio=${ASPECT_RATIO}"&lt;/code&gt; for every aspect ratio variation you support right after creating the invite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Got a layout with static elements (logos, icons, etc)? Export it as a single image. Don’t try to manually position everything with styles - trust me.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://squoosh.app/" rel="noopener noreferrer"&gt;Squoosh&lt;/a&gt; to compress those static assets. I find turning everything to JPEG via MozJPEG the best option for size/quality balance.&lt;/li&gt;
&lt;li&gt;Minimize fonts. Each style/weight is a separate asset.&lt;/li&gt;
&lt;li&gt;Always use &lt;code&gt;Promise.all&lt;/code&gt; to load everything in parallel.&lt;/li&gt;
&lt;li&gt;Stick to WOFF fonts. TTF and OTF are too big. WOFF2 isn’t supported by &lt;code&gt;satori&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s for text. Tags like &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; come with built-in styles that’ll mess things up.&lt;/li&gt;
&lt;li&gt;For Twitter DMs, note the label that appears in the bottom-left corner of the image:&lt;/li&gt;
&lt;/ol&gt;

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




&lt;p&gt;Thanks for reading! Really means a lot. Every like, share, or comment is genuinely appreciated — your support keeps me building (and overengineering) cool stuff like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/topcatnocap" rel="noopener noreferrer"&gt;Follow me on X&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Preload splash screen in Framer (code component)</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:36:38 +0000</pubDate>
      <link>https://forem.com/topcat/preload-splash-screen-in-framer-code-component-37m</link>
      <guid>https://forem.com/topcat/preload-splash-screen-in-framer-code-component-37m</guid>
      <description>&lt;p&gt;Coming to Framer as a developer was weird: on one hand, simple things are done fast and easily, but when it came to complex ones, like creating a loading splash screen, it felt surprisingly challenging.&lt;/p&gt;

&lt;p&gt;I was very disappointed by the fact that Framer is used for building visually rich websites with lots of media content like video backgrounds and yet the problem of showing a loading screen is left unsolved.&lt;/p&gt;

&lt;p&gt;It was funny to learn that there are tutorials teaching how to build an animated loading screen (&lt;a href="https://youtu.be/ptmbqPXKLFc" rel="noopener noreferrer"&gt;like this one&lt;/a&gt;, or &lt;a href="https://youtu.be/Ts8mA9_uwlM" rel="noopener noreferrer"&gt;this one&lt;/a&gt;). But what they recommend is to use a predefined delay for these splash screens (what?).&lt;/p&gt;

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

&lt;p&gt;This means that every time a user opens a page, they will see this animation for a fixed period of time. Even if the page content is cached or wasn't loaded, the loading animation will always play for, let's say, 5 seconds. Crazy 🤯&lt;/p&gt;

&lt;p&gt;I wasn't going to give up, and I came up with an honest loading screen solution. It's not ideal but it seems like it's the only one!&lt;/p&gt;

&lt;h1&gt;
  
  
  The code
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;addPropertyControls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ControlType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;framer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;motion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;framer-motion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * These annotations control how your component sizes
 * Learn more: https://www.framer.com/developers/#code-components-auto-sizing
 *
 * @framerSupportedLayoutWidth auto
 * @framerSupportedLayoutHeight auto
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SplashScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imagesLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setImagesLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preloading images:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="c1"&gt;// toggle body scroll off&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;
                    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchPriority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="s2"&gt;`Preloaded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; images (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Couldn't preload image &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error loading images:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;finished preloading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// toggle body scroll back on&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
            &lt;span class="nf"&gt;setImagesLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;loadImages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;motion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
            &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fixed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;zIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100vh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100vw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;pointerEvents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imagesLoaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imagesLoaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;addPropertyControls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SplashScreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ControlType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ControlType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;SplashScreen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a pretty simple component and it doesn't include any progress indicator, but you can easily add it yourself by setting the preloaded images count to state and adding some animation to it.&lt;/p&gt;

&lt;p&gt;Here's how you use it: place it as the top layer of your page and list the images to preload in the component’s controls.&lt;/p&gt;

&lt;p&gt;You may ask why it only supports images. There are two reasons for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framer doesn't support &lt;code&gt;ControlType.File&lt;/code&gt; as an item of &lt;code&gt;ControlType.Image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you're preloading images and videos, just images are enough most of the time. Why? Because you can set a video's first frame as a &lt;code&gt;poster&lt;/code&gt;, and it will be displayed as a still image until it's ready to play. Otherwise, you can always add a single &lt;code&gt;ControlType.File&lt;/code&gt; for every file you have on the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find this solution useful. If you have any questions or suggestions, feel free to share them in the comments.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I've decided to post &lt;a href="https://gist.github.com/mlshv/4838be345580d916af4e5c32fc6f0d75" rel="noopener noreferrer"&gt;the code that I use on my production project&lt;/a&gt;. In this variation I use string input instead of image input because I am preloading videos as well. I hope that helps, happy hacking!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Create React UI Lib 1.1: Ladle and ESLint</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Sat, 08 Jul 2023 13:13:20 +0000</pubDate>
      <link>https://forem.com/topcat/create-react-ui-lib-11-ladle-and-eslint-42d6</link>
      <guid>https://forem.com/topcat/create-react-ui-lib-11-ladle-and-eslint-42d6</guid>
      <description>&lt;p&gt;Recently I've released &lt;a href="https://dev.to/topcat/create-react-ui-lib-component-library-speedrun-25bp"&gt;Create React UI Lib&lt;/a&gt;: a CLI tool that helps you set up and start building your own UI React component library in no time.&lt;/p&gt;

&lt;p&gt;Give it a try!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create react-ui-lib@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great news! I've just updated it with cool features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can now pick &lt;a href="https://ladle.dev/" rel="noopener noreferrer"&gt;Ladle&lt;/a&gt; over Storybook (shout out to &lt;a class="mentioned-user" href="https://dev.to/insulineru"&gt;@insulineru&lt;/a&gt; for this &lt;a href="https://dev.to/insulineru/comment/27bn2"&gt;cool idea&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;You can also add ESLint now (props to &lt;a class="mentioned-user" href="https://dev.to/femincan"&gt;@femincan&lt;/a&gt; for the &lt;a href="https://dev.to/femincan/comment/27do7"&gt;suggestion&lt;/a&gt;).  It comes with recommended settings for these plugins: &lt;a href="https://typescript-eslint.io/" rel="noopener noreferrer"&gt;typescript&lt;/a&gt;, &lt;a href="https://github.com/prettier/eslint-plugin-prettier" rel="noopener noreferrer"&gt;prettier&lt;/a&gt;, &lt;a href="https://github.com/jsx-eslint/eslint-plugin-react" rel="noopener noreferrer"&gt;react&lt;/a&gt;, &lt;a href="https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks" rel="noopener noreferrer"&gt;react-hooks&lt;/a&gt;, &lt;a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y" rel="noopener noreferrer"&gt;jsx-a11y&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;p&gt;Do let me know in the comments what you'd like me to add next. Your ideas are always welcome!&lt;/p&gt;

&lt;p&gt;If you're liking my work, support me by giving my &lt;a href="https://github.com/mlshv/create-react-ui-lib" rel="noopener noreferrer"&gt;project&lt;/a&gt; a few stars on GitHub. Also, feel free to &lt;a href="https://github.com/mlshv/create-react-ui-lib/issues/new" rel="noopener noreferrer"&gt;point out any issues&lt;/a&gt; you come across.&lt;/p&gt;




&lt;p&gt;If you enjoyed this update, give it a like and stay tuned for more.&lt;/p&gt;

&lt;p&gt;Also follow me on &lt;a href="https://twitter.com/mishgun_eth" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and connect on &lt;a href="https://www.linkedin.com/in/mikhail-malyshev-030395237/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Let's keep in touch!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Create React UI Lib: Component Library Speedrun</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Fri, 16 Jun 2023 18:12:38 +0000</pubDate>
      <link>https://forem.com/topcat/create-react-ui-lib-component-library-speedrun-25bp</link>
      <guid>https://forem.com/topcat/create-react-ui-lib-component-library-speedrun-25bp</guid>
      <description>&lt;p&gt;Setting up an NPM package for React was always exhausting for me 😮‍💨&lt;/p&gt;

&lt;p&gt;Long articles to follow, giant boilerplate repos with tools I never knew I need. It was too complex and overwhelming, even for an experienced dev such as myself. It shouldn't be this way.&lt;/p&gt;

&lt;p&gt;So I've decided to finally fix React npm package development DX.&lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://github.com/mlshv/create-react-ui-lib" rel="noopener noreferrer"&gt;Create React UI Lib&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Speedrun
&lt;/h1&gt;

&lt;p&gt;Let's create a React.js component with TypeScript typings, Storybook docs, and publish it as NPM package.&lt;/p&gt;

&lt;p&gt;Bootstrap the project by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create react-ui-lib@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the project folder with your favorite editor, run &lt;code&gt;npm start&lt;/code&gt; and get going!&lt;/p&gt;

&lt;p&gt;When you're ready to share your library:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the package: &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;package.json&lt;/code&gt;, update the description, your name, the repository, and take out &lt;code&gt;"private": true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm publish&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And to share documentation on GitHub Pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build Storybook: &lt;code&gt;npm run build-storybook&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Commit and push the &lt;code&gt;docs&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site" rel="noopener noreferrer"&gt;Get your Pages going on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And you're done! 😨&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's a demo project bootstrapped with &lt;code&gt;create-react-ui-lib&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mlshv/create-react-ui-lib-demo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://npmjs.com/package/create-react-ui-lib-demo" rel="noopener noreferrer"&gt;NPM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, it has &lt;a href="https://mlshv.github.io/create-react-ui-lib-demo" rel="noopener noreferrer"&gt;docs deployed to GitHub Pages&lt;/a&gt;. I told GitHub to take pages from &lt;code&gt;docs&lt;/code&gt; folder and boom — it just works!&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Let's Dive in
&lt;/h1&gt;

&lt;p&gt;Hey there! I know the headache. If you're looking to create and share a React component library in 2023, it feels like you have to spend forever just to get started.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which bundler should I use?&lt;/li&gt;
&lt;li&gt;How do I get TypeScript to make typings for my components?&lt;/li&gt;
&lt;li&gt;How do I make a demo or documentation?&lt;/li&gt;
&lt;li&gt;How do I set up NPM?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so on...&lt;/p&gt;

&lt;p&gt;Right now, there's no quick way to create a UI component and share it without spending a lot of time on set up. &lt;/p&gt;

&lt;p&gt;So I've made a CLI that bootstraps a minimalistic template so you can start working on your next UI React component library in seconds. I've also made a few choices for you, so you don't have to worry about the tools too much.&lt;/p&gt;

&lt;p&gt;Let's review the tech.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite
&lt;/h3&gt;

&lt;p&gt;I've picked &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; for bundling. It's quick and easy to set up. It also has a library mode to simplify bundling a library. For module types, I've specified ES and UMD — you probably won't need anything else. But if you do, you can check out &lt;a href="https://vitejs.dev/config/build-options.html#build-lib" rel="noopener noreferrer"&gt;Vite's docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript
&lt;/h3&gt;

&lt;p&gt;There's no doubt you're going to use TS in your React UI library in modern age. It's already in Vite, and type definitions for your package are pulled to &lt;code&gt;dist&lt;/code&gt; thanks to &lt;code&gt;vite-plugin-dts&lt;/code&gt;, so no extra work is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storybook
&lt;/h3&gt;

&lt;p&gt;The template already has &lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; set up. It's used as a development environment and as documentation for your library. It's built to a docs folder which is under git, so no extra effort is needed to share it on GitHub Pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bring Your Own Tools
&lt;/h3&gt;

&lt;p&gt;I made this template as unopinionated as possible not to bother you with tools you don't need or will most likely configure yourself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't come with pre-set ESLint.&lt;/li&gt;
&lt;li&gt;No styling library is included, pick the one you like.&lt;/li&gt;
&lt;li&gt;No pre-commit hooks. I think you should add them if and when you need them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, I think including popular tools as options in CLI is a good idea. I'll wait for feedback and update the library as needed.&lt;/p&gt;

&lt;p&gt;Let me know in the comments which tools you'd prefer to have pre-set. Styled Components or Tailwind? Maybe Reach or Radix? Or would you like ESLint with an AirBnb config? I'm all ears!&lt;/p&gt;

&lt;p&gt;Give &lt;a href="https://github.com/mlshv/create-react-ui-lib" rel="noopener noreferrer"&gt;the project&lt;/a&gt; some stars on GitHub and feel free to &lt;a href="https://github.com/mlshv/create-react-ui-lib/issues/new" rel="noopener noreferrer"&gt;create an issue&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you liked the post, please hit reactions and subscribe.&lt;/p&gt;

&lt;p&gt;Also follow me on &lt;a href="https://twitter.com/mishgun_eth" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and connect on &lt;a href="https://www.linkedin.com/in/mikhail-malyshev-030395237/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>node</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Correct :hover</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Mon, 12 Jun 2023 13:32:36 +0000</pubDate>
      <link>https://forem.com/topcat/the-correct-hover-5djb</link>
      <guid>https://forem.com/topcat/the-correct-hover-5djb</guid>
      <description>&lt;p&gt;If you're styling hover with CSS, I have some news for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;THERE IS NO HOVER ON TOUCHSCREENS!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why? Well, because hover is the state of an element over which a pointer (mouse cursor) is placed. Obviously, a smartphone does not know over which element a user's finger is hovering.&lt;/p&gt;

&lt;p&gt;I don't what was the logic behind it, but for some reason mobile browser developers decided to enable hover styles when tapping on an element. The problem is that tapping on an element on a touchscreen leads to the styles hanging in a hover state.&lt;/p&gt;

&lt;p&gt;What it looks like: the user taps a button, and it starts glowing as if a cursor has been placed over it. And this state is maintained until the next tap or scroll​​.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do?
&lt;/h2&gt;

&lt;p&gt;There are &lt;a href="https://caniuse.com/css-media-interaction" rel="noopener noreferrer"&gt;Interaction media features&lt;/a&gt; in CSS. It's a media query that determines whether a user can hover over elements and whether they use a precise (mouse or stylus) or imprecise (finger) pointer. Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* smartphones, touchscreens */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;coarse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* stylus-based screens */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Nintendo Wii controller, Microsoft Kinect */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;coarse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* mouse, touch pad */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using SCSS, please write yourself a mixin.&lt;/p&gt;

&lt;p&gt;And if you're using Tailwind 3+ just go to your config and set &lt;code&gt;hoverOnlyWhenSupported&lt;/code&gt; to true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hoverOnlyWhenSupported&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you like the article, please hit reactions and subscribe.&lt;/p&gt;

&lt;p&gt;Also follow me on &lt;a href="https://twitter.com/mishgun_eth" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, connect on &lt;a href="https://www.linkedin.com/in/topcatnocap/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>html</category>
      <category>beginners</category>
    </item>
    <item>
      <title>5 Ways to Make Mobile Frontend Look Like a Native App</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Mon, 12 Jun 2023 13:03:44 +0000</pubDate>
      <link>https://forem.com/topcat/5-ways-to-make-mobile-frontend-look-like-a-native-app-5eha</link>
      <guid>https://forem.com/topcat/5-ways-to-make-mobile-frontend-look-like-a-native-app-5eha</guid>
      <description>&lt;p&gt;When I create a PWA, hybrid app, or even just mobile markup, I want the interface to feel like a native app. It's difficult to achieve native smoothness on the web, that's true. But even fast and beautiful web apps are easily distinguishable from native ones due to the browser-specific UX.&lt;/p&gt;

&lt;p&gt;In this article, I will show several tricks that might even make experienced users doubt that they are not facing a native interface. I used these tricks in the mobile version of &lt;a href="https://timestripe.com" rel="noopener noreferrer"&gt;Timestripe&lt;/a&gt;, examples in the article will be from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a Manifest
&lt;/h2&gt;

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

&lt;p&gt;The Web App Manifest is part of the Progressive Web Application (PWA) technology.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A PWA is a web application that can be installed on a device and launched without a browser interface in its own window. A good PWA is awesome. Instant installation, access to native APIs, offline-first, and so on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To turn a site into a top-notch offline-first PWA, it's not a one-sprint job. But for starters, you can simply add a &lt;code&gt;manifest.json&lt;/code&gt; to the static folder, then when adding the application to the desktop, it will launch separately from the browser! Then, you can tweak the rest of the PWA features, read about it &lt;a href="https://web.dev/progressive-web-apps/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest" rel="noopener noreferrer"&gt;More about web app manifests at MDN.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://timestripe.com/static/v2/manifest.webmanifest.json" rel="noopener noreferrer"&gt;here&lt;/a&gt; you can see the tiny manifest of Timestripe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Transparent -webkit-tap-highlight-color
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufr7uiizokn3qy7292o1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufr7uiizokn3qy7292o1.gif" alt="Transparent -webkit-tap-highlight-color" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-webkit-tap-highlight-color&lt;/code&gt; property determines the color of semi-transparent rectangles that appear above interactive UI elements during pressing.&lt;/p&gt;

&lt;p&gt;This feature is necessary just in case, so that the user understands that an interaction with the interface has occurred. Therefore, if your UI elements have no issues with visual feedback, you can confidently make these highlights transparent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-tap-highlight-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fixing hover styles
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo3ckssx52h201ce4fgo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmo3ckssx52h201ce4fgo.gif" alt="Fixing hover styles" width="600" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(pay attention to the "Goal options" button)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is no hover on touchscreens! Please see &lt;a href="https://dev.to/topcat/the-correct-hover-5djb"&gt;my article on the topic&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Disabling pinch-to-zoom
&lt;/h2&gt;

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

&lt;p&gt;Because native apps do not zoom!&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;user-scalable: 0&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; 
  &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; 
  &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Removing Overscroll in Safari
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxkxdlrn6uz04jnqywbj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxkxdlrn6uz04jnqywbj.gif" alt="Removing Overscroll in Safari" width="219" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By overscroll, I mean the visual effect of scroll elasticity. In itself, it's not something bad, but an experienced user will smell the scent of the web at the sight of it.&lt;/p&gt;

&lt;p&gt;There is &lt;a href="https://www.bram.us/2016/05/02/prevent-overscroll-bounce-in-ios-mobilesafari-pure-css/" rel="noopener noreferrer"&gt;a good article&lt;/a&gt; about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: use-gesture
&lt;/h2&gt;

&lt;p&gt;For React applications, there's a superb tool - the &lt;code&gt;use-gesture&lt;/code&gt; library which has a very convenient API. With its help, you can easily add support for swipes and other gestures to a React component. The documentation includes beautiful examples, including the use of &lt;code&gt;react-spring&lt;/code&gt;. So far, these two libraries (&lt;code&gt;react-spring&lt;/code&gt; + &lt;code&gt;use-gesture&lt;/code&gt;) have provided me with the best DX for complex interface animations.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Other articles on the topic
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/app-like-pwas/" rel="noopener noreferrer"&gt;Make your PWA feel more like an app, Thomas Stainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spicefactory.co/blog/2019/10/18/native-like-pwas/" rel="noopener noreferrer"&gt;Building Native-like Experiences on the Web with PWAs, Miloš Žikić&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/swlh/how-you-can-develop-progressive-web-apps-that-feel-native-5110fbbcbf4b" rel="noopener noreferrer"&gt;How you can develop Progressive Web Apps that feel like native mobile apps, Samuele Dassatti&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you find the article useful, please hit reactions and subscribe.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://twitter.com/mishgun_eth" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, connect on &lt;a href="https://www.linkedin.com/in/topcatnocap/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also make sure to follow &lt;a href="https://instagram.com/timestripe.pro" rel="noopener noreferrer"&gt;Timestripe on Instagram&lt;/a&gt;, it's awesome!&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>pwa</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Perfect Code Makes Everyone Lose</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Tue, 06 Jun 2023 18:07:35 +0000</pubDate>
      <link>https://forem.com/topcat/perfect-code-makes-everyone-lose-1o97</link>
      <guid>https://forem.com/topcat/perfect-code-makes-everyone-lose-1o97</guid>
      <description>&lt;p&gt;Perfect code may seem like the ideal goal, but aiming for it can actually lead to loss for everyone involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;It sucks up time and resources like a black hole.&lt;/p&gt;

&lt;p&gt;Longer hours of work? Check. Higher costs? Double check.&lt;/p&gt;

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

&lt;p&gt;It's a quick ticket to burnout and a slow ticket to progress.&lt;/p&gt;

&lt;p&gt;While you're busy polishing every bit of your code, important tasks get left in the dust. Does it make you a coding superstar? Nope, just a worker ant stuck in the endless cycle of 'improvements'.&lt;/p&gt;

&lt;p&gt;Your team? Stuck trying to decode your masterpiece instead of pushing the project forward. And all for what? A shiny, 'perfect' code that took twice the time and money it should've?&lt;/p&gt;

&lt;p&gt;Here's a hint: aim for 'good enough'. It's fast, it's cheap, and it does the job. No hassle, no waste. Just effective problem-solving.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what is ‘good enough’ anyway?
&lt;/h2&gt;

&lt;p&gt;I've got a simple checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the code meet the spec?&lt;/strong&gt; Write code, make sure it meets the requirements and move on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the code follow your codebase rules?&lt;/strong&gt; Keeping things consistent is a must for easy reading and maintaining. Use linters and formatters to automate code convention checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the code pass the tests?&lt;/strong&gt; Well, that's if you've got tests to begin with. If not, trying to introduce them outside of the task's initial scope isn't a wise move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the code smell... not too much?&lt;/strong&gt; This is going to be specific to your particular case, trust your gut feeling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As long as you stick to these simple checks, you should be fine. Just don't over-engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what is over-engineering?
&lt;/h2&gt;

&lt;p&gt;Over-engineering is like building a spaceship when all you needed was a bike. It's when you take a simple task and inflate it with complexity that isn't necessary.&lt;/p&gt;

&lt;p&gt;Maybe you find yourself continuously refactoring the code you've just written, or messing with parts of the code that weren't part of the original problem. Or, adding unnecessary features for a future that may never come. These are flashing red lights on the road from 'good enough' to over-engineering.&lt;/p&gt;

&lt;p&gt;Remember, it's about solving the problem at hand, not crafting a masterpiece for the ages.&lt;/p&gt;




&lt;p&gt;If you have something to add, please leave a comment.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://twitter.com/mishgun_eth" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and connect on &lt;a href="https://www.linkedin.com/in/mikhail-malyshev-030395237/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>career</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Is your package.json safe?</title>
      <dc:creator>Misha</dc:creator>
      <pubDate>Wed, 26 Oct 2022 12:39:55 +0000</pubDate>
      <link>https://forem.com/topcat/is-your-packagejson-safe-20c1</link>
      <guid>https://forem.com/topcat/is-your-packagejson-safe-20c1</guid>
      <description>&lt;p&gt;Let's do a simple check: open any JS project that you have at hand and try to find the &lt;code&gt;^&lt;/code&gt; symbol in the package versions. If you don't have one, congratulations! Otherwise, I have bad news for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  “Compatible” versions
&lt;/h2&gt;

&lt;p&gt;By default NPM saves package versions in &lt;code&gt;package.json&lt;/code&gt; with &lt;code&gt;^&lt;/code&gt; prefix in front of the version number.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;^version&lt;/code&gt; is described as “Compatible with version” in &lt;a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json#dependencies" rel="noopener noreferrer"&gt;NPM docs&lt;/a&gt; which means “any minor or patch version equal or higher than specified”.&lt;/p&gt;

&lt;p&gt;The problem with such versions is that after removing &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; and running &lt;code&gt;npm install&lt;/code&gt; some packages may be upgraded to higher patch or minor versions without you knowing it.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;shouldn't&lt;/em&gt; break anything because usually package developers follow semantic versioning rule saying that breaking changes can only be introduced in major version updates.&lt;/p&gt;

&lt;p&gt;But in reality there is no guarantee that new minor version of a package wouldn't break something in your app. This happens even for well-tested and well-maintained packages with large community like Webpack (&lt;a href="https://github.com/webpack/webpack/issues/8082" rel="noopener noreferrer"&gt;see issue #8082&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;But there is also a much worse problem: security risks. Imagine that a hacker gets access to the NPM account of the owner of some popular package. Publishing a patch version with malicious code would immediately make all the applications using this dependency (or even package that depends on this dependency) vulnerable. You can easily find examples of such attacks by googling 'npm package hack', here is one of them: &lt;a href="https://github.com/dydxprotocol/solo/issues/521" rel="noopener noreferrer"&gt;DeFi Exchange dYdX NPM User Account Gets Hacked&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix the problem
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;save-exact=true&lt;/code&gt; to your package.json. This would make npm install specific versions of the packages that are used in your project.&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;^&lt;/code&gt; signs from the package versions in package.json. Or replace this inexact versions with actual versions of installed packages. You can find them in package-lock.json.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But there is a simpler solution: a CLI tool called &lt;a href="https://www.npmjs.com/package/exactify" rel="noopener noreferrer"&gt;exactify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've created it to automate the process of replacing “compatible with” versions with exact versions of installed packages taken from &lt;code&gt;package-lock.json&lt;/code&gt;. It also adds &lt;code&gt;save-exact=true&lt;/code&gt; to your &lt;code&gt;.npmrc&lt;/code&gt; file which forces NPM to save exact versions for further package installations.&lt;/p&gt;

&lt;p&gt;Just type &lt;code&gt;npx exactify&lt;/code&gt; in the root of your project and see what happens!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you for reading
&lt;/h2&gt;

&lt;p&gt;Let me know if you found this useful. If you have any suggestions, feature requests or bug reports, feel free to fill an issue or open a PR on &lt;a href="https://github.com/mlshv/exactify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Stars are kindly appreciated!&lt;/p&gt;

&lt;p&gt;Thank you for reading! Don't forget to hit the like button and leave a comment bellow ✨&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>security</category>
    </item>
  </channel>
</rss>
