<?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: Sachith Perera</title>
    <description>The latest articles on DEV Community by Sachith Perera (@sachith_perera_ad65c08093).</description>
    <link>https://dev.to/sachith_perera_ad65c08093</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%2F3937953%2Fc81652d7-8d62-48ca-aa1b-558c9539c02b.png</url>
      <title>DEV Community: Sachith Perera</title>
      <link>https://dev.to/sachith_perera_ad65c08093</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sachith_perera_ad65c08093"/>
    <language>en</language>
    <item>
      <title>I built 30 free developer tools in 4 weeks after work — here's everything I learned</title>
      <dc:creator>Sachith Perera</dc:creator>
      <pubDate>Mon, 18 May 2026 12:19:18 +0000</pubDate>
      <link>https://dev.to/sachith_perera_ad65c08093/i-built-30-free-developer-tools-in-4-weeks-after-work-heres-everything-i-learned-6m3</link>
      <guid>https://dev.to/sachith_perera_ad65c08093/i-built-30-free-developer-tools-in-4-weeks-after-work-heres-everything-i-learned-6m3</guid>
      <description>&lt;p&gt;It started with a stupid moment of paranoia.&lt;/p&gt;

&lt;p&gt;I was merging some work PDFs one evening.&lt;br&gt;
Contracts. NDAs. Sensitive stuff.&lt;/p&gt;

&lt;p&gt;Using one of those popular online PDF tools.&lt;/p&gt;

&lt;p&gt;And then I thought — &lt;em&gt;wait. Where do these files actually go?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I read the privacy policy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"We delete your files after 2 hours."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sure. But I have no way to verify that.&lt;/p&gt;

&lt;p&gt;That frustration turned into 4 weeks of building after work every day.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;taptools.dev&lt;/strong&gt; — 30 free developer tools that run entirely in your browser.&lt;/p&gt;


&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Most online tools work like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your file → their server → processing → result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;taptools works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your file → your browser → processing → result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No upload. No server. No way for anyone to access your files.&lt;/p&gt;

&lt;p&gt;Your data never leaves your device. Not mine. Not anyone's.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  PDF Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge PDF&lt;/strong&gt; — combine multiple PDFs into one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split PDF&lt;/strong&gt; — extract specific pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compress PDF&lt;/strong&gt; — reduce file size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF to JPG&lt;/strong&gt; — convert pages to images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF to Word&lt;/strong&gt; — extract text with paragraph detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON Formatter&lt;/strong&gt; — format + auto-fix broken JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XML Formatter&lt;/strong&gt; — format with syntax highlighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT Decoder&lt;/strong&gt; — decode and inspect JWT tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base64 Encoder/Decoder&lt;/strong&gt; — encode and decode instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex Tester&lt;/strong&gt; — test with live match highlighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash Generator&lt;/strong&gt; — MD5, SHA-1, SHA-256, SHA-512&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unix Timestamp Converter&lt;/strong&gt; — with next run times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Encoder/Decoder&lt;/strong&gt; — component and full URL modes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UUID Generator&lt;/strong&gt; — v1 and v4 with multiple formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cURL to Fetch Converter&lt;/strong&gt; — convert in both directions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron Expression Parser&lt;/strong&gt; — plain English explanation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff Checker&lt;/strong&gt; — unified and split view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color Converter&lt;/strong&gt; — HEX, RGB, HSL, CMYK&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Text &amp;amp; Other Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Word Counter&lt;/strong&gt; — words, chars, reading time, top words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Case Converter&lt;/strong&gt; — camelCase, snake_case and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lorem Ipsum Generator&lt;/strong&gt; — paragraphs, sentences, words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password Generator&lt;/strong&gt; — random + passphrase mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Number Base Converter&lt;/strong&gt; — decimal, binary, octal, hex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QR Code Generator&lt;/strong&gt; — with custom frames and colors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image to Base64&lt;/strong&gt; — drag and drop converter&lt;/li&gt;
&lt;li&gt;And more...&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The tech stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Next.js 14 + TypeScript&lt;/span&gt;
&lt;span class="na"&gt;Styling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;Tailwind CSS&lt;/span&gt;
&lt;span class="na"&gt;Hosting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;Vercel&lt;/span&gt;

&lt;span class="na"&gt;PDF&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;pdfjs-dist + pdf-lib&lt;/span&gt;
&lt;span class="na"&gt;Crypto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;Web Crypto API&lt;/span&gt;
&lt;span class="na"&gt;Images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;Canvas API&lt;/span&gt;
&lt;span class="na"&gt;Word docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;docx library&lt;/span&gt;
&lt;span class="na"&gt;QR codes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;qrcode library&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The most interesting technical challenge
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JSON Auto-Fix
&lt;/h3&gt;

&lt;p&gt;Most JSON formatters just show you the error and stop.&lt;/p&gt;

&lt;p&gt;Mine tries to &lt;em&gt;fix&lt;/em&gt; the error automatically.&lt;/p&gt;

&lt;p&gt;The fix pipeline:&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;// Step 1 — Remove comments&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/\/[^\n]&lt;/span&gt;&lt;span class="sr"&gt;*/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2 — Fix boolean casing&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;True&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;False&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3 — Remove trailing commas&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/,&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\]])&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Step 4 — Add missing quotes around keys&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;{,&lt;/span&gt;&lt;span class="se"&gt;]\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z_&lt;/span&gt;&lt;span class="se"&gt;][&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*:/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1"$2":&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Step 5 — Fix missing closing quote on keys&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/"&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_&lt;/span&gt;&lt;span class="se"&gt;\s\-]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!"&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;(?!\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\/\/)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"$1":&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Step 6 — Character level scanner (handles edge cases)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single quotes → double quotes&lt;/li&gt;
&lt;li&gt;Trailing commas&lt;/li&gt;
&lt;li&gt;Unquoted keys&lt;/li&gt;
&lt;li&gt;Missing closing quotes on keys&lt;/li&gt;
&lt;li&gt;Wrong boolean casing&lt;/li&gt;
&lt;li&gt;Comments (JavaScript style)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not perfect. But it handles the most common real-world mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF to Word with Smart Paragraph Detection
&lt;/h3&gt;

&lt;p&gt;Instead of dumping raw text I built a positioning algorithm:&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;// 1. Extract text items with X, Y positions&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Group items on the same line (similar Y value)&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Detect paragraph breaks (large Y gap between lines)&lt;/span&gt;
&lt;span class="c1"&gt;// 4. Detect headings (larger font size than average)&lt;/span&gt;
&lt;span class="c1"&gt;// 5. Detect bullet points (starts with •, -, *, etc)&lt;/span&gt;
&lt;span class="c1"&gt;// 6. Create proper Word paragraphs with spacing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result is a properly structured Word document &lt;br&gt;
instead of a wall of text.&lt;/p&gt;
&lt;h3&gt;
  
  
  Browser-Based PDF Rendering
&lt;/h3&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;pdfjsLib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdfjs-dist&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;pdf&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;pdfjsLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocument&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;promise&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&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;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageNum&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;viewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scale&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;span class="c1"&gt;// 144 DPI&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="na"&gt;canvasContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt; 
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;All in the browser. No server needed.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Ship early with fewer tools
&lt;/h3&gt;

&lt;p&gt;I launched with 8 tools in week 1.&lt;/p&gt;

&lt;p&gt;Real users from 10 countries found it organically &lt;br&gt;
within the first month without any marketing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🇺🇸 USA         — 24 users
🇮🇹 Italy        — 19 users
🇫🇷 France       — 6 users
🇱🇰 Sri Lanka    — 4 users
🇬🇧 UK           — 4 users
🇳🇱 Netherlands  — 4 users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That feedback loop is worth more than any amount of planning.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Privacy is a real differentiator
&lt;/h3&gt;

&lt;p&gt;Most competing tools upload your files to their servers.&lt;/p&gt;

&lt;p&gt;Some even have subscription traps — generate a QR code &lt;br&gt;
for free, print it on 1000 business cards, then your &lt;br&gt;
QR code expires unless you pay monthly.&lt;/p&gt;

&lt;p&gt;taptools QR codes are &lt;strong&gt;static&lt;/strong&gt; — the data is embedded &lt;br&gt;
directly in the image. They work forever even if &lt;br&gt;
taptools.dev shuts down tomorrow.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. 56 hours is enough to build something real
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 hours/day × 28 days = 56 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I work on a legacy enterprise system by day.&lt;br&gt;
Next.js was relatively new to me.&lt;/p&gt;

&lt;p&gt;Those 2-3 hours after work every day compounded &lt;br&gt;
into 30 live tools.&lt;/p&gt;

&lt;p&gt;Your day job teaches you more than you think.&lt;br&gt;
Debugging complex systems, thinking about edge cases,&lt;br&gt;
handling errors — those skills transfer directly.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. SEO compounds slowly but powerfully
&lt;/h3&gt;

&lt;p&gt;Each tool page targets specific search terms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Merge PDF          — 1.2M searches/month
Compress PDF       — 900k searches/month  
QR Code Generator  — 800k searches/month
Word Counter       — 500k searches/month
Password Generator — 400k searches/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;30 pages × focused keywords = compounding &lt;br&gt;
organic traffic over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The hardest part is not the code
&lt;/h3&gt;

&lt;p&gt;It's convincing yourself to keep going &lt;br&gt;
when nobody is watching.&lt;/p&gt;

&lt;p&gt;When you have a full time job a side project &lt;br&gt;
lives or dies in those 2-3 hours after work.&lt;/p&gt;

&lt;p&gt;Tired hours. Distracted hours.&lt;/p&gt;

&lt;p&gt;But those hours compound into something real.&lt;/p&gt;




&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Tools live&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="m"&gt;30&lt;/span&gt;
&lt;span class="na"&gt;Countries with users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10+&lt;/span&gt;
&lt;span class="na"&gt;Marketing spend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;€0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;More tools based on analytics data&lt;/li&gt;
&lt;li&gt;Blog content targeting long-tail SEO keywords&lt;/li&gt;
&lt;li&gt;AdSense monetization (pending approval)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple passive income that funds &lt;br&gt;
future projects.&lt;/p&gt;

&lt;p&gt;Not to get rich. Just to build something &lt;br&gt;
sustainable that keeps earning while I sleep.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://taptools.dev" rel="noopener noreferrer"&gt;taptools.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No signup. Nothing stored. Ever.&lt;/p&gt;

&lt;p&gt;Would love your feedback in the comments below 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What tools do you use daily that you wish had a &lt;br&gt;
privacy-first browser-based version?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading! If you found this useful &lt;br&gt;
consider following for more build-in-public content.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
