<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fares Galal</title>
    <description>The latest articles on DEV Community by Fares Galal (@f-47).</description>
    <link>https://dev.to/f-47</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%2F816768%2Fe1dbf0c9-7ed4-443a-ac3c-5fad347d60dc.jpg</url>
      <title>DEV Community: Fares Galal</title>
      <link>https://dev.to/f-47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/f-47"/>
    <language>en</language>
    <item>
      <title>I Turned a Small Lazy Moment into a Chrome Extension</title>
      <dc:creator>Fares Galal</dc:creator>
      <pubDate>Fri, 10 Apr 2026 07:50:42 +0000</pubDate>
      <link>https://dev.to/f-47/i-turned-a-small-lazy-moment-into-a-chrome-extension-22jc</link>
      <guid>https://dev.to/f-47/i-turned-a-small-lazy-moment-into-a-chrome-extension-22jc</guid>
      <description>&lt;p&gt;I was watching a YouTube video (around 2 hours long) when I noticed something annoying: I kept moving my mouse just to check how much time had passed.&lt;/p&gt;

&lt;p&gt;It’s such a small thing, but it kept breaking my focus. And honestly, it felt ridiculous that there wasn’t a simpler solution for this.&lt;/p&gt;

&lt;p&gt;So I built one myself.&lt;/p&gt;




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

&lt;p&gt;I created a Chrome Extension that shows the current timestamp as a persistent overlay in the top-right corner while watching videos in fullscreen mode.&lt;/p&gt;

&lt;p&gt;No mouse movement needed. No interruptions. Just always visible progress.&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%2Fh2fjkyyhyeuuz25fht3k.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%2Fh2fjkyyhyeuuz25fht3k.png" alt="Preview for the overlay 1" width="393" height="94"&gt;&lt;/a&gt;&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%2Fpnxmoatxfmossohm6cmy.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%2Fpnxmoatxfmossohm6cmy.png" alt="Preview for the overlay 2" width="244" height="124"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Shortcuts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Press &lt;code&gt;Y&lt;/code&gt;&lt;/strong&gt; → Toggle the overlay on/off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Press &lt;code&gt;Shift + Y&lt;/code&gt;&lt;/strong&gt; → Change the overlay style&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;This wasn’t some big product idea. It started as pure frustration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watching long videos&lt;/li&gt;
&lt;li&gt;Constantly moving the mouse just to check progress&lt;/li&gt;
&lt;li&gt;Breaking immersion every time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of tolerating it, I built a fix in a few hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Simple, but Useful
&lt;/h2&gt;

&lt;p&gt;It’s not complex, but that’s the point.&lt;/p&gt;

&lt;p&gt;Sometimes the best tools are the ones that remove a tiny annoyance you didn’t realize was draining your attention.&lt;/p&gt;

&lt;p&gt;Now I can watch long content without constantly interacting with the UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/kaicajfaonlbgnmodfalclemdfpnbnen" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/kaicajfaonlbgnmodfalclemdfpnbnen&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Chrome Extensions API&lt;/li&gt;
&lt;li&gt;DOM manipulation&lt;/li&gt;
&lt;li&gt;Fullscreen overlay handling&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>web</category>
      <category>extensions</category>
      <category>youtube</category>
      <category>ui</category>
    </item>
    <item>
      <title>Tracking File Upload Progress on AWS S3 – Lessons from Large File Uploads</title>
      <dc:creator>Fares Galal</dc:creator>
      <pubDate>Wed, 14 Jan 2026 12:32:33 +0000</pubDate>
      <link>https://dev.to/f-47/tracking-file-upload-progress-on-aws-s3-lessons-from-large-file-uploads-4imn</link>
      <guid>https://dev.to/f-47/tracking-file-upload-progress-on-aws-s3-lessons-from-large-file-uploads-4imn</guid>
      <description>&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%2Fmyhutvhdqsz90rf9h4u4.jpeg" 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%2Fmyhutvhdqsz90rf9h4u4.jpeg" alt="Post Thumbnail" width="800" height="533"&gt;&lt;/a&gt;Have you ever tried uploading a large file to AWS S3 and wanted to &lt;strong&gt;show progress reliably&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;I recently worked on a feature that involved &lt;strong&gt;uploading videos and large files&lt;/strong&gt;, and I learned a lot about &lt;strong&gt;browser limitations, multipart uploads, and UX improvements&lt;/strong&gt;. Here’s a breakdown of what I discovered.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;onUploadProgress&lt;/code&gt; Alone Isn't Enough
&lt;/h2&gt;

&lt;p&gt;For small files, &lt;code&gt;Axios&lt;/code&gt; gives us a handy &lt;code&gt;onUploadProgress&lt;/code&gt; callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onUploadProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progressEvent&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;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;progressEvent&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;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;progressEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="o"&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;Progress:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Works perfectly for &lt;strong&gt;files ≤ 20MB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But when we tried &lt;strong&gt;large files&lt;/strong&gt; (150MB+):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Progress either &lt;strong&gt;lagged behind&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Or jumped &lt;strong&gt;directly to 100%&lt;/strong&gt; at the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Because the browser &lt;strong&gt;sends the file in a single request&lt;/strong&gt;, making &lt;code&gt;onUploadProgress&lt;/code&gt; unreliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Multipart Upload (Chunking)
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;large files&lt;/strong&gt;, the file must be split into &lt;strong&gt;chunks&lt;/strong&gt; before uploading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each chunk is a separate request&lt;/li&gt;
&lt;li&gt;Progress is tracked per chunk&lt;/li&gt;
&lt;li&gt;Failed chunks can be retried individually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 500MB file with 20MB chunks → 25 requests&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Some might ask: Isn’t 25 requests a lot?&lt;br&gt;
Not really. Each request is &lt;strong&gt;short-lived&lt;/strong&gt; and uploaded with &lt;strong&gt;controlled concurrency&lt;/strong&gt;.&lt;br&gt;
This makes uploads &lt;strong&gt;predictable&lt;/strong&gt;, &lt;strong&gt;faster&lt;/strong&gt;, and easier to retry on network failures.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Choosing the Right Chunk Size
&lt;/h2&gt;

&lt;p&gt;AWS S3 requires &lt;strong&gt;minimum 5MB per chunk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// 5MB&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DESIRED_CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 20MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check the file size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;≤ 20MB&lt;/strong&gt; → Upload normally (no multipart)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&amp;gt; 20MB&lt;/strong&gt; → Split into chunks and start multipart upload&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Why 20MB? There’s &lt;strong&gt;no magic number&lt;/strong&gt; where uploads fail. Factors like &lt;strong&gt;network speed, connection stability, and device performance&lt;/strong&gt; all play a role.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Backend Workflow for Multipart Uploads
&lt;/h2&gt;

&lt;p&gt;Previously, the backend returned a single S3 URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://my-bucket.s3.amazonaws.com/file.mp4"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UPLOAD_ID_PLACEHOLDER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/FILE_NAME_PLACEHOLDER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"partNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uploadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://my-bucket.s3.amazonaws.com/KEY_PLACEHOLDER"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EXPIRATION_DATE_PLACEHOLDER"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Each chunk is uploaded to its corresponding &lt;code&gt;uploadUrl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;S3 returns an &lt;strong&gt;ETag&lt;/strong&gt; for each chunk → acts as a fingerprint to confirm the chunk was uploaded successfully&lt;/li&gt;
&lt;li&gt;ETags + part numbers are required for &lt;code&gt;CompleteMultipartUpload&lt;/code&gt; to assemble the file in order&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tracking Progress for Large Files
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small files&lt;/strong&gt; → browser events track progress by bytes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large files&lt;/strong&gt; → track progress based on the number of chunks uploaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Progress is &lt;strong&gt;stable and predictable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Better &lt;strong&gt;UX&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No sudden &lt;strong&gt;jumps or delays&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Failed chunks can &lt;strong&gt;retry individually&lt;/strong&gt;, no need to restart the entire upload&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final Step: Complete the Upload
&lt;/h2&gt;

&lt;p&gt;Once all chunks are uploaded, send a final request with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uploadId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;All &lt;code&gt;ETags&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend calls &lt;code&gt;CompleteMultipartUpload&lt;/code&gt; → file is assembled in S3 as if it were uploaded in a single request.&lt;/p&gt;




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

&lt;p&gt;Switching to &lt;strong&gt;chunked uploads with progress tracking&lt;/strong&gt; dramatically improves the experience for large files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable progress&lt;/li&gt;
&lt;li&gt;Stable uploads&lt;/li&gt;
&lt;li&gt;Retry for individual chunks&lt;/li&gt;
&lt;li&gt;Better UX for your users&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>FormCN: Generate React Forms in Seconds</title>
      <dc:creator>Fares Galal</dc:creator>
      <pubDate>Sun, 28 Dec 2025 22:35:10 +0000</pubDate>
      <link>https://dev.to/f-47/formcn-generate-react-forms-in-seconds-1nfc</link>
      <guid>https://dev.to/f-47/formcn-generate-react-forms-in-seconds-1nfc</guid>
      <description>&lt;p&gt;Forms are one of the most common components in web applications. Whether it’s a &lt;strong&gt;login form&lt;/strong&gt;, &lt;strong&gt;registration&lt;/strong&gt;, or a &lt;strong&gt;multi-step wizard&lt;/strong&gt;, we often find ourselves writing the same boilerplate code over and over again.&lt;/p&gt;

&lt;p&gt;I wanted to change that, so I built &lt;strong&gt;FormCN&lt;/strong&gt;, my first CLI tool designed to &lt;strong&gt;generate fully-typed, validated React forms in seconds&lt;/strong&gt;, integrating &lt;strong&gt;ShadCN UI&lt;/strong&gt;, &lt;strong&gt;React Hook Form&lt;/strong&gt;, and &lt;strong&gt;Zod&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;In most projects, forms are a repetitive task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We either &lt;strong&gt;rewrite them from scratch&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Or &lt;strong&gt;copy &amp;amp; tweak old code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Or &lt;strong&gt;rely on AI generators&lt;/strong&gt;, which aren’t always precise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FormCN&lt;/strong&gt; solves this problem by automating form creation, giving you a &lt;strong&gt;ready-to-use structure&lt;/strong&gt; while keeping full control over the design and validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-step or Multi-step forms&lt;/strong&gt; — Generate forms for simple or complex workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready-made templates&lt;/strong&gt; — Login, registration, or custom forms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling presets&lt;/strong&gt; — Predefined UI styles using ShadCN components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual mode&lt;/strong&gt; — Step-by-step configuration if you want full control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic folder &amp;amp; file generation&lt;/strong&gt; — Creates the component folder with all schemas and files ready to use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart CLI checks&lt;/strong&gt; — Prevents duplicate forms, checks empty fields, ensures required dependencies like Zod, React Hook Form, and resolvers are installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;After installing FormCN via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; formcn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;formcn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be prompted to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enter form name&lt;/strong&gt; — e.g., &lt;code&gt;register&lt;/code&gt; → creates &lt;code&gt;RegisterForm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose form type&lt;/strong&gt; — single-step or multi-step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a template&lt;/strong&gt; — ready template or manual configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select a styling preset&lt;/strong&gt; — default, minimal, or custom.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, FormCN generates a complete folder with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/forms/{FormName}/
├── schema.ts
└── form.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for multi-step forms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/forms/{FormName}/
├── step1-schema.ts
├── step1Step.tsx
└── {FormName}Form.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is &lt;strong&gt;ready to use or tweak&lt;/strong&gt; in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Will Love It
&lt;/h2&gt;

&lt;p&gt;FormCN is built for &lt;strong&gt;React developers who want to focus on features instead of boilerplate&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript-first&lt;/strong&gt; with Zod validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beautiful UI&lt;/strong&gt; with ShadCN components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional CLI&lt;/strong&gt; that prevents errors and guides the developer&lt;/li&gt;
&lt;li&gt;Saves &lt;strong&gt;hours of repetitive work&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NPM:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/formcn" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/formcn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/F-47/formcn" rel="noopener noreferrer"&gt;https://github.com/F-47/formcn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FormCN is &lt;strong&gt;actively maintained&lt;/strong&gt;, and I’d love to get your &lt;strong&gt;feedback, ideas, or contributions&lt;/strong&gt;. If you try it out, let me know what templates or presets you’d like to see next!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>shadcn</category>
      <category>react</category>
      <category>zod</category>
    </item>
  </channel>
</rss>
