<?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: Alex Kay</title>
    <description>The latest articles on DEV Community by Alex Kay (@alex_kay).</description>
    <link>https://dev.to/alex_kay</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%2F3826737%2F5cad5ee9-d5c7-423f-bef7-5ea019dd9f5f.png</url>
      <title>DEV Community: Alex Kay</title>
      <link>https://dev.to/alex_kay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alex_kay"/>
    <language>en</language>
    <item>
      <title>I Built a Free QR Code Generator with Scan Analytics</title>
      <dc:creator>Alex Kay</dc:creator>
      <pubDate>Mon, 16 Mar 2026 08:51:43 +0000</pubDate>
      <link>https://dev.to/alex_kay/how-to-create-a-qr-code-step-by-step-guide-d0g</link>
      <guid>https://dev.to/alex_kay/how-to-create-a-qr-code-step-by-step-guide-d0g</guid>
      <description>&lt;p&gt;Most QR generators are either free but static (no tracking, can't edit), or dynamic with analytics but $15-35/month. I wanted both for free. So I built &lt;a href="https://qree.app" rel="noopener noreferrer"&gt;qree.app&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;7 QR types: URL, vCard, WiFi, Email, Phone, SMS, Text&lt;/li&gt;
&lt;li&gt;Custom colors, dot styles, logo in center&lt;/li&gt;
&lt;li&gt;PNG + SVG download, no watermarks&lt;/li&gt;
&lt;li&gt;Dynamic QR codes — change URL without reprinting&lt;/li&gt;
&lt;li&gt;Scan analytics: country, city, device, browser, time&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Rails 8 + Vue 3 + Vite + Tailwind v4 + PostgreSQL. QR generation client-side with &lt;code&gt;qr-code-styling&lt;/code&gt;. GeoIP with MaxMind. Background jobs with Solid Queue.&lt;/p&gt;

&lt;p&gt;Rails monolith, not a separate API + SPA. Marketing pages are server-rendered ERB (SEO-friendly), Vue mounts only on interactive parts (generator, dashboard, analytics).&lt;/p&gt;

&lt;h2&gt;
  
  
  How Scan Tracking Works
&lt;/h2&gt;

&lt;p&gt;This is the interesting part. The QR doesn't encode your actual URL. It encodes a short redirect: &lt;code&gt;qree.app/abc1234&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When someone scans:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Phone opens &lt;code&gt;qree.app/abc1234&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Server logs IP → GeoIP (country, city) + User-Agent → device detection&lt;/li&gt;
&lt;li&gt;302 redirect to actual destination&lt;/li&gt;
&lt;li&gt;Total: ~50ms, user doesn't notice
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="n"&gt;qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;QrCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;short_code: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:code&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="no"&gt;LogScanJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;original_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;allow_other_host: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logging happens async so the redirect stays fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Results
&lt;/h2&gt;

&lt;p&gt;8 days after launch, Google Search Console shows 147 impressions, 55 queries, 27 countries. Only 5 out of 78 blog posts indexed so far. No clicks yet (positions 20-90), but trending up.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://qree.app" rel="noopener noreferrer"&gt;qree.app&lt;/a&gt; — no sign-up needed for static codes. Free account for dynamic + analytics.&lt;/p&gt;

&lt;p&gt;What would you add?&lt;/p&gt;

</description>
      <category>rails</category>
      <category>vue</category>
      <category>webdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
