<?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: fadow</title>
    <description>The latest articles on DEV Community by fadow (@fadow).</description>
    <link>https://dev.to/fadow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3932470%2F499efdc4-113a-4885-99c4-db48b6c52a4c.jpeg</url>
      <title>DEV Community: fadow</title>
      <link>https://dev.to/fadow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fadow"/>
    <language>en</language>
    <item>
      <title>I moved from Windows to Linux — here's the full story (pain included)</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Sun, 31 May 2026 16:26:56 +0000</pubDate>
      <link>https://dev.to/fadow/i-moved-from-windows-to-linux-heres-the-full-story-pain-included-1641</link>
      <guid>https://dev.to/fadow/i-moved-from-windows-to-linux-heres-the-full-story-pain-included-1641</guid>
      <description>&lt;p&gt;I'm a person that use Windows since I'm still a kid&lt;/p&gt;

&lt;p&gt;Hi, I'm a SWE that been using Windows before I even understand what "girlfriend" even meaning. From good old days when every home's pc have McAfee on their pc. But then I decided to move to Linux.&lt;/p&gt;

&lt;p&gt;The reason I move to Linux is not because AI slop or anything, it's just a random day and I have a random thought: "Why Windows?". And then the journey begin.&lt;/p&gt;

&lt;p&gt;At first, like all other newbies, I head to community of Linux to make a question that we all know&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey, I come from Windows, what distro do you guys suggest me to use.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I was kinda surprise. Not like the myth of Linux user always a not-friendly community, they actually kinda friendly. They suggest me to use distro that many people using such&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu&lt;/li&gt;
&lt;li&gt;Mint&lt;/li&gt;
&lt;li&gt;Debian&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But then a guy slide into my DMs and say&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you should try fedora, they just release KDE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And without even understand what is 'KDE'. I hop on google and search "Fedora KDE". Go to the first link and download the .iso.&lt;/p&gt;

&lt;p&gt;After burning the .iso file into the USB and boot into the USB. Oh boy the KDE UI make me drop my jaws. Not any alike the myth where "Linux user need to type 30 commands just to install browser". Fedora KDE offer me a installer with just a &lt;strong&gt;single&lt;/strong&gt; click.&lt;/p&gt;

&lt;p&gt;And after install the fedora KDE and reboot. I got my first ever step into the Linux world.&lt;/p&gt;

&lt;p&gt;My first ever thought is "Woah this look cool, and smooth as hell"&lt;br&gt;
It can do anything Windows do but smooth like butter instead slow like a snail.&lt;/p&gt;

&lt;p&gt;But that just a first step, now is the journey.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Nvidia problem
&lt;/h3&gt;

&lt;p&gt;Welp nvidia decided to not open source their driver anymore. So things just tear apart for Linux user. I cannot get my screen to use my card with 100% potential. I cannot get discord to screen share. Screen sometimes stuttering. Super high latency when in X11. All just pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fedora is not the promised land
&lt;/h3&gt;

&lt;p&gt;After a few weeks of daily driving Fedora, the cracks started to show.&lt;/p&gt;

&lt;p&gt;It works fine for basic stuff — browsing, coding, watching YouTube. Smooth, stable, no complaints. But I'm not a "basic stuff" person. I tinker. I customize. I want my desktop to feel like &lt;em&gt;mine&lt;/em&gt;, not like someone else's default.&lt;/p&gt;

&lt;p&gt;And Fedora pushes back when you tinker. DNF (their package manager) is slow. Finding niche packages means adding 3 different COPR repos. KDE is pretty but heavy — my laptop fan spins up just opening the settings panel. Every time I fixed one thing, two other things broke.&lt;/p&gt;

&lt;p&gt;So I spent days scrolling Reddit, lurking in Discord servers, watching YouTube videos at 2AM. And one name kept coming up.&lt;/p&gt;

&lt;p&gt;Arch Linux.&lt;/p&gt;

&lt;p&gt;Everyone described it the same way: "hard to install, but after that, it's exactly what you want it to be." No bloat. No hand-holding. Just you and the system.&lt;/p&gt;

&lt;p&gt;I was scared. I was also curious.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Arch install — first attempt, first fail
&lt;/h3&gt;

&lt;p&gt;I downloaded the Arch ISO. Burned it. Booted.&lt;/p&gt;

&lt;p&gt;And then I stared at a black screen with a blinking cursor.&lt;/p&gt;

&lt;p&gt;No installer. No "click here to continue." Just a terminal. And me.&lt;/p&gt;

&lt;p&gt;I followed the wiki. Step by step. Partition the disk with &lt;code&gt;fdisk&lt;/code&gt;. Format with &lt;code&gt;mkfs.ext4&lt;/code&gt;. Mount everything. &lt;code&gt;pacstrap&lt;/code&gt; the base system. Generate fstab. Chroot in.&lt;/p&gt;

&lt;p&gt;Somewhere around step 14, I typed the wrong thing and the system wouldn't boot. I didn't even know what I did wrong. Just a wall of text and "kernel panic."&lt;/p&gt;

&lt;p&gt;Second attempt. Third. Fourth. I lost count.&lt;/p&gt;

&lt;p&gt;On the fifth try — around 4AM — it booted. A black screen with white text: &lt;code&gt;archlinux login: _&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I literally said "oh my god" out loud to an empty room.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The real pain: drivers, internet, audio, video
&lt;/h3&gt;

&lt;p&gt;Booting is one thing. Making it &lt;em&gt;usable&lt;/em&gt; is another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nvidia&lt;/strong&gt; — nouveau (the open source driver) gave me 720p resolution and screen tearing. Installing the proprietary &lt;code&gt;nvidia&lt;/code&gt; driver meant figuring out kernel modules, DKMS, and praying it doesn't break on the next &lt;code&gt;pacman -Syu&lt;/code&gt;. It broke twice. I learned to read the Arch news before every update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internet&lt;/strong&gt; — &lt;code&gt;iwctl&lt;/code&gt; to connect to WiFi from the terminal. Not hard once you know the commands. But the first time, staring at &lt;code&gt;station wlan0 scan&lt;/code&gt; and seeing nothing? I thought my WiFi card wasn't supported. Turns out I just forgot to bring the interface up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio&lt;/strong&gt; — PipeWire vs PulseAudio. I didn't even know this was a debate. My speakers worked, my headphone jack didn't. Two days of reading wiki pages about &lt;code&gt;wireplumber&lt;/code&gt; config before I got both working simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt; — Discord screen sharing on Wayland. This is still not fully solved today, but on X11 it was worse. My whole screen would freeze for 3 seconds when I clicked "share." My friends heard my voice but saw a gray box. I eventually switched to OBS virtual camera as a workaround.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Why I stayed
&lt;/h3&gt;

&lt;p&gt;Here's the thing nobody tells you about Arch.&lt;/p&gt;

&lt;p&gt;After you go through all that pain — the failed installs, the broken drivers, the 4AM wiki reading sessions — you come out the other side and realize you understand your computer for the first time.&lt;/p&gt;

&lt;p&gt;On Windows, when something breaks, you restart. Maybe you Google the error code, find a forum post from 2019, run some &lt;code&gt;.reg&lt;/code&gt; file, and pray. On Arch, when something breaks, you know &lt;em&gt;why&lt;/em&gt;. You broke it. You fix it.&lt;/p&gt;

&lt;p&gt;And the system you end up with? It's exactly what you want. Nothing less, nothing more.&lt;/p&gt;

&lt;p&gt;My current setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arch (btw)&lt;/li&gt;
&lt;li&gt;RiverWM — a Wayland tiling window manager that starts in 0.3 seconds&lt;/li&gt;
&lt;li&gt;Neovim 0.12 with native LSP&lt;/li&gt;
&lt;li&gt;fish shell with autosuggestions&lt;/li&gt;
&lt;li&gt;Hermes Agent to handle boilerplate work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No desktop environment. No bloat. My laptop from 2021 runs smoother than my friend's 2024 Windows machine with double the RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Was it worth it?
&lt;/h3&gt;

&lt;p&gt;If you just want to browse the web and edit documents — stick with Ubuntu or Mint. Seriously. Don't suffer for no reason.&lt;/p&gt;

&lt;p&gt;But if you're a developer who wants to understand their machine, who enjoys the process of building something from nothing, who looks at Windows and thinks "why?" — Arch is waiting for you.&lt;/p&gt;

&lt;p&gt;Just clear your weekend. And maybe the next weekend too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of my tools and setup series. I also wrote about &lt;a href="https://dev.to/fadow/my-pc-setup-as-a-linux-user-1dhn"&gt;my full dev setup here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're a NestJS dev tired of setup time, I built a starter kit with auth + i18n + MongoDB + Redis pre-configured: &lt;a href="https://fadow.gumroad.com" rel="noopener noreferrer"&gt;fadow.gumroad.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>archlinux</category>
      <category>windows</category>
      <category>developers</category>
    </item>
    <item>
      <title>Most project templates are either too simple or absurdly over-engineered. Here's what actually works for building MVPs fast.</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Fri, 29 May 2026 14:25:00 +0000</pubDate>
      <link>https://dev.to/fadow/most-project-templates-are-either-too-simple-or-absurdly-over-engineered-heres-what-actually-1pgo</link>
      <guid>https://dev.to/fadow/most-project-templates-are-either-too-simple-or-absurdly-over-engineered-heres-what-actually-1pgo</guid>
      <description>&lt;h2&gt;
  
  
  Most project templates are either too simple or absurdly over-engineered. Here's what actually works for building MVPs fast.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I'm a developer who has worked with many clients from multiple domains and countries. So I used a lot of templates to boost up my speed. And from those usage, today I'm sharing the key pain points I've discovered while using templates around the internet.
&lt;/h3&gt;




&lt;h3&gt;
  
  
  1. Over-engineering
&lt;/h3&gt;

&lt;p&gt;It's wonderful for a template that already cover the future scaling if the client needed. But sometimes, it adds unnecessary complexity to the template itself. Example, if I want a micro-saas template that ready to give me to MVP ASAP. Setting up a project with k8s, microservices, RabitMQ is not only time consuming, energy drawing but also being overkill. Even though that is a good stack for when going big, but for a micro-saas with target of getting MVP ASAP to have a research about the market? It's a big down side. With me, a good template is cover scaling ability, but recognize the threshold—knowing when &lt;strong&gt;good enough&lt;/strong&gt; is actually good enough to stop scaling more. A monolith project with base fundamental covering that good enough so project not getting messy to quickly, but not a gigantic walking machine that handle 100k concurrent requests is a &lt;em&gt;sweet spot&lt;/em&gt; for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No test
&lt;/h3&gt;

&lt;p&gt;Untested templates are inexcusable. What's worse for a client when they receive the thing they bought just to find it in an unusable condition? This is the absolute bare minimum of an item before reaching the client, no one is willing to buy a piece of junk after all.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. English only
&lt;/h3&gt;

&lt;p&gt;It's true that English is used widely all over the world, but there are still other languages too. Making a template for a globalized language is a good thing, but with me it's kinda not enough. Just by simply adding support to one other language at least, you have easily unlocked much more the size of the client pool. From browsing marketplace sites like Gumroads, Colorlib, Envato,... I've noticed multilingual templates consistently have higher sales than templates that only support one language, and base on that we can tell that we - human really want somethings that could help us getting things done faster but requiring less effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Too many friction
&lt;/h3&gt;

&lt;p&gt;We all want things that go swiftly, no one wants to have something that go slow and wasting time to wait for it to run. And the speed I'm talking about right here is the time taken of client to use the product they bought. When a product reaches a client, they may still have to wait for hours for it to build? That's a major red flag. Instead of that, a product that starts with one command, and also boots up in seconds is a perfect thing that everyone wanted.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Bloated
&lt;/h3&gt;

&lt;p&gt;A template with such simple built in function but taking too much space is a dead product. We have evolved for years, turning a gigantic computer that a size of a building now just fit on a desk, so why we have a template that bloated like a couple of GB just for very few functions right? Compact it, de-bloat it is a must to do for better user experiences&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With me, sometimes the best template is the simplest one. No need to go crazy, no need to be huge. Just covering the base fundamental things that everyone wants to avoid is the best.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;I've created templates that address all five of these problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit &amp;amp; integration tests&lt;/li&gt;
&lt;li&gt;i18n localization support&lt;/li&gt;
&lt;li&gt;Optimized for speed &amp;amp; small footprint&lt;/li&gt;
&lt;li&gt;One-command Docker setup&lt;/li&gt;
&lt;li&gt;Full documentation &amp;amp; guides&lt;/li&gt;
&lt;li&gt;Separate FE (Next.js) and BE (NestJS) templates&lt;/li&gt;
&lt;li&gt;Modern, startup-friendly tech stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BE template: &lt;strong&gt;$8&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;FE template: &lt;strong&gt;$13&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Both FE + BE: &lt;strong&gt;$20&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fadow.gumroad.com/" rel="noopener noreferrer"&gt;Get your template on Gumroad&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>code</category>
      <category>developers</category>
      <category>marketing</category>
      <category>buy</category>
    </item>
    <item>
      <title>Built a Vietnam phone validator API. Send any phone number format</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Wed, 27 May 2026 23:29:38 +0000</pubDate>
      <link>https://dev.to/fadow/built-a-vietnam-phone-validator-api-send-any-phone-number-format-17m7</link>
      <guid>https://dev.to/fadow/built-a-vietnam-phone-validator-api-send-any-phone-number-format-17m7</guid>
      <description>&lt;p&gt;I build a very fast minimal API to validate VietNam phone number that you can use instantly&lt;/p&gt;

&lt;p&gt;Input: 0912345678, +84912345678, 84-91-2345678 — all work&lt;br&gt;
Carriers: Viettel, Vinaphone, Mobifone, Vietnamobile, Gmobile&lt;/p&gt;

&lt;p&gt;Free tier available. Live on RapidAPI:&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://rapidapi.com/clocktoktok/api/vietnam-phone-validator1" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;rapidapi.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>api</category>
      <category>startup</category>
      <category>tooling</category>
      <category>rapidapi</category>
    </item>
    <item>
      <title>My PC setup as a Linux user</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Wed, 27 May 2026 09:53:36 +0000</pubDate>
      <link>https://dev.to/fadow/my-pc-setup-as-a-linux-user-1dhn</link>
      <guid>https://dev.to/fadow/my-pc-setup-as-a-linux-user-1dhn</guid>
      <description>&lt;p&gt;Arch Linux + RiverWM + Neovim — my daily driver:&lt;/p&gt;

&lt;p&gt;OS: Arch (btw)&lt;br&gt;
WM: River (Wayland, no X11)&lt;br&gt;
Editor: Neovim 0.12 (native LSP, no lspconfig.nvim)&lt;br&gt;
Terminal: Alacritty + fish shell&lt;br&gt;
Agent: Hermes (offloads 80% of my boilerplate work)&lt;/p&gt;

&lt;p&gt;Why this stack:&lt;br&gt;
• River starts in 0.3s. No desktop environment bloat&lt;br&gt;
• Neovim with blink.cmp + treesitter — instant completions&lt;br&gt;
• fish shell — autosuggestions save hours&lt;br&gt;
• Hermes builds starter kits while I sleep&lt;/p&gt;

&lt;p&gt;Results:&lt;br&gt;
• NestJS Vietnam Starter shipped in 17 minutes (AI-built)&lt;br&gt;
• Next.js starter in 30 minutes&lt;br&gt;
• Advanced NestJS pack in 15 minutes&lt;/p&gt;

&lt;p&gt;Your tools multiply your output.&lt;/p&gt;

&lt;h1&gt;
  
  
  archlinux #neovim #devsetup
&lt;/h1&gt;

</description>
      <category>linux</category>
      <category>archlinux</category>
      <category>developers</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Ship a Production NestJS Backend in Under 30 Minutes</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Thu, 21 May 2026 13:04:40 +0000</pubDate>
      <link>https://dev.to/fadow/how-i-ship-a-production-nestjs-backend-in-under-30-minutes-4277</link>
      <guid>https://dev.to/fadow/how-i-ship-a-production-nestjs-backend-in-under-30-minutes-4277</guid>
      <description>&lt;p&gt;Every new project used to take me 2-3 days of setup before I wrote a single line of business logic.&lt;/p&gt;

&lt;p&gt;Auth. i18n. MongoDB. Redis. Docker. Swagger. Tests. Every. Single. Time.&lt;/p&gt;

&lt;p&gt;Here's the stack I landed on after 6 projects:&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. One command to run everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;mongo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo:7&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;27017:27017"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;&lt;span class="pi"&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 docker"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 services. Zero config drift between my machine and production.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Auth that doesn't leak
&lt;/h3&gt;

&lt;p&gt;JWT access tokens (15min) + refresh tokens (7 days, hashed in DB). Logout invalidates both — refresh token deleted from DB, access token blacklisted in Redis. No dangling sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. i18n from day one
&lt;/h3&gt;

&lt;p&gt;Vietnamese users deserve proper tone marks. Two JSON files, one auto-detect from &lt;code&gt;Accept-Language&lt;/code&gt;, one translation function. Not a 500KB library. Just ~50 lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email_invalid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email không hợp lệ"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email_invalid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid email address"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Validation that actually validates
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })&lt;/code&gt; — one line. No more unexpected fields in your DTOs. If your API crashes in dev, it crashes the same way in prod.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Tests that prove it works
&lt;/h3&gt;

&lt;p&gt;36 tests. Auth flow. User CRUD. i18n responses. RBAC guards. Not "it compiled, ship it." Actually verified.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math
&lt;/h2&gt;

&lt;p&gt;At 2h/day of freelance time, setup costs ~$25/hour:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth + RBAC: 2 hours = $50&lt;/li&gt;
&lt;li&gt;i18n: 1 hour = $25&lt;/li&gt;
&lt;li&gt;Docker setup: 30 min = $12.50&lt;/li&gt;
&lt;li&gt;Tests: 2 hours = $50&lt;/li&gt;
&lt;li&gt;Swagger docs: 30 min = $12.50&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;That's $150 in setup costs per project.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shortcut
&lt;/h2&gt;

&lt;p&gt;I packaged this exact setup into a starter kit. Clone, &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;docker compose up -d&lt;/code&gt;. 30 minutes instead of 2 days.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://fadow.gumroad.com/l/nestjs-starter-kit-vietnam" rel="noopener noreferrer"&gt;https://fadow.gumroad.com/l/nestjs-starter-kit-vietnam&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bilingual Vietnamese + English. 36 passing tests. Docker-ready. $10.&lt;/p&gt;




&lt;p&gt;What's your NestJS setup ritual? Drop it below.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>mongodb</category>
      <category>webdev</category>
    </item>
    <item>
      <title>7 Developer Tools I Actually Pay For (And 3 That Are Free)</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Wed, 20 May 2026 02:11:17 +0000</pubDate>
      <link>https://dev.to/fadow/7-developer-tools-i-actually-pay-for-and-3-that-are-free-4f7k</link>
      <guid>https://dev.to/fadow/7-developer-tools-i-actually-pay-for-and-3-that-are-free-4f7k</guid>
      <description>&lt;p&gt;I'm cheap. I use Arch Linux, Neovim, and open-source everything. But some tools are worth paying for. Here's my list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paid (worth every đồng)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. DeepSeek API (~$10/month)
&lt;/h3&gt;

&lt;p&gt;My AI coding agent runs on DeepSeek v4 Pro. Cheaper than GitHub Copilot ($10 vs $19/month). Better for Vietnamese context. I use it through Hermes Agent to build boilerplates, write tests, and debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://platform.deepseek.com" rel="noopener noreferrer"&gt;DeepSeek Platform&lt;/a&gt;&lt;/strong&gt; — $0.14/M input tokens&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Railway (~$5/month)
&lt;/h3&gt;

&lt;p&gt;Full-stack hosting that just works. Docker Compose one-click deploy. Built-in MongoDB + Redis. No YAML hell. No Kubernetes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://railway.app" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;&lt;/strong&gt; — free $5 credit&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Gumroad (free + 10% fee)
&lt;/h3&gt;

&lt;p&gt;I sell my NestJS/Next.js starter kits here. Free to list. They take 10% only on sales. Built-in email marketing to past customers. PayPal + card payments handled automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://gumroad.com" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt;&lt;/strong&gt; — free to start&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Namecheap (~$12/year)
&lt;/h3&gt;

&lt;p&gt;Domains. .dev, .app, .com — pick whatever. I use .gumroad.com for now but will switch when there's more revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://namecheap.com" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;&lt;/strong&gt; — domains from $1/year&lt;/p&gt;

&lt;h2&gt;
  
  
  Free (surprisingly good)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5. Hermes Agent (free, open-source)
&lt;/h3&gt;

&lt;p&gt;AI agent that runs in my terminal. Builds projects, manages Obsidian, schedules cron jobs. It wrote 80% of my starter kits while I was AFK. Works with DeepSeek, OpenAI, Anthropic, local models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Obsidian (free)
&lt;/h3&gt;

&lt;p&gt;My second brain. PARA method. Everything I know about NestJS/Next.js/Go lives here. Free. Offline. No vendor lock-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Neovim (free)
&lt;/h3&gt;

&lt;p&gt;0.12 "Stale Earth" with native LSP. No VS Code. No JetBrains. Instant startup. Full control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://neovim.io" rel="noopener noreferrer"&gt;Neovim&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Not worth it (tried, cancelled)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Why I cancelled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;$19/month for worse code than DeepSeek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Slow, online-only, bloated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel Pro&lt;/td&gt;
&lt;td&gt;Hobby tier is enough for my scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe Creative Cloud&lt;/td&gt;
&lt;td&gt;GIMP + Inkscape do the job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grammarly Premium&lt;/td&gt;
&lt;td&gt;Free tier catches 90%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Total monthly cost
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DeepSeek API:  ~$10
Railway:        ~$5
Domain:         ~$1/month (annual)
Gumroad:        free until sale
─────────────────────
Total:         ~$16/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;$16/month to run a full dev setup with AI coding, hosting, and a storefront. Not bad.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building a NestJS project? &lt;a href="https://waldyctt.gumroad.com" rel="noopener noreferrer"&gt;Save 7 hours of setup&lt;/a&gt; with my pre-configured starter kit.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Next.js Dark Mode with Tailwind CSS v4 in 5 Minutes</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Mon, 18 May 2026 03:23:50 +0000</pubDate>
      <link>https://dev.to/fadow/nextjs-dark-mode-with-tailwind-css-v4-in-5-minutes-5e3n</link>
      <guid>https://dev.to/fadow/nextjs-dark-mode-with-tailwind-css-v4-in-5-minutes-5e3n</guid>
      <description>&lt;p&gt;Dark mode isn't a "nice to have" anymore — users expect it. Here's the fastest way to add it to a Next.js 16 app with Tailwind v4 and &lt;code&gt;next-themes&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;next-themes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Wrap your app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-themes&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&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;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;suppressHydrationWarning&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="nt"&gt;body&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;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="na"&gt;defaultTheme&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"system"&lt;/span&gt; &lt;span class="na"&gt;enableSystem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&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="nt"&gt;body&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="nt"&gt;html&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;Key flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;attribute="class"&lt;/code&gt; — toggles &lt;code&gt;.dark&lt;/code&gt; class on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, Tailwind picks it up&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;defaultTheme="system"&lt;/code&gt; — respects OS preference on first visit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suppressHydrationWarning&lt;/code&gt; — prevents React hydration mismatch flash&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Configure Tailwind v4
&lt;/h2&gt;

&lt;p&gt;Tailwind v4 uses CSS-based config. Add this to &lt;code&gt;globals.css&lt;/code&gt;:&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="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@custom-variant&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No &lt;code&gt;tailwind.config.js&lt;/code&gt;. No &lt;code&gt;darkMode: 'class'&lt;/code&gt;. Tailwind v4 just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build the toggle
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/ThemeToggle.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-themes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ThemeToggle&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;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&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;mounted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMounted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setMounted&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="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="nx"&gt;mounted&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-9 h-9"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
      &lt;span class="na"&gt;onClick&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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;light&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;dark&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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;☀️&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;🌙&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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;&lt;strong&gt;Why the &lt;code&gt;mounted&lt;/code&gt; check?&lt;/strong&gt; &lt;code&gt;next-themes&lt;/code&gt; doesn't know the theme until after hydration (it reads from localStorage). Rendering before mount causes a flash of wrong theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use in components
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"&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="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&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="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Tailwind utility works with &lt;code&gt;dark:&lt;/code&gt; prefix. No special components needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: System theme + manual override
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;defaultTheme="system"&lt;/code&gt; means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First visit → matches OS setting&lt;/li&gt;
&lt;li&gt;User clicks toggle → saves preference to localStorage&lt;/li&gt;
&lt;li&gt;Next visit → uses saved preference, ignores OS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what users expect. Don't force dark mode. Let them choose.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full setup (dark mode + i18n + auth + dashboard): &lt;a href="https://fadow.gumroad.com" rel="noopener noreferrer"&gt;Next.js Vietnam Starter Kit&lt;/a&gt; — $15, ready in 5 minutes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How to Set Up i18n in NestJS — with Vietnamese Translations</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Fri, 15 May 2026 09:15:07 +0000</pubDate>
      <link>https://dev.to/fadow/how-to-set-up-i18n-in-nestjs-with-vietnamese-translations-58ff</link>
      <guid>https://dev.to/fadow/how-to-set-up-i18n-in-nestjs-with-vietnamese-translations-58ff</guid>
      <description>&lt;p&gt;Most i18n tutorials stop at English and Spanish. If you're building for Vietnamese users, you need proper tone marks, natural phrasing, and a setup that doesn't fight you.&lt;/p&gt;

&lt;p&gt;Here's how I built a NestJS i18n system that auto-detects &lt;code&gt;Accept-Language&lt;/code&gt; and returns error messages in Vietnamese or English.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install nestjs-i18n
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nestjs-i18n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Create translation files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/i18n/
├── vi/
│   ├── validation.json
│   └── response.json
└── en/
    ├── validation.json
    └── response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;vi/validation.json:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"EMAIL_INVALID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email không hợp lệ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PASSWORD_MIN_LENGTH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mật khẩu phải có ít nhất 6 ký tự"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"USER_NOT_FOUND"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Không tìm thấy người dùng"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"EMAIL_EXISTS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email đã tồn tại"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FORBIDDEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bạn không có quyền truy cập"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;vi/response.json:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LOGIN_SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Đăng nhập thành công"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"REGISTER_SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Đăng ký thành công"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LOGOUT_SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Đăng xuất thành công"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Register I18nModule
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.module.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HeaderResolver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestjs-i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;I18nModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;fallbackLanguage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loaderOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/i18n/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;watch&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="na"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;:&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;HeaderResolver&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept-language&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="c1"&gt;// ... other imports&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Use in exception filters
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// http-exception.filter.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;I18nContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nestjs-i18n&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="nd"&gt;Catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpExceptionFilter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ExceptionFilter&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;exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ArgumentsHost&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToHttp&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;i18n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;I18nContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getResponse&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;exception&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="c1"&gt;// Translate if the message is a dot-notation key like "validation.EMAIL_INVALID"&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i18n&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;translate&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="o"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&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="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Auto-detect language from headers
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Accept-Language&lt;/code&gt; header resolver does this automatically. The frontend sends &lt;code&gt;Accept-Language: vi&lt;/code&gt; or &lt;code&gt;en&lt;/code&gt; — NestJS picks it up. Zero extra code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vietnamese-specific gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tone marks&lt;/strong&gt;: &lt;code&gt;nest-i18n&lt;/code&gt; stores JSON as UTF-8. Vietnamese characters (ắ, ễ, ở) work natively. No special handling needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polite phrasing&lt;/strong&gt;: Vietnamese error messages should sound respectful. "Vui lòng nhập email" (Please enter email) not "Nhập email" (Enter email).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placeholders&lt;/strong&gt;: Use &lt;code&gt;{min}&lt;/code&gt; and &lt;code&gt;{max}&lt;/code&gt; — they work with nestjs-i18n's interpolation:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"NAME_LENGTH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tên phải có từ {min} đến {max} ký tự"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your NestJS API now speaks Vietnamese natively.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you don't want to set this up from scratch, I built a &lt;a href="https://fadow.gumroad.com/l/nestjs-starter-kit-vietnam" rel="noopener noreferrer"&gt;NestJS Vietnam Starter Kit&lt;/a&gt; with i18n, JWT auth, RBAC, Docker, and 36 tests — ready to go.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>i18n</category>
      <category>typescript</category>
      <category>vietnam</category>
    </item>
    <item>
      <title>How I built a NestJS + Next.js starter kit with i18n (vi/en) — and why you should too</title>
      <dc:creator>fadow</dc:creator>
      <pubDate>Fri, 15 May 2026 06:12:56 +0000</pubDate>
      <link>https://dev.to/fadow/how-i-built-a-nestjs-nextjs-starter-kit-with-i18n-vien-and-why-you-should-too-5efa</link>
      <guid>https://dev.to/fadow/how-i-built-a-nestjs-nextjs-starter-kit-with-i18n-vien-and-why-you-should-too-5efa</guid>
      <description>&lt;p&gt;After setting up the same NestJS backend boilerplate for the Nth time, I realized I was spending 1-2 days just configuring auth, i18n, Docker, Redis, Swagger — before writing a single line of business logic.&lt;/p&gt;

&lt;p&gt;So I built two starter kits. Not vibe-coded. Clean code. Tested. Production-ready.&lt;/p&gt;

&lt;p&gt;What's inside&lt;/p&gt;

&lt;p&gt;🔥 NestJS Vietnam Starter ($8)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT auth (access + refresh tokens) + RBAC (admin/user roles)&lt;/li&gt;
&lt;li&gt;Bilingual i18n (Vietnamese + English) — auto-detects Accept-Language&lt;/li&gt;
&lt;li&gt;MongoDB + Redis + Bull queues&lt;/li&gt;
&lt;li&gt;Docker Compose — one command to run everything&lt;/li&gt;
&lt;li&gt;Auto-generated Swagger docs&lt;/li&gt;
&lt;li&gt;Rate limiting, validation pipes, exception filters&lt;/li&gt;
&lt;li&gt;36 tests (unit + e2e) — all green&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚡ Next.js Vietnam Starter ($13)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 16 App Router + React 19 + Tailwind v4&lt;/li&gt;
&lt;li&gt;Dark mode + bilingual i18n&lt;/li&gt;
&lt;li&gt;Auth pages (login/register) wired to the NestJS backend&lt;/li&gt;
&lt;li&gt;Dashboard, profile page, PayOS payment template&lt;/li&gt;
&lt;li&gt;Fully responsive, mobile-first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tech stack&lt;br&gt;
NestJS TypeScript MongoDB Redis Docker Next.js 16 React 19 Tailwind v4&lt;/p&gt;

&lt;p&gt;Why I built this&lt;/p&gt;

&lt;p&gt;Most starter kits are English-only. Vietnamese devs building for local markets need i18n from day one, with proper tone marks — not machine-translated garbage. These kits ship with natural Vietnamese translations alongside English.&lt;/p&gt;

&lt;p&gt;Also, I got tired of Googling the same setup steps every project.&lt;/p&gt;

&lt;p&gt;Try it&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fadow.gumroad.com/l/nestjs-starter-kit-vietnam" rel="noopener noreferrer"&gt;https://fadow.gumroad.com/l/nestjs-starter-kit-vietnam&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fadow.gumroad.com/l/nextjs-starter-kit-vn" rel="noopener noreferrer"&gt;https://fadow.gumroad.com/l/nextjs-starter-kit-vn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No free tier yet. But 7-day no-questions-asked refund if it doesn't save you time.&lt;/p&gt;

&lt;p&gt;Built to use. Not to flex.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
