<?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: Shuvo</title>
    <description>The latest articles on DEV Community by Shuvo (@0shuvo0).</description>
    <link>https://dev.to/0shuvo0</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F728097%2F22055fc3-95c3-4684-8344-ac7727d2dc3a.png</url>
      <title>DEV Community: Shuvo</title>
      <link>https://dev.to/0shuvo0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0shuvo0"/>
    <language>en</language>
    <item>
      <title>My friend's Game Boy got stolen so I built him one</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Sat, 03 Oct 2026 06:45:29 +0000</pubDate>
      <link>https://dev.to/0shuvo0/my-friends-game-boy-got-stolen-so-i-built-him-one-3lnn</link>
      <guid>https://dev.to/0shuvo0/my-friends-game-boy-got-stolen-so-i-built-him-one-3lnn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My friend's Game Boy Color got stolen. Rude. Then he went looking for a replacement and found out that decent used ones are now rare, overpriced, or "mint condition" in the way that means "mint-flavored lie."&lt;/p&gt;

&lt;p&gt;So I built him a new one that can't be stolen: a &lt;strong&gt;Game Boy Color in the browser&lt;/strong&gt;. It's teal, it has a D-pad that clicks, and it plays real Game Boy games.&lt;/p&gt;

&lt;p&gt;The twist is that he never has to scroll through a ROM list. He just tells it what he's in the mood for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I want to play a game involving Pokémon."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A small AI reads the request, picks a game from the collection, and boots it. He can type the request or tap the mic and say it out loud. A bit like a game store clerk who lives inside the console.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff82oi9rintgdt2uyaqvb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff82oi9rintgdt2uyaqvb.gif" alt="Game demo" width="600" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/0shuvo0" rel="noopener noreferrer"&gt;
        0shuvo0
      &lt;/a&gt; / &lt;a href="https://github.com/0shuvo0/Game-boy-color-emulator" rel="noopener noreferrer"&gt;
        Game-boy-color-emulator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Browser-based Gameboy Color Emulator with AI game search capabilities using Google gemma 4b
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/0shuvo0/Game-boy-color-emulator/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2F0shuvo0%2FGame-boy-color-emulator%2FHEAD%2Fdemo.gif" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Browser-based Gameboy Color Emulator&lt;/h1&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;with AI game search capabilities using Google gemma 4b&lt;/h2&gt;

&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: For the AI feature to work you need to have &lt;a href="https://ollama.com/library/gemma3:4b" rel="nofollow noopener noreferrer"&gt;https://ollama.com/library/gemma3:4b&lt;/a&gt; installed and running on your machine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;in the package.json file there is a script called "start:ai" which will start the ollama server.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/0shuvo0/Game-boy-color-emulator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Emulator:&lt;/strong&gt; The mGBA core compiled to WebAssembly, wired up in a Vite + TypeScript project. It loads ROMs straight from &lt;code&gt;public/gameroms&lt;/code&gt;, including ones packed inside ZIP files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controls:&lt;/strong&gt; Keyboard (&lt;code&gt;Z&lt;/code&gt;/&lt;code&gt;X&lt;/code&gt;/&lt;code&gt;Enter&lt;/code&gt;/&lt;code&gt;Shift&lt;/code&gt;), touch buttons on the on-screen console, and gamepad support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AI picker:&lt;/strong&gt; Gemma 3 (4B), running locally through &lt;strong&gt;Ollama&lt;/strong&gt;. When he makes a request, the app sends it along with the list of available games. Gemma picks the best match and the emulator loads it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice:&lt;/strong&gt; The browser's built-in speech recognition fills in the prompt and submits it when he stops talking. It's not an AI model, but it makes the whole thing feel like magic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escape hatch:&lt;/strong&gt; If he knows what he wants, "Choose a game instead" opens a normal list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt is simple: "here's what the player wants, here's the game list, pick one." The game list is just a file (&lt;code&gt;src/games.ts&lt;/code&gt;), so adding a game means dropping a ROM in a folder and adding its name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;This project needed an AI that could do one small job well: match a vibe to a filename. A giant cloud model would be overkill, and a cloud API would have made it worse in a few ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing.&lt;/strong&gt; No API key, no per-request bill, no "you've hit your limit" while someone is trying to play Pokémon. For a gift to a friend, that matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs locally.&lt;/strong&gt; His requests never leave the machine. Nobody needs to know what he plays at 2 AM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's swappable.&lt;/strong&gt; Gemma 3 4B was small and fast enough. If a different model picks better, it's one line to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't break on me.&lt;/strong&gt; No deprecation emails, no pricing changes, no surprise outages. The model is on the disk, and it stays there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 4B open model on a normal machine turned out to be plenty for this. It does the job without anyone's permission or a credit card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: Gemma 3 (4B) running locally through Ollama powers the natural-language game picker.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hacktoberfest 2026, what changed?</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Thu, 01 Oct 2026 04:06:27 +0000</pubDate>
      <link>https://dev.to/0shuvo0/hacktoberfest-2026-what-changed-1g3g</link>
      <guid>https://dev.to/0shuvo0/hacktoberfest-2026-what-changed-1g3g</guid>
      <description>&lt;h1&gt;
  
  
  Hacktoberfest 2026 Is Not What You Remember (And That's Actually Good News)
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Last updated: October 1, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you've done Hacktoberfest before, you probably remember the drill: open four pull requests, wait for the merge notifications, and wear your free T-shirt with quiet pride.&lt;/p&gt;

&lt;p&gt;Forget all of that. &lt;strong&gt;Pull requests no longer count toward Hacktoberfest rewards.&lt;/strong&gt; The whole event has been rebuilt from the ground up, and this year it's about learning and building with open-source AI.&lt;/p&gt;

&lt;p&gt;Don't close the tab yet. The new version is free, open to beginners, and has more ways to earn swag than the old one did. This guide walks through what changed, why, and exactly how to take part, online or in person.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 60-second version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pull requests are out.&lt;/strong&gt; Neither PRs nor merge requests count toward rewards anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The theme is open-source AI.&lt;/strong&gt; The tagline is "AI belongs to everyone," and the focus is open-weight models, agents, and the tools around them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are two ways to join:&lt;/strong&gt; 300+ one-day in-person events called &lt;strong&gt;Fests&lt;/strong&gt;, and an &lt;strong&gt;online program&lt;/strong&gt; with livestreams, challenges, and a week-long event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewards run on stickers.&lt;/strong&gt; You earn virtual stickers for activities, and real stickers get mailed to you once you collect enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's free&lt;/strong&gt;, and you need to be 13 or older.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLH and DEV run it now&lt;/strong&gt;, with DigitalOcean (the original creator) as presenting partner.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Old vs. new: what actually changed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;The old Hacktoberfest (2014-2025)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Hacktoberfest 2026&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core task&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open 4 pull requests on participating repos&lt;/td&gt;
&lt;td&gt;Learn and build with open-source AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How progress is measured&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PR count&lt;/td&gt;
&lt;td&gt;Virtual stickers from activities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Online, solo, on GitHub&lt;/td&gt;
&lt;td&gt;300+ in-person Fests plus an online program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reward&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;T-shirt (or a tree planted, in some years)&lt;/td&gt;
&lt;td&gt;Sticker pack by mail, with extra tiers up to a raffle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who runs it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DigitalOcean&lt;/td&gt;
&lt;td&gt;MLH and DEV, with DigitalOcean presenting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Open-source contributions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The entire point&lt;/td&gt;
&lt;td&gt;Still welcome, just not rewarded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Community element&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mostly social media and Discord&lt;/td&gt;
&lt;td&gt;Real events in real cities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T-shirt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The headline prize&lt;/td&gt;
&lt;td&gt;Only at Fests, while supplies last, not guaranteed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A small correction to a common memory: older editions let you contribute to &lt;em&gt;any&lt;/em&gt; public repo that opted in, not just a short curated list. The 2026 change is bigger than swapping the list. The counter itself is gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why they changed it
&lt;/h2&gt;

&lt;p&gt;Short answer: the spam got out of hand.&lt;/p&gt;

&lt;p&gt;The organizers say it's now easier than ever to churn out low-effort pull requests, and maintainers kept paying the price in review time and burnout. Their own history page describes the 2015-2025 era as one where thousands of people made a first contribution, but the flood of low-quality PRs eventually became too much for the people on the receiving end.&lt;/p&gt;

&lt;p&gt;So rather than patching the rules again, they dropped the incentive. The mission page puts it simply: open source was never defined by a PR counter, and this year they'd rather help people learn to build with open models and tools.&lt;/p&gt;

&lt;p&gt;You can agree or disagree with the call. But the reasoning is easy to follow, and anyone who has ever maintained a popular repo in October will probably nod along.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet the new cast: MLH, DEV, and DigitalOcean
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.mlh.com/" rel="noopener noreferrer"&gt;Major League Hacking (MLH)&lt;/a&gt;&lt;/strong&gt; runs the Fests and the sign-in system (MyMLH).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to"&gt;DEV&lt;/a&gt;&lt;/strong&gt; runs the online challenges and hands out badges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;&lt;/strong&gt; started Hacktoberfest in 2014 and is still on board as presenting partner.&lt;/li&gt;
&lt;li&gt;A long sponsor list backs the swag and events, including GitHub, MongoDB, Snowflake, Google Cloud, IBM, Qualcomm, Arduino, and many others.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Two ways to take part
&lt;/h2&gt;

&lt;p&gt;You can do either one, or both. They stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path 1: Go to a Fest (in person)
&lt;/h3&gt;

&lt;p&gt;A Fest is a free, one-day, locally organized event. Almost all of them are free, though a few pop-ups happen inside ticketed conferences. There are two flavors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Hack Day&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Meetup&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vibe&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mini hackathon&lt;/td&gt;
&lt;td&gt;Community hangout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What you do&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build a project with open-weight models or open-source AI tools, then demo it&lt;/td&gt;
&lt;td&gt;Talks, workshops, panels, and meeting people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Solo or any team size (prizes cover up to four members)&lt;/td&gt;
&lt;td&gt;No team needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prizes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, usually a swag bag&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Laptop and charger&lt;/td&gt;
&lt;td&gt;Curiosity, plus a laptop only if the host asks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Beginners are welcome.&lt;/strong&gt; You don't need to build an AI project to attend a Meetup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everyone on a team must register and check in.&lt;/strong&gt; One person submits the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projects must be finished and submitted on the day,&lt;/strong&gt; before the host's deadline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every in-person attendee gets an official participation certificate,&lt;/strong&gt; available in your My Hacktoberfest dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swag varies by Fest.&lt;/strong&gt; T-shirts and other goodies are limited and not guaranteed. The local host decides how to hand them out. Select Hack Days also have Arduino boards available while supplies last.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to join a Fest:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://hacktoberfest.com/fests/" rel="noopener noreferrer"&gt;Find a Fest&lt;/a&gt; and search by city, country, or name (or use your location).&lt;/li&gt;
&lt;li&gt;Open the Fest's page and read the details: date, venue, food, accessibility, and what to install or bring.&lt;/li&gt;
&lt;li&gt;Register on that Fest's own page with a free MyMLH account. Every Fest has a separate registration.&lt;/li&gt;
&lt;li&gt;Show up, check in with the host, and say hi to a stranger.&lt;/li&gt;
&lt;li&gt;Use the &lt;strong&gt;Contact Host&lt;/strong&gt; button on the page for anything about logistics, accommodations, or late arrival.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Path 2: Join online (from anywhere)
&lt;/h3&gt;

&lt;p&gt;The online program runs all month and has three main pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Livestreams.&lt;/strong&gt; Sessions on open-weight models, agents, and tools. You check in with a code shown on screen to collect a sticker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV Challenges.&lt;/strong&gt; A weekend kickoff challenge plus four weekly open-source AI rounds, all labeled as cash-prize challenges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Hack Week: Hacktoberfest.&lt;/strong&gt; A week-long virtual festival of livestreams and challenges, run by MLH.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the dates as listed on DEV's challenge hub and in community guides. Always double-check the deadline printed on each official challenge page, because that's the one that counts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Activity&lt;/th&gt;
&lt;th&gt;Dates (UTC, as reported)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Weekend Challenge&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 2 (02:00) to Oct 5 (06:59)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Open-Source AI Challenge, Week 1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 5 to Oct 11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://events.mlh.com/events/14553-global-hack-week-hacktoberfest" rel="noopener noreferrer"&gt;Global Hack Week&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 9 to Oct 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Open-Source AI Challenge, Week 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 12 to Oct 18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Open-Source AI Challenge, Week 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 19 to Oct 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Open-Source AI Challenge, Week 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Oct 26 to Oct 31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full online calendar lives on the &lt;a href="https://hacktoberfest.com/schedule/" rel="noopener noreferrer"&gt;official schedule page&lt;/a&gt; and shows times in your own time zone.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; the exact prize amounts and the actual challenge prompts weren't public when I researched this. The prompts are revealed when each challenge launches, so don't start building a "winning entry" before you've read the real brief. Challenge announcements go out under DEV's &lt;a href="https://dev.to/t/hf26challenge"&gt;&lt;code&gt;#hf26challenge&lt;/code&gt; tag&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The sticker system, explained
&lt;/h2&gt;

&lt;p&gt;This is the new reward engine, and it's friendlier than it sounds.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;24 virtual stickers&lt;/strong&gt; in total: 22 you can earn online, plus 2 you earn in person (attend a Fest, or host one). Each one is tied to an activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reward tiers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stickers collected&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A real, mailed Hacktoberfest 2026 sticker pack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A bonus holographic sticker added to your pack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Completionist&lt;/strong&gt; status, plus automatic entry into a raffle for a Hacktoberfest 2026 T-shirt or an Arduino Uno Q board&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two stickers are &lt;strong&gt;required&lt;/strong&gt; before anything ships: signing in to Hacktoberfest.com, and adding your mailing address to your MyMLH account. Do those first. They also count toward your total, so you're already two-thirds of the way to the first reward after five minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the rest come from
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Examples of what earns a sticker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Livestreams&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Check into 1, 3, or 5 livestreams, or the launch livestream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEV Challenges&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connect your DEV account, then submit to the Launch Weekend and Weeks 1 to 4 challenges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Global Hack Week&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complete registration challenges, check into a session, and hit 15, 30, or 75 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Install and log in to DevRelay, join the MLH Community Discord, connect your DigitalOcean account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Surveys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fill out the pre-event and post-event surveys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;In person&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Attend a Fest, or host one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full, up-to-date list is on the &lt;a href="https://hacktoberfest.com/activities/" rel="noopener noreferrer"&gt;Activities page&lt;/a&gt;, and your personal progress lives in &lt;a href="https://hacktoberfest.com/my/" rel="noopener noreferrer"&gt;My Hacktoberfest&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good to know about swag
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Online participants don't get T-shirts.&lt;/strong&gt; Those are a Fest thing, and even there they aren't guaranteed. Completionists can still win one in the raffle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mailing takes a while.&lt;/strong&gt; The main site says stickers and prizes go out 8 to 12 weeks after Hacktoberfest ends, while the FAQ says packs should arrive at most destinations within 30 to 60 days of shipping starting. Plan for a wait either way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activity done early still counts.&lt;/strong&gt; The FAQ says eligible activity completed before milestones were announced is credited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signing in alone won't earn you a pack.&lt;/strong&gt; You need to do at least a few activities.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your quick-start checklist
&lt;/h2&gt;

&lt;p&gt;If you only do one section of this article, make it this one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today (5 to 10 minutes):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in at &lt;a href="https://hacktoberfest.com/my/" rel="noopener noreferrer"&gt;My Hacktoberfest&lt;/a&gt; with a free MyMLH account.&lt;/li&gt;
&lt;li&gt;Add your mailing address to MyMLH.&lt;/li&gt;
&lt;li&gt;Connect your DEV account (optional, but it earns a sticker and a DEV badge, and it's needed for DEV Challenges credit).&lt;/li&gt;
&lt;li&gt;Join the &lt;a href="https://discord.mlh.io" rel="noopener noreferrer"&gt;MLH Community Discord&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Register for &lt;a href="https://events.mlh.com/events/14553-global-hack-week-hacktoberfest" rel="noopener noreferrer"&gt;Global Hack Week: Hacktoberfest&lt;/a&gt;, which is free.&lt;/li&gt;
&lt;li&gt;Follow each &lt;a href="https://dev.to/challenges/hacktoberfest"&gt;DEV Hacktoberfest challenge&lt;/a&gt; so you get launch notifications.&lt;/li&gt;
&lt;li&gt;Check the &lt;a href="https://hacktoberfest.com/schedule/" rel="noopener noreferrer"&gt;schedule&lt;/a&gt; and mark a few livestreams.&lt;/li&gt;
&lt;li&gt;Look up &lt;a href="https://hacktoberfest.com/fests/" rel="noopener noreferrer"&gt;Fests near you&lt;/a&gt; and register if one fits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;All month:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check into livestreams and submit to challenges as they open.&lt;/li&gt;
&lt;li&gt;Fill out the pre-event survey early and the post-event survey at the end.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What should you build? Ideas to get started
&lt;/h2&gt;

&lt;p&gt;The official line is that instead of counting PRs, you'll write your first &lt;code&gt;skills.md&lt;/code&gt;, build an open-source agent, fine-tune an open-weight model, or follow your curiosity. Here are some starter ideas by difficulty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beginner-friendly&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A reusable agent &lt;strong&gt;skill&lt;/strong&gt;: a folder with a &lt;code&gt;SKILL.md&lt;/code&gt;, a README, a license, and a couple of examples.&lt;/li&gt;
&lt;li&gt;A small local chatbot running an open-weight model through a local runtime such as Ollama or llama.cpp.&lt;/li&gt;
&lt;li&gt;A tiny &lt;strong&gt;evaluation script&lt;/strong&gt; that checks whether a model gives consistent answers to the same prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Intermediate&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small &lt;strong&gt;open-source agent&lt;/strong&gt; that combines a local model, a few tools (files, APIs, MCP servers), and simple memory.&lt;/li&gt;
&lt;li&gt;A CPU-friendly &lt;strong&gt;fine-tuning experiment&lt;/strong&gt; using Hugging Face's &lt;a href="https://github.com/huggingface/peft" rel="noopener noreferrer"&gt;PEFT&lt;/a&gt; or &lt;a href="https://github.com/huggingface/trl" rel="noopener noreferrer"&gt;TRL&lt;/a&gt;. Keep it small. A tiny project that works beats a giant one that doesn't.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;tool-call correctness test suite&lt;/strong&gt; or prompt regression tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ambitious&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve an open-source &lt;strong&gt;agent harness or SDK&lt;/strong&gt;: better error messages, safer permissions, clearer setup on Windows, macOS, or Linux.&lt;/li&gt;
&lt;li&gt;Build an &lt;strong&gt;evaluation dashboard&lt;/strong&gt; that compares open-weight models side by side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good project checklist before you submit anything: what real problem does it solve, which license applies, which model and under what terms, can someone else run it without hidden steps, is there a short demo or screenshot, and are the limitations written down.&lt;/p&gt;




&lt;h2&gt;
  
  
  But I still want to contribute to open source!
&lt;/h2&gt;

&lt;p&gt;You absolutely can, and nobody is stopping you. The organizers say outright that open source runs 365 days a year and that you can contribute anytime. They just aren't giving swag for it anymore. They point newcomers to this &lt;a href="https://dev.to/opensauced/open-source-101-a-beginners-guide-to-getting-started-37fb"&gt;beginner's guide to getting started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're going to send a PR this October, here's how to be the kind of contributor maintainers love:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; first.&lt;/strong&gt; Some projects require you to be assigned the issue, get design approval, sign a CLA, or follow an AI-disclosure policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check that the issue is still open and unclaimed,&lt;/strong&gt; with no competing PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce the problem yourself.&lt;/strong&gt; A fix without a reproduction is a guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the change small and focused,&lt;/strong&gt; and add a test that fails before your fix and passes after it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be honest about AI help.&lt;/strong&gt; Many repos now ask for disclosure. Never submit code you can't explain or test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say what you're about to do&lt;/strong&gt; before writing a lot of code. A short comment like "I reproduced this, here's my plan, anyone already on it?" saves everyone time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where to look
&lt;/h3&gt;

&lt;p&gt;Hacktoberfest has no official participating-repo list this year. These are active projects in the open-AI orbit that have accepted outside contributions. Treat them as places to browse, not guaranteed openings, since issues get claimed quickly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;MCP TypeScript SDK&lt;/a&gt; and &lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/huggingface/peft" rel="noopener noreferrer"&gt;Hugging Face PEFT&lt;/a&gt; and &lt;a href="https://github.com/huggingface/trl" rel="noopener noreferrer"&gt;TRL&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Giskard-AI/giskard-oss" rel="noopener noreferrer"&gt;Giskard&lt;/a&gt; (AI evaluation)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mdn/content" rel="noopener noreferrer"&gt;MDN content&lt;/a&gt; (documentation, with low setup cost)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browser-use/browser-use" rel="noopener noreferrer"&gt;Browser Use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PrefectHQ/fastmcp" rel="noopener noreferrer"&gt;FastMCP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A smaller, well-scoped issue in a project with a clear guide is almost always a better bet than a big feature nobody has approved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to host a Fest?
&lt;/h2&gt;

&lt;p&gt;Anyone can. If your city has no Fest yet, it could be you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Hack Day&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Meetup&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Attendees build projects&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workshops and speakers&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prizes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MLH funding&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Applications are open and are confirmed on a rolling basis, within about a week of a completed application.&lt;/li&gt;
&lt;li&gt;Hosts get stickers, swag, and prizes for participants, a listing in the searchable directory, programming help, and (for Hack Days) funding with reimbursement for certain event expenses.&lt;/li&gt;
&lt;li&gt;Start here: &lt;a href="https://hacktoberfest.com/host/" rel="noopener noreferrer"&gt;Host a Fest&lt;/a&gt;, then read the &lt;a href="https://mlh.gitbook.io/mlh-hacktoberfest-organizer-guide" rel="noopener noreferrer"&gt;host handbook&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Hosting also earns you a sticker and a DEV badge.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do pull requests count at all?&lt;/strong&gt;&lt;br&gt;
No. They don't count toward rewards, and the organizers say that's deliberate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I still get a T-shirt?&lt;/strong&gt;&lt;br&gt;
Maybe, but only at a Fest while supplies last (the host decides), or through the Completionist raffle if you hit 17 stickers. Online participants aren't promised one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who can join?&lt;/strong&gt;&lt;br&gt;
People aged 13 and over, subject to country eligibility rules. Individual Fests can have their own age limits, and if you're under 18 you should ask the host about parental permission. DEV Challenges have separate contest eligibility rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it free?&lt;/strong&gt;&lt;br&gt;
Yes, including nearly all Fests and Global Hack Week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to build an AI project to take part?&lt;/strong&gt;&lt;br&gt;
No. You can watch livestreams, attend a Meetup, join the Discord, and do surveys. Those all count toward stickers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a GPU?&lt;/strong&gt;&lt;br&gt;
Not for everything. Plenty of beginner-friendly work (documentation, SDK bugs, evaluation tools, small fine-tuning experiments) runs fine on a laptop CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who do I contact with problems?&lt;/strong&gt;&lt;br&gt;
Use &lt;strong&gt;Contact Host&lt;/strong&gt; on a Fest's page for event questions, the relevant challenge page for DEV Challenge questions, and the support emails listed on the &lt;a href="https://hacktoberfest.com/questions/" rel="noopener noreferrer"&gt;official FAQ&lt;/a&gt; for swag, prizes, or safety concerns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;It's okay to feel a little nostalgic. The four-PR challenge got a lot of people into open source, and it's strange to see it retired.&lt;/p&gt;

&lt;p&gt;But the new format has a lot going for it. Maintainers get a break from the spam. Beginners get real events and a clear, low-stakes path to a reward. And if you've been meaning to try out open-weight models or build your first agent, October now comes with a built-in excuse, a community, and stickers.&lt;/p&gt;

&lt;p&gt;So sign in, add your address, grab those first two stickers, and see where the month takes you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hacktoberfest.com/" rel="noopener noreferrer"&gt;Hacktoberfest 2026 home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hacktoberfest.com/questions/" rel="noopener noreferrer"&gt;Official FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hacktoberfest.com/online/" rel="noopener noreferrer"&gt;Attend online&lt;/a&gt; and the &lt;a href="https://hacktoberfest.com/activities/" rel="noopener noreferrer"&gt;Activities page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hacktoberfest.com/in-person/" rel="noopener noreferrer"&gt;Attend in person&lt;/a&gt; and &lt;a href="https://hacktoberfest.com/fests/" rel="noopener noreferrer"&gt;Find a Fest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hacktoberfest.com/host/" rel="noopener noreferrer"&gt;Host a Fest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hacktoberfest.com/mission/" rel="noopener noreferrer"&gt;Mission and history&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/challenges/hacktoberfest"&gt;DEV Hacktoberfest challenges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aniruddhaadak/hacktoberfest-2026-what-online-builders-can-join-win-and-claim-4fge"&gt;Hacktoberfest 2026: what online builders can join, win, and claim&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hacktoberfest</category>
      <category>hackathon</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>You Can't Even Make an AI Wrapper Chat App 😆🫵</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Fri, 05 Jun 2026 05:48:23 +0000</pubDate>
      <link>https://dev.to/0shuvo0/you-cant-even-make-an-ai-wrapper-chat-app-3ph5</link>
      <guid>https://dev.to/0shuvo0/you-cant-even-make-an-ai-wrapper-chat-app-3ph5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"It's just a fetch call, bro." — Every junior dev, moments before learning a hard lesson.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you want to build a ChatGPT wrapper. Fair enough. It sounds embarrassingly simple — you've got an API key, a text box, and ambition. How hard can it be?&lt;/p&gt;

&lt;p&gt;Turns out: pretty hard. Not &lt;em&gt;rocket science&lt;/em&gt; hard, but &lt;em&gt;"oh god why is my chat broken on refresh"&lt;/em&gt; hard.&lt;/p&gt;

&lt;p&gt;Let's walk through the three stages of enlightenment.&lt;/p&gt;




&lt;h2&gt;
  
  
  🟢 Stage 1: The Junior Dev Move
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"Ship it. It works on my machine."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The junior dev opens a blank React project, installs &lt;code&gt;axios&lt;/code&gt; (or doesn't, and just uses &lt;code&gt;fetch&lt;/code&gt;), and writes something like this:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;setReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Deployed. LinkedIn post drafted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem?&lt;/strong&gt; You're waiting for the &lt;em&gt;entire&lt;/em&gt; AI response to generate before anything shows up on screen. For a short reply, fine. For a longer one? You're staring at a blank screen for 10–15 seconds while your users quietly close the tab.&lt;/p&gt;

&lt;p&gt;It feels like dial-up internet. In 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🟡 Stage 2: The Mid-Level Dev Move
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"I've heard of this 'streaming' thing."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The mid-level dev has seen the ChatGPT UI. Tokens appear one by one, like the AI is &lt;em&gt;thinking&lt;/em&gt;. That's streaming — and it's a vastly better experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;while &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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;Now tokens stream in real-time. The app &lt;em&gt;feels&lt;/em&gt; alive. Users are happy. You are happy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem?&lt;/strong&gt; Hit refresh. &lt;/p&gt;

&lt;p&gt;Gone. All of it. Every conversation, vanished. The app has the memory of a goldfish. Each new page load is a blank slate — no history, no context, no continuity.&lt;/p&gt;

&lt;p&gt;You've built a chat app that forgets you exist the moment you leave. That's not a chat app. That's a very expensive &lt;code&gt;alert()&lt;/code&gt; box.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔴 Stage 3: The Senior Dev Move
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"Streaming is table stakes. Persistence is the real work."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The senior dev knows that a chat app without history isn't a product — it's a demo. Real users expect to close the tab, come back tomorrow, and pick up where they left off. They expect their conversation to &lt;em&gt;exist&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This means the stream itself needs to be saved &lt;em&gt;as it arrives&lt;/em&gt; — not after it's done, not on the next request, but &lt;strong&gt;token by token&lt;/strong&gt;, persisted to a database in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend — save while streaming:&lt;/strong&gt;&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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="c1"&gt;// Save user message immediately&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;fullResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fullResponse&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Save complete assistant response after stream finishes&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fullResponse&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&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;&lt;strong&gt;Frontend — load history on mount:&lt;/strong&gt;&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="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/conversations/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when a user refreshes? Their chat is still there. When they come back on mobile? Still there. When they share a conversation link? You get the idea.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But wait, there's more.&lt;/strong&gt; The senior dev also thinks about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending the full conversation history back to the AI (so it has &lt;em&gt;actual&lt;/em&gt; context)&lt;/li&gt;
&lt;li&gt;Handling mid-stream disconnects gracefully (what if the user closes the tab at 50%?)&lt;/li&gt;
&lt;li&gt;Debouncing saves so you're not hammering your DB on every single token&lt;/li&gt;
&lt;li&gt;Auth, so users only see &lt;em&gt;their&lt;/em&gt; conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is glamorous. It's just... what production looks like.&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;What they build&lt;/th&gt;
&lt;th&gt;What's missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Junior&lt;/td&gt;
&lt;td&gt;Request → Response&lt;/td&gt;
&lt;td&gt;Speed, UX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid&lt;/td&gt;
&lt;td&gt;Streaming response&lt;/td&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Senior&lt;/td&gt;
&lt;td&gt;Streaming + saved history&lt;/td&gt;
&lt;td&gt;(probably something else they'll discover next week)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap between a junior and senior implementation isn't really about the AI part at all — the API call is the easy bit. It's about treating the &lt;em&gt;surrounding infrastructure&lt;/em&gt; with the same seriousness as the feature itself.&lt;/p&gt;

&lt;p&gt;So next time someone says "I built an AI chat app over the weekend" — ask them what happens when they refresh the page.&lt;/p&gt;

&lt;p&gt;The silence will tell you everything.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Now go add persistence to your side project. You know who you are.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Before AI we had ...</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Fri, 22 May 2026 04:12:37 +0000</pubDate>
      <link>https://dev.to/0shuvo0/before-ai-we-had--5a0g</link>
      <guid>https://dev.to/0shuvo0/before-ai-we-had--5a0g</guid>
      <description>&lt;p&gt;Nowadays you will hear a lot of people saying &lt;strong&gt;Why should I hire a software developer when I can vibecode it my self with about $100 worth of AI tools?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Well they aren't wrong about that!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But getting an app made and running was never the hard part. Before &lt;strong&gt;AI or vibe coding&lt;/strong&gt; we had platforms like &lt;strong&gt;Fiverr/UpWork&lt;/strong&gt; where people could just pay a third world country developer &lt;strong&gt;$50-$100&lt;/strong&gt; to build them their app.&lt;/p&gt;

&lt;p&gt;So getting a &lt;strong&gt;working APP&lt;/strong&gt; or at least &lt;strong&gt;MVP&lt;/strong&gt; was never really very hard or expensive rather its the &lt;strong&gt;distribution&lt;/strong&gt; part that kills 99% of the apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;And for handling complex apps or codebases &lt;strong&gt;cheap developers&lt;/strong&gt; were not reliable then, &lt;strong&gt;AI tools&lt;/strong&gt; are not reliable now!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>discuss</category>
      <category>coding</category>
    </item>
    <item>
      <title>Frontend rate limiting can save you $10,000</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Thu, 21 May 2026 04:04:07 +0000</pubDate>
      <link>https://dev.to/0shuvo0/frontend-rate-limiting-can-save-you-10000-272l</link>
      <guid>https://dev.to/0shuvo0/frontend-rate-limiting-can-save-you-10000-272l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Fastest Requests Are the Ones You Never Make&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While backend rate limiting is absolutely crucial, &lt;strong&gt;it comes at a cost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For backend rate limiting, we usually store a user's IP and request timestamps in &lt;strong&gt;memory&lt;/strong&gt; or a &lt;strong&gt;Redis database&lt;/strong&gt;. If your app has millions of users, you end up storing a lot of data in memory or a database — and that can get expensive fast.&lt;/p&gt;

&lt;p&gt;But what if &lt;strong&gt;we block requests &lt;em&gt;before&lt;/em&gt; they ever leave the browser/client&lt;/strong&gt;? By handling rate limiting on the client side, we can save a significant number of unnecessary request and read/write operations on the backend. Depending on how many users your app has, this can translate to anywhere between &lt;strong&gt;hundreds to thousands of dollars saved per year&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1: Basic frontend rate limiter example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// utils/rate-limiter.ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// Maximum number of requests allowed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TIME_WINDOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Time window in milliseconds (1 minute)&lt;/span&gt;

&lt;span class="c1"&gt;// Store timestamps of recent requests&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Check if a new request is within the allowed limit&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canMakeRequest&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove timestamps that have fallen outside the time window&lt;/span&gt;
    &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;TIME_WINDOW&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_REQUESTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Drop-in fetch wrapper with rate limiting&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt;&lt;span class="p"&gt;):&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="nx"&gt;Response&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;canMakeRequest&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rate limit exceeded. Please try again later.&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="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Better rate limiter with cooldown time and localstorage implementation so user can't just reset limit by refreshing the page
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Front end rate limiter&lt;/span&gt;
&lt;span class="c1"&gt;// Max number of requests allowed per time window&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Maximum number of requests allowed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TIME_WINDOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Time window in milliseconds (e.g., 1 minute)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COOLDOWN_TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Time to wait after hitting the rate limit before requests are allowed again&lt;/span&gt;

&lt;span class="c1"&gt;// localStorage keys for persisting state across page refreshes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STORAGE_KEY_TIMESTAMPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate_limiter_timestamps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STORAGE_KEY_COOLDOWN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate_limiter_cooldown_until&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- localStorage helpers ---&lt;/span&gt;

&lt;span class="c1"&gt;// Load request timestamps from localStorage, falling back to an empty array&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadTimestamps&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;number&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STORAGE_KEY_TIMESTAMPS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stored&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;catch&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;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Persist the current timestamps array to localStorage&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveTimestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="k"&gt;void&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STORAGE_KEY_TIMESTAMPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamps&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="c1"&gt;// Silently fail if localStorage is unavailable (e.g. private browsing restrictions)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Load the cooldown expiry timestamp (ms since epoch), or 0 if none is set&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadCooldownUntil&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;number&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;return&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STORAGE_KEY_COOLDOWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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="c1"&gt;// Persist the cooldown expiry timestamp to localStorage&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveCooldownUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;until&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STORAGE_KEY_COOLDOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;until&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="c1"&gt;// Silently fail if localStorage is unavailable&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// --- Core rate-limit logic ---&lt;/span&gt;

&lt;span class="c1"&gt;// Function to check if a request can be made&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canMakeRequest&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// If an active cooldown exists, block all requests until it expires&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cooldownUntil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadCooldownUntil&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cooldownUntil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cooldownUntil&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Load persisted timestamps so a page refresh doesn't reset the counter&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadTimestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove timestamps that are outside the time window&lt;/span&gt;
    &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;TIME_WINDOW&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Persist the cleaned-up timestamps&lt;/span&gt;
    &lt;span class="nf"&gt;saveTimestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if the number of requests in the time window is less than the maximum allowed&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_REQUESTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Function to make a request&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestInit&lt;/span&gt;&lt;span class="p"&gt;):&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="nx"&gt;Response&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;canMakeRequest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Inform the caller how long (in seconds) they need to wait&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remaining&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;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;loadCooldownUntil&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&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;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rate limit exceeded. Please try again in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Load, update, and persist timestamps before making the request&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadTimestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;saveTimestamps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If this request tips us over the limit, start a cooldown period.&lt;/span&gt;
    &lt;span class="c1"&gt;// The cooldown begins NOW and lasts COOLDOWN_TIME ms, after which&lt;/span&gt;
    &lt;span class="c1"&gt;// canMakeRequest() will unblock and the sliding window will have cleared.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestTimestamps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_REQUESTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;saveCooldownUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;COOLDOWN_TIME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Make the actual fetch request&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Whenever you need to make a request to the backend, just use &lt;code&gt;$fetch&lt;/code&gt; instead of the native &lt;code&gt;fetch&lt;/code&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;// example usage&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utils/rate-limiter&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/get-data&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&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;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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; This will prevent the vast majority of users from flooding your backend, but a determined minority and AI bots may attempt to bypass client-side checks entirely. &lt;strong&gt;Backend rate limiting must still be in place&lt;/strong&gt; as your true line of defense. Think of frontend rate limiting as an optimization layer, not a security layer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When to Use Frontend Rate Limiting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your app has a high volume of users and you want to reduce unnecessary backend load&lt;/li&gt;
&lt;li&gt;The endpoint being called is expensive (e.g., hits a third-party API, runs a heavy DB query)&lt;/li&gt;
&lt;li&gt;You want to improve UX by giving users instant feedback instead of waiting for a backend &lt;code&gt;429&lt;/code&gt; response&lt;/li&gt;
&lt;li&gt;You want to reduce cloud costs on services that charge per request or per DB operation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When &lt;em&gt;Not&lt;/em&gt; to Use Frontend Rate Limiting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;As a &lt;strong&gt;replacement&lt;/strong&gt; for backend rate limiting — it is never a substitute&lt;/li&gt;
&lt;li&gt;For protecting sensitive or security-critical endpoints (authentication, payments, etc.)&lt;/li&gt;
&lt;li&gt;When your users are primarily API consumers or developers who interact directly with your backend&lt;/li&gt;
&lt;li&gt;When the client environment is untrusted (e.g., mobile apps that can be reverse-engineered or patched)&lt;/li&gt;
&lt;li&gt;When accurate rate limiting is a hard requirement — client-side state can be reset simply by refreshing the page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>coding</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your trycatch sucks - lets fix it</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Thu, 21 May 2026 03:19:50 +0000</pubDate>
      <link>https://dev.to/0shuvo0/your-trycatch-sucks-lets-fix-it-37e9</link>
      <guid>https://dev.to/0shuvo0/your-trycatch-sucks-lets-fix-it-37e9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You're not handling errors. You're &lt;em&gt;hiding&lt;/em&gt; them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every app crashes. Every API fails. Every database hiccups at 2am on a Friday.&lt;br&gt;
The difference between a good dev and a great one? &lt;strong&gt;What happens next.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's roast your error handling — then make it legendary.&lt;/p&gt;


&lt;h2&gt;
  
  
  🤦 Level 0: The "Trust Me Bro" Dev
&lt;/h2&gt;

&lt;p&gt;No try/catch at all. Just vibes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 💥 TypeError: Cannot read properties of undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The crime:&lt;/strong&gt; One bad response nukes the entire app. Users see a white screen. You get a 3am Slack ping.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐣 Level 1: The Junior — "I Googled try/catch"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 and... that's it. shipped.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's wrong here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;console.log&lt;/code&gt; in production helps &lt;em&gt;nobody&lt;/em&gt; — users still see a broken UI&lt;/li&gt;
&lt;li&gt;No distinction between a 404 and a 500 — every error is treated the same&lt;/li&gt;
&lt;li&gt;The error disappears into the void (or a DevTools tab nobody has open)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;err&lt;/code&gt; might be &lt;code&gt;null&lt;/code&gt;, a string, or an &lt;code&gt;Error&lt;/code&gt; object — you're not checking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The mindset:&lt;/strong&gt; &lt;em&gt;"At least it won't crash."&lt;/em&gt; — Yeah, it just silently breaks instead. Cool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📈 Level 2: The Mid-Level — "I've Been Burned Before"
&lt;/h2&gt;

&lt;p&gt;Now we're thinking. You've seen production fires. You have trust issues with APIs. Good.&lt;/p&gt;

&lt;h3&gt;
  
  
  2a — Typed errors, real messages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NetworkError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection lost. Check your internet.&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;warning&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="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found.&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;error&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong. We're on it.&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;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;logger&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;[fetchUserData]&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="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 👈 goes to Sentry/Datadog&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;✅ Users get &lt;em&gt;useful&lt;/em&gt; feedback, not a frozen screen&lt;br&gt;
✅ Engineers get &lt;em&gt;structured logs&lt;/em&gt;, not a haystack of &lt;code&gt;console.log&lt;/code&gt;s&lt;/p&gt;


&lt;h3&gt;
  
  
  2b — Undo previous operations (the "atomic mindset")
&lt;/h3&gt;

&lt;p&gt;Imagine you're updating a user's profile &lt;em&gt;and&lt;/em&gt; their avatar. Step 1 succeeds. Step 2 fails.&lt;br&gt;
Congrats — your user now has a corrupted half-state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;previousProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="nx"&gt;previousProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// snapshot&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newProfileData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// step 1&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadAvatar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newAvatar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// step 2 💥 fails here&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&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;Profile update failed&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="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ↩️ Roll back step 1 since step 2 failed&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousProfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;previousProfile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Update failed. Your profile has been restored.&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;warning&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Users never see broken half-state&lt;br&gt;
✅ Rollback is &lt;em&gt;explicit&lt;/em&gt;, not accidental&lt;/p&gt;


&lt;h3&gt;
  
  
  2c — Wrap it in a clean utility (stop repeating yourself)
&lt;/h3&gt;

&lt;p&gt;Tired of writing &lt;code&gt;try/catch&lt;/code&gt; 50 times? Make a helper:&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;// utils/tryCatch.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tryCatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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;err&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="nx"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&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="c1"&gt;// Usage — clean, flat, readable&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;tryCatch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Couldn't load user.&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;error&lt;/span&gt;&lt;span class="dl"&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;}&lt;/span&gt;

&lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ No more deeply nested try/catch pyramids&lt;br&gt;
✅ Forces you to &lt;em&gt;handle&lt;/em&gt; the error at call site — can't ignore it&lt;/p&gt;


&lt;h2&gt;
  
  
  🧠 Level 3: The Senior — "I've Seen Things"
&lt;/h2&gt;

&lt;p&gt;You don't just &lt;em&gt;catch&lt;/em&gt; errors. You &lt;em&gt;anticipate&lt;/em&gt; them. You build systems that heal themselves.&lt;/p&gt;
&lt;h3&gt;
  
  
  3a — Retry queue with exponential backoff
&lt;/h3&gt;

&lt;p&gt;Networks are flaky. Don't give up on the first failure.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&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;err&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="nx"&gt;isLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;retries&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;isRetryable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NetworkError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLast&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isRetryable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// don't retry 401s or 404s&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 500ms → 1s → 2s&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed. Retrying in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backoff&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="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchWithRetry&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Temporary blips are invisible to users&lt;br&gt;
✅ Smart: retries server errors, not client errors (no point retrying a 401)&lt;/p&gt;


&lt;h3&gt;
  
  
  3b — Circuit breaker (stop hammering a dead service)
&lt;/h3&gt;

&lt;p&gt;A retry queue is great — unless the &lt;em&gt;whole service is down&lt;/em&gt;. Then you're just DDoS-ing a corpse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// CLOSED = healthy, OPEN = tripped, HALF_OPEN = testing&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextAttempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&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;Circuit open — service unavailable&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HALF_OPEN&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="k"&gt;try&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recordFailure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&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="nf"&gt;recordFailure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;logger&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;🔴 Circuit breaker TRIPPED&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;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&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;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userServiceBreaker&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;CircuitBreaker&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userServiceBreaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Failing fast is &lt;em&gt;kind&lt;/em&gt; — users get an error immediately, not after 10s of retrying&lt;br&gt;
✅ Gives the downstream service a breather to recover&lt;/p&gt;


&lt;h3&gt;
  
  
  3c — Structured error classes (errors that &lt;em&gt;mean something&lt;/em&gt;)
&lt;/h3&gt;

&lt;p&gt;Stop throwing raw strings or generic &lt;code&gt;Error&lt;/code&gt;s. Give your errors context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;retryable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AppError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retryable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&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;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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="c1"&gt;// Subclass for specificity&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AUTH_ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;retryable&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceUnavailableError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AppError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&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;service&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is unavailable`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVICE_DOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;retryable&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Throwing&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ServiceUnavailableError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UserService&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="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Catching&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;AuthError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;redirectToLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;AppError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;retryQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logger&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unexpected error. Engineers notified.&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;✅ Catch blocks can make &lt;em&gt;decisions&lt;/em&gt;, not just log and pray&lt;br&gt;
✅ Every error carries its own context — no more guessing what happened&lt;/p&gt;




&lt;h2&gt;
  
  
  🗺️ The Full Picture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;th&gt;Junior&lt;/th&gt;
&lt;th&gt;Mid&lt;/th&gt;
&lt;th&gt;Senior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catch errors&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inform the user&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send to a logger&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typed/structured errors&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;⚠️ partial&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback on failure&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry transient errors&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circuit breaker&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors are self-describing&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ✅ The Golden Rules
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never swallow errors silently.&lt;/strong&gt; A hidden bug is a time bomb.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always tell the user &lt;em&gt;something&lt;/em&gt;.&lt;/strong&gt; Frozen UI is worse than an error message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log with context&lt;/strong&gt;, not just a message — &lt;em&gt;what&lt;/em&gt; failed, &lt;em&gt;who&lt;/em&gt; triggered it, &lt;em&gt;when&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not all errors are equal&lt;/strong&gt; — 404 ≠ 500 ≠ NetworkError. Handle them differently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retryable ≠ always retry&lt;/strong&gt; — client errors (4xx) should fail fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave the system in a valid state.&lt;/strong&gt; Roll back or compensate when operations are partial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your &lt;code&gt;catch&lt;/code&gt; block is business logic&lt;/strong&gt; — treat it that way.&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;"The mark of a great engineer isn't writing code that never fails.&lt;br&gt;&lt;br&gt;
It's writing code that &lt;em&gt;fails gracefully&lt;/em&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now go fix your try/catches. 🛠️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt="GitMission Preview" width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>coding</category>
      <category>programming</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I Built a Fully Automated YouTube Channel That Uploads Every Day - Without Touching It</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 20 May 2026 04:57:27 +0000</pubDate>
      <link>https://dev.to/0shuvo0/i-built-a-fully-automated-youtube-channel-that-uploads-every-day-without-touching-it-1a2i</link>
      <guid>https://dev.to/0shuvo0/i-built-a-fully-automated-youtube-channel-that-uploads-every-day-without-touching-it-1a2i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Every morning, a video goes live on my YouTube channel. I didn't film it. I didn't edit it. My computer did all of it while I slept.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the story of how I built &lt;strong&gt;&lt;a href="https://www.youtube.com/@ProgressOfYear" rel="noopener noreferrer"&gt;Progress Bar of the Year&lt;/a&gt;&lt;/strong&gt; - a fully automated YouTube Shorts channel that posts a daily update on how much of the current year has passed - and the surprisingly elegant tech stack behind it.&lt;br&gt;
&lt;a href="https://www.youtube.com/@ProgressOfYear" rel="noopener noreferrer"&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%2F5wk671r70fzcqoj6kz8z.png" alt="YouTube channel preview" width="799" height="671"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Idea: Simple, But Strangely Satisfying
&lt;/h2&gt;

&lt;p&gt;The premise couldn't be simpler: every day, a short video goes up showing &lt;strong&gt;what percentage of the year has elapsed&lt;/strong&gt;, paired with a fun historical fact about something that happened on this same day in a previous year.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;But there's something almost meditative about watching the year tick forward - and something genuinely cool about a channel that &lt;em&gt;runs itself&lt;/em&gt;. No burnout, no filming schedule, no editor. Just code, running quietly on a small computer in the corner.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Tech Stack at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core logic&lt;/td&gt;
&lt;td&gt;JavaScript / TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical facts&lt;/td&gt;
&lt;td&gt;Google Gemini API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video generation&lt;/td&gt;
&lt;td&gt;Remotion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload&lt;/td&gt;
&lt;td&gt;YouTube Data API v3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation&lt;/td&gt;
&lt;td&gt;Bash + crontab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's break down each piece.&lt;/p&gt;



&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fwzzlb8ltgzwelahgpuyy.png" alt=" " width="799" height="471"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: The Math - How Much of the Year Has Passed?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Calculates today's date, the total days in the current year, and the percentage of the year that's gone by.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; This is pure JavaScript - no libraries needed. The logic boils down to three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&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;Date&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;startOfYear&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endOfYear&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startOfYear&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;endOfYear&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startOfYear&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;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also accounts for &lt;strong&gt;leap years&lt;/strong&gt; automatically, since &lt;code&gt;endOfYear - startOfYear&lt;/code&gt; will naturally be 366 days in a leap year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this approach:&lt;/strong&gt; JavaScript's &lt;code&gt;Date&lt;/code&gt; object handles all the heavy lifting. There's no edge case to manually code - the language figures out whether it's February 28th or 29th, whether we're in a leap year, all of it. Simple and bulletproof.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; A number like &lt;code&gt;38.21&lt;/code&gt; - meaning 38.21% of 2026 has already passed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: The History Hook - Google Gemini API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Fetches a genuinely interesting historical event that happened on &lt;strong&gt;this exact calendar day&lt;/strong&gt; in a previous year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI instead of a static database?&lt;/strong&gt; A hardcoded database of historical facts would work - but it would be finite, brittle, and boring to maintain. The Gemini API gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An effectively &lt;strong&gt;unlimited&lt;/strong&gt; and varied set of historical facts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Natural language output&lt;/strong&gt; ready to display on screen&lt;/li&gt;
&lt;li&gt;The ability to request facts in a specific format or tone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt; A prompt is sent to the Gemini API each day with the current month and day, asking for a notable historical event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  Give me one interesting historical event that happened on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;month&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;day&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 
  in any previous year. Keep it to 1-2 sentences. Be specific with the year.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gemini&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;fact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API returns something like: &lt;em&gt;"49 years ago on this day, the first public screening of Star Wars generated enormous excitement among audiences who had never seen anything like it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters for the channel:&lt;/strong&gt; It transforms what could be a dry statistics update into something actually worth watching. Every video has a little historical surprise baked in. It gives viewers a reason to come back — not just for the percentage, but for the story of the day.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Video Generation - Remotion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Takes the percentage and the historical fact and renders them into a polished, animated MP4 video - entirely in code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Remotion?&lt;/strong&gt; Remotion is a framework that lets you &lt;strong&gt;write videos using React&lt;/strong&gt;. Instead of opening Premiere Pro or After Effects, you define your video as a React component. Animations, timing, transitions - all written in TypeScript. Then you render it to an actual video file using Node.js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Remotion over other tools?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full programmatic control&lt;/strong&gt; - Every pixel, every animation, every timing decision is in code. No manual steps, no GUI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent output&lt;/strong&gt; - The same code produces the same visual style every single day, automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React-based&lt;/strong&gt; - If you already know React, you're immediately productive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High quality&lt;/strong&gt; - Remotion renders frame-by-frame at whatever resolution and framerate you specify. The output is a proper video file, not a screen recording.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What the video looks like:&lt;/strong&gt; A dark, minimal design with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The year progress percentage in large type&lt;/li&gt;
&lt;li&gt;A smooth animated progress bar filling to the correct point&lt;/li&gt;
&lt;li&gt;Today's date&lt;/li&gt;
&lt;li&gt;The historical fact rendered as clean text below&lt;/li&gt;
&lt;li&gt;A short looping background animation for visual polish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire &lt;code&gt;VideoTemplate.tsx&lt;/code&gt; component is just a React component with Remotion's &lt;code&gt;&amp;lt;AbsoluteFill&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;spring&amp;gt;&lt;/code&gt;, and timing hooks to drive the animation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VideoTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VideoProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&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;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCurrentFrame&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;fps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useVideoConfig&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;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;damping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AbsoluteFill&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;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0f0f1a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProgressBar&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PercentageText&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HistoricalFact&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fact&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AbsoluteFill&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the component is ready, rendering is a single CLI command (or programmatic call):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx remotion render VideoTemplate out/video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Uploading to YouTube - YouTube Data API v3
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Takes the rendered MP4 and automatically uploads it to the YouTube channel with a title, description, and the correct Shorts formatting - no human involvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How the YouTube Data API works:&lt;/strong&gt; Google provides an official API for interacting with YouTube programmatically. After a one-time OAuth2 authentication setup (where you grant your app permission to upload to your channel), you can upload videos, set metadata, manage playlists, and more - all from code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The upload flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;youtube&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;oauth2Client&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;part&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snippet&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="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Year Progress Update - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;formattedDate&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="na"&gt;description&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;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has passed.\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;historicalFact&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="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yearProgress&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="s1"&gt;motivation&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="s1"&gt;history&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;22&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;privacyStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&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;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out/video.mp4&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;&lt;strong&gt;Making it a YouTube Short:&lt;/strong&gt; YouTube Shorts are simply vertical videos (9:16 aspect ratio) under 60 seconds. Remotion renders the video at 1080×1920, and that's all YouTube needs to classify it as a Short - no special API flag required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The token refresh problem:&lt;/strong&gt; OAuth2 tokens expire. To keep the automation running forever without manually re-authenticating, the app stores refresh tokens in &lt;code&gt;tokens.json&lt;/code&gt; and automatically refreshes the access token before each upload. This is the kind of detail that separates a weekend project from something that actually keeps running for months.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: The Bash Script - Gluing It All Together
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; &lt;code&gt;run-daily.sh&lt;/code&gt; is the conductor. It runs once a day and orchestrates the entire pipeline end-to-end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why a bash script?&lt;/strong&gt; The core logic is in Node.js/TypeScript, but crontab (which triggers the daily run) works at the OS level. A bash script acts as the perfect bridge - it can set up the environment, invoke Node, handle errors, and maintain a log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the script does, step by step:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;PROJECT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/Users/mdshuvo/yearlyprogress"&lt;/span&gt;
&lt;span class="nv"&gt;STATE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/last-upload-date.txt"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/log.txt"&lt;/span&gt;

&lt;span class="nv"&gt;TODAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Read the last run date&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;LAST_RUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nv"&gt;LAST_RUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Skip if already ran today&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LAST_RUN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TODAY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# Run the main script, append output to log&lt;/span&gt;
/Users/mdshuvo/.nvm/versions/node/... &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Trim the log to keep only the last 2000 lines&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 2000 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;.tmp"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;.tmp"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; - The state file check ensures the script never accidentally uploads twice on the same day, even if crontab triggers it multiple times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; - All output is appended to &lt;code&gt;log.txt&lt;/code&gt; for easy debugging if something goes wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log trimming&lt;/strong&gt; - The log is kept to 2,000 lines so it doesn't grow forever and fill up the disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nvm path&lt;/strong&gt; - The full path to the Node binary is specified, because crontab runs in a minimal environment that doesn't load your shell's &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;. This is a classic gotcha that trips up many automation projects.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: The Crontab - Set It and Forget It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Tells my computer(Mac mini) to run &lt;code&gt;run-daily.sh&lt;/code&gt; automatically at a set time every day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is crontab?&lt;/strong&gt; It's a Unix job scheduler built into macOS and Linux. You define a schedule using a simple syntax, point it at a script, and the OS handles the rest - forever, or until you delete the entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The crontab entry:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 9 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /Users/mdshuvo/yearlyprogress/run-daily.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the script at &lt;strong&gt;9:00 AM every day&lt;/strong&gt;. The five fields represent: minute, hour, day of month, month, day of week. Five stars with the first two set means "every day at 9:00."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; Every morning at 9 AM, without any human action, computer runs the script, generates the video, and uploads it to YouTube. By the time most people are having their morning coffee, the day's video is live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes This Project Special
&lt;/h2&gt;

&lt;p&gt;Beyond the individual pieces, what's interesting here is how each layer is chosen for a specific reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript for logic&lt;/strong&gt; - Universal, fast to write, perfect for date manipulation and API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini for content&lt;/strong&gt; - Turns a static metric into a story worth watching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remotion for video&lt;/strong&gt; - Treats video like software, making it reproducible and automatable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube Data API for distribution&lt;/strong&gt; - The last mile, handled entirely in code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash for orchestration&lt;/strong&gt; - Lightweight, reliable, and plays perfectly with crontab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crontab for scheduling&lt;/strong&gt; - The simplest possible scheduler, built into the OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No overengineering. No cloud infrastructure to manage. No monthly bills for a complex pipeline. Just a small computer, some clever code, and a crontab entry.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;A few directions this project could grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple languages&lt;/strong&gt; - Generate the same video in Bengali, Spanish, French, etc. to reach broader audiences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Milestone alerts&lt;/strong&gt; - Special videos at 25%, 50%, 75%, and 99% with more elaborate animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thumbnail generation&lt;/strong&gt; - Use a canvas library or Puppeteer to auto-generate custom thumbnails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics tracking&lt;/strong&gt; - Pull view counts back from the YouTube API and log the growth over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-channel&lt;/strong&gt; - Clone the pipeline for a "Day of the Year" or "Days Until New Year" variant.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;All the core tools used are free or have generous free tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://remotion.dev" rel="noopener noreferrer"&gt;Remotion&lt;/a&gt; - Free for open-source and personal projects&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev" rel="noopener noreferrer"&gt;Google Gemini API&lt;/a&gt; - Free tier available&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/youtube/v3" rel="noopener noreferrer"&gt;YouTube Data API v3&lt;/a&gt; - Free with a Google account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to watch the year tick away one percentage point at a time, the channel is live at &lt;strong&gt;&lt;a href="https://www.youtube.com/@ProgressOfYear" rel="noopener noreferrer"&gt;youtube.com/@ProgressOfYear&lt;/a&gt;&lt;/strong&gt;. A new video drops every morning.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with JavaScript, Remotion, Gemini&lt;/em&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>programming</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Traditional Tutorials Don't Work for Learning Git</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 13 May 2026 04:57:00 +0000</pubDate>
      <link>https://dev.to/0shuvo0/why-traditional-tutorials-dont-work-for-learning-git-36j3</link>
      <guid>https://dev.to/0shuvo0/why-traditional-tutorials-dont-work-for-learning-git-36j3</guid>
      <description>&lt;p&gt;If you've tried learning Git through videos or articles and still feel confused, you're not alone. The problem isn't you - it's the way Git is usually taught.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Git Tutorials
&lt;/h2&gt;

&lt;p&gt;Most tutorials focus on explaining commands instead of helping you understand how Git actually works in real situations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They are passive (watching instead of doing)&lt;/li&gt;
&lt;li&gt;They focus on syntax, not workflows&lt;/li&gt;
&lt;li&gt;No real problem-solving scenarios&lt;/li&gt;
&lt;li&gt;No feedback when you make mistakes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why This Approach Fails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Git is Conceptual, Not Just Commands
&lt;/h3&gt;

&lt;p&gt;Git involves understanding history, branches, and changes - not just memorizing commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No Muscle Memory
&lt;/h3&gt;

&lt;p&gt;Watching tutorials doesn't build the habit of actually using Git commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No Real Context
&lt;/h3&gt;

&lt;p&gt;Real development involves conflicts, mistakes, and workflows - tutorials rarely simulate this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Traditional Learning vs Effective Learning
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Watching tutorials&lt;/td&gt;
&lt;td&gt;Temporary understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading docs&lt;/td&gt;
&lt;td&gt;Good reference, low retention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interactive practice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Real understanding&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  A Better Way to Learn Git
&lt;/h2&gt;

&lt;p&gt;The most effective way to learn Git is through &lt;strong&gt;hands-on, interactive practice&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn a concept&lt;/li&gt;
&lt;li&gt;Apply it immediately&lt;/li&gt;
&lt;li&gt;Make mistakes and fix them&lt;/li&gt;
&lt;li&gt;Repeat with real workflows&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Stop Watching, Start Practicing
&lt;/h2&gt;

&lt;p&gt;If you want to truly understand Git, you need to use it - not just watch someone else use it.&lt;br&gt;
&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Are tutorials useless for learning Git?&lt;/strong&gt;&lt;br&gt;
Not useless, but incomplete. They should be combined with hands-on practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does Git feel confusing?&lt;/strong&gt;&lt;br&gt;
Because it involves abstract concepts that require practice to fully understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best way to learn Git?&lt;/strong&gt;&lt;br&gt;
The best way is practicing real workflows instead of relying only on passive learning.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>coding</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learn Git Basics in 5 Minutes (Quick Guide)</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 13 May 2026 04:53:45 +0000</pubDate>
      <link>https://dev.to/0shuvo0/learn-git-basics-in-5-minutes-quick-guide-1e8b</link>
      <guid>https://dev.to/0shuvo0/learn-git-basics-in-5-minutes-quick-guide-1e8b</guid>
      <description>&lt;p&gt;Want to learn Git fast? This 5-minute guide covers the essential Git concepts and commands you need to get started. No fluff - just the basics that actually matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What is Git?
&lt;/h2&gt;

&lt;p&gt;Git is a version control system that helps you track changes in your code, collaborate with others, and manage different versions of your project.&lt;/p&gt;




&lt;h2&gt;
  
  
  3 Core Git Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Repository (Repo)
&lt;/h3&gt;

&lt;p&gt;A project folder tracked by Git.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commit
&lt;/h3&gt;

&lt;p&gt;A snapshot of your changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch
&lt;/h3&gt;

&lt;p&gt;A separate line of development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Essential Git Commands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initialize a new repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stage changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git commit -m "message"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Save changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check current state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git checkout -b feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and switch branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git merge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merge branches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Basic Git Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Make changes to your files&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git add .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git commit -m "message"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Repeat as you build your project&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Important: Reading Isn't Enough
&lt;/h2&gt;

&lt;p&gt;You can't learn Git just by reading. You need to actually use the commands in real scenarios.&lt;/p&gt;

&lt;p&gt;The fastest way to learn Git:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn a concept&lt;/li&gt;
&lt;li&gt;Practice it immediately&lt;/li&gt;
&lt;li&gt;Repeat with real workflows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practice Git in Minutes
&lt;/h2&gt;

&lt;p&gt;Instead of just reading, start practicing Git commands interactively and build real confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I learn Git in 5 minutes?&lt;/strong&gt;&lt;br&gt;
You can understand the basics in 5 minutes, but mastering Git requires practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I learn first in Git?&lt;/strong&gt;&lt;br&gt;
Start with repositories, commits, and basic commands like add and commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Git hard for beginners?&lt;/strong&gt;&lt;br&gt;
It can feel confusing at first, but becomes easy with hands-on practice.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>coding</category>
      <category>programming</category>
    </item>
    <item>
      <title>Git Commands Senior Developers Actually Use</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 13 May 2026 04:48:34 +0000</pubDate>
      <link>https://dev.to/0shuvo0/git-commands-senior-developers-actually-use-4dck</link>
      <guid>https://dev.to/0shuvo0/git-commands-senior-developers-actually-use-4dck</guid>
      <description>&lt;p&gt;Want to use Git like a senior developer? It’s not about knowing hundreds of commands - it’s about mastering a small set of powerful ones and using them confidently in real workflows.&lt;br&gt;
&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What Makes These Commands “Senior-Level”?
&lt;/h4&gt;

&lt;p&gt;Senior developers don’t just run commands - they understand how Git history works, how to fix mistakes, and how to manage clean workflows. These commands reflect that mindset.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;git status&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Always Know What’s Happening&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check your current state before doing anything. Senior devs constantly use this.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;git log --oneline --graph --decorate&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Visualize History&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See your commit history in a clean, readable format.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;git checkout -b feature-name&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Work in Branches&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create isolated environments for features and fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;git merge&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Combine Work Safely&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Merge branches when features are complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;git rebase&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Clean Up History&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rewrite commit history to keep it linear and clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;code&gt;git stash&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Save Work Temporarily&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quickly store changes without committing.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. &lt;code&gt;git reset&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Undo Changes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Move back to a previous state when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. &lt;code&gt;git restore&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Fix Mistakes Safely&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Restore files without affecting commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. &lt;code&gt;git pull --rebase&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid Messy Merges&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep history clean when pulling updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. &lt;code&gt;git diff&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;See What Changed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understand exactly what modifications were made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice These Commands Like a Pro
&lt;/h2&gt;

&lt;p&gt;Reading commands isn’t enough - you need to actually use them in real workflows.&lt;br&gt;
&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>coding</category>
      <category>programming</category>
    </item>
    <item>
      <title>10 Git Mistakes Beginners Make (And How to Avoid Them)</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 13 May 2026 04:40:12 +0000</pubDate>
      <link>https://dev.to/0shuvo0/10-git-mistakes-beginners-make-and-how-to-avoid-them-1e44</link>
      <guid>https://dev.to/0shuvo0/10-git-mistakes-beginners-make-and-how-to-avoid-them-1e44</guid>
      <description>&lt;h2&gt;
  
  
  Why Beginners Struggle with Git
&lt;/h2&gt;

&lt;p&gt;Git isn’t just about commands - it’s about understanding how changes, history, and branches work together. Without hands-on practice, these concepts feel confusing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  10 Common Git Mistakes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Trying to Memorize Commands
&lt;/h4&gt;

&lt;p&gt;Many beginners try to memorize Git commands instead of understanding what they actually do.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Focus on workflows, not commands. Practice using Git in real scenarios.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Not Practicing Enough
&lt;/h4&gt;

&lt;p&gt;Watching tutorials without actually using Git leads to shallow understanding.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Use interactive practice to apply what you learn immediately. &lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&gt;GitMission&lt;/a&gt; is great for that.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Ignoring Branching
&lt;/h4&gt;

&lt;p&gt;Branching is a core concept, but many beginners avoid it.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Practice creating, switching, and merging branches regularly.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Fear of Breaking Things
&lt;/h4&gt;

&lt;p&gt;Beginners often hesitate because they’re afraid of making mistakes.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Git is designed to recover changes - experiment freely.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Not Understanding Commits
&lt;/h4&gt;

&lt;p&gt;Treating commits like random saves instead of meaningful checkpoints.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Make small, descriptive commits that reflect logical changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Skipping Git Fundamentals
&lt;/h4&gt;

&lt;p&gt;Jumping into advanced topics without understanding basics.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Master repositories, commits, and staging first.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Confusion Between Git and GitHub
&lt;/h4&gt;

&lt;p&gt;Many beginners mix up Git (tool) and GitHub (platform).&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Understand Git as the system, GitHub as a hosting service.&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Poor Commit Messages
&lt;/h4&gt;

&lt;p&gt;Using vague messages like 'update' or 'fix'.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Write clear messages describing what changed and why.&lt;/p&gt;

&lt;h4&gt;
  
  
  9. Not Learning How to Undo Changes
&lt;/h4&gt;

&lt;p&gt;Beginners panic when something goes wrong.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Learn commands for undoing commits, restoring files, and fixing mistakes.&lt;/p&gt;

&lt;h4&gt;
  
  
  10. Avoiding Real Workflows
&lt;/h4&gt;

&lt;p&gt;Only learning isolated commands instead of real use cases.&lt;br&gt;
&lt;strong&gt;How to fix&lt;/strong&gt;: Practice full workflows like feature development and merging.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Secret to Learning Git
&lt;/h3&gt;

&lt;p&gt;The biggest mistake is relying only on passive learning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn a concept&lt;/li&gt;
&lt;li&gt;Practice it immediately&lt;/li&gt;
&lt;li&gt;Repeat with real scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Learn Git Without These Mistakes
&lt;/h3&gt;

&lt;p&gt;Instead of struggling through trial and error, you can learn Git the right way through structured, hands-on practice.&lt;br&gt;
&lt;a href="https://www.gitmission.com/" rel="noopener noreferrer"&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%2Fln6va7gb9hatyiij747b.png" alt="GitMission CTA" width="219" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>coding</category>
      <category>programming</category>
    </item>
    <item>
      <title>Functional tabs with only CSS (No JavaScript)</title>
      <dc:creator>Shuvo</dc:creator>
      <pubDate>Wed, 04 Jun 2025 01:34:42 +0000</pubDate>
      <link>https://dev.to/0shuvo0/functional-tabs-with-only-css-no-javascript-29ok</link>
      <guid>https://dev.to/0shuvo0/functional-tabs-with-only-css-no-javascript-29ok</guid>
      <description>&lt;p&gt;This article demonstrates how to create functional tabs using only CSS, without any JavaScript. The tabs will allow users to switch between different content sections seamlessly, showcasing the power of CSS for interactive elements. &lt;br&gt;
Original article: &lt;a href="https://www.wtfcodes.com/wtf-articles/read/Functional-tabs-with-only-CSSS-No-JavaScript" rel="noopener noreferrer"&gt;wtfcodes.com&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Basic setup:
&lt;/h3&gt;

&lt;p&gt;To get started, we will create a typical HTML structure for our tabs and apply some basic styles to make them visually appealing.&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-nav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab A&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab B&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab C&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab A content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab B content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab C content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tabs-nav&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;user-select&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="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tab-item&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#bcbcbc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tab-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;grey&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;&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%2Fdwt6ft7j92j7o787poou.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%2Fdwt6ft7j92j7o787poou.png" alt="Basic setup result" width="507" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! Now we need somekind of interactivity.&lt;br&gt;
Lets think what cab we do so the active tab has something different than other tabs.&lt;br&gt;
Well we can use &lt;code&gt;&amp;lt;input type="radio" /&amp;gt;&lt;/code&gt; to indicate the active tab. Since radio inputs allow only one to be selected at a time, we can use them to control which tab is active.&lt;br&gt;
And to make sure we can toggle the radio inputs by clicking on the tab, we can use the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element instead of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; for the tab items.&lt;br&gt;
So the updated HTML structure will look like this:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-nav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab A&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;checked&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab B&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab C&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab A content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab B content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab C content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fbgrgeq7z554qxbne7xd1.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%2Fbgrgeq7z554qxbne7xd1.png" alt="Updated html structure" width="518" height="232"&gt;&lt;/a&gt;&lt;br&gt;
Nice progress!&lt;br&gt;
Since we don't want the radio inputs to be visible, we can hide them using &lt;code&gt;.tab-item input{display: none; }&lt;/code&gt; in the CSS.&lt;/p&gt;
&lt;h3&gt;
  
  
  Highlight the active tab:
&lt;/h3&gt;

&lt;p&gt;We can use the &lt;code&gt;tab-item:has(input:checked)&lt;/code&gt; selector in CSS to check if a tab has a checked input and apply styles to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tab-item&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tab-item&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6b54f0&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;&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%2Fdoh4p119zmbozd6auhql.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%2Fdoh4p119zmbozd6auhql.png" alt="Active tab visible" width="506" height="219"&gt;&lt;/a&gt;&lt;br&gt;
Amazing! Now we can see which tab is active.&lt;br&gt;
Using the same logic, we can also show/hide the content of the active tab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tab-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-a-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-a-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-b-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-b-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-c-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-c-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&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;&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%2Fyrom935yn23zifdmsbbv.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%2Fyrom935yn23zifdmsbbv.gif" alt="Final result" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final code:
&lt;/h3&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs-nav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab A&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;checked&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab B&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-toggle"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Tab C&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tabs-toggle"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-toggle"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-a-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab A content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-b-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab B content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab-content"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab-c-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    I am Tab C content
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tabs-nav&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;user-select&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="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tab-item&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#bcbcbc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tab-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;grey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tab-item&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tab-item&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#6b54f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tab-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-a-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-a-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-b-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-b-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tabs-wrapper&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;#tab-c-toggle&lt;/span&gt;&lt;span class="nd"&gt;:checked&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;#tab-c-content&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Thank you for reading!
&lt;/h3&gt;

&lt;p&gt;If you enjoyed this article, please consider sharing it with others.&lt;br&gt;
You can support me on &lt;a href="https://buymeacoffee.com/0shuvo0" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;.&lt;br&gt;
If you have any feedback or suggestions, feel free to reach out to me on &lt;a href="https://twitter.com/0shuvo0" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;br&gt;
Don't forget to checkout &lt;a href="https://www.wtfcodes.com/" rel="noopener noreferrer"&gt;wtfcodes.com&lt;/a&gt; &lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
