<?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: Said Ouarrak</title>
    <description>The latest articles on DEV Community by Said Ouarrak (@saidtechnology).</description>
    <link>https://dev.to/saidtechnology</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%2F1201245%2Fb2d33574-9897-42f0-a2f5-05b0792cce99.png</url>
      <title>DEV Community: Said Ouarrak</title>
      <link>https://dev.to/saidtechnology</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saidtechnology"/>
    <language>en</language>
    <item>
      <title>I Built an Uptime Monitor That Does Not Wake You Up for Nothing</title>
      <dc:creator>Said Ouarrak</dc:creator>
      <pubDate>Thu, 01 Oct 2026 10:56:41 +0000</pubDate>
      <link>https://dev.to/saidtechnology/i-built-an-uptime-monitor-that-does-not-wake-you-up-for-nothing-278g</link>
      <guid>https://dev.to/saidtechnology/i-built-an-uptime-monitor-that-does-not-wake-you-up-for-nothing-278g</guid>
      <description>&lt;p&gt;I built an uptime monitoring service, and the hardest part wasn't any single feature. It was the decision to &lt;strong&gt;not page anyone until I'm confident something is actually broken.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The false alarm problem
&lt;/h2&gt;

&lt;p&gt;Naive uptime monitoring is trivial to build. You schedule a request, you check the status code, you fire an alert if it's not 200. Ship it, and within a week you regret it.&lt;/p&gt;

&lt;p&gt;Here's what actually happens in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A deploy restarts your server for 4 seconds&lt;/li&gt;
&lt;li&gt;A shared database hiccups for one query&lt;/li&gt;
&lt;li&gt;Someone's deploy triggers a 502 for a single request while the load balancer routes around it&lt;/li&gt;
&lt;li&gt;A CDN edge node in a distant region times out on one TLS handshake&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those is a "failed check." In a naive implementation each one opens an incident, sends you a Telegram message, and wakes you up. Within a month you mute the channel. Once you mute it, the one alert that mattered — the real 40-minute outage — goes unread with everything else.&lt;/p&gt;

&lt;p&gt;So before writing any monitoring code, the rule was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An incident only opens after &lt;strong&gt;N consecutive failed checks&lt;/strong&gt; (default: 2).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One blip is a blip. Two in a row is a pattern. That's a one-line fix that completely changes the signal-to-noise ratio.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second half: alert on transitions, not on failures
&lt;/h2&gt;

&lt;p&gt;The related mistake is alerting per failed check. If something is down for an hour and you check every 60 seconds, that's 60 messages. Meaningless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pulseping.xyz/" rel="noopener noreferrer"&gt;UptimeCadet&lt;/a&gt; sends alerts &lt;strong&gt;once per state transition&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="c1"&gt;// The whole alerting rule, in spirit&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wasDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&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;isDown&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&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;isDown&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wasDown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nf"&gt;notifyDown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// fires exactly once&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;isDown&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;wasDown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;notifyUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// fires exactly once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Down → notification. Stayed down → silence. Recovered → notification.&lt;/p&gt;

&lt;p&gt;This is the kind of thing that seems obvious once you know it and feels like a puzzle when you don't. Worth writing down so the next person doesn't reinvent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I check beyond "is it up?"
&lt;/h2&gt;

&lt;p&gt;HTTP status is the baseline, but it answers a much narrower question than people expect. A monitor that only checks for 200 misses entire categories of real problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSL certificate expiry.&lt;/strong&gt; A cert that expires at 3am doesn't break anything until 3am. Then everything breaks at once. Checking the cert's not-after date days in advance turns a catastrophe into a Tuesday afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain expiry via RDAP.&lt;/strong&gt; The RDAP protocol replaced WHOIS because WHOIS responses were inconsistent and often rate-limited. Domain expiry is the same class of problem as SSL — an invisible countdown that becomes total at zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lighthouse page-speed scores.&lt;/strong&gt; Now this one is a genuine tradeoff, and I'll be honest about it. Lighthouse is slow — running it on every interval would be absurd. The value isn't "your site got faster," it's &lt;em&gt;directional&lt;/em&gt;: a score that slides from 92 to 55 over a week means you shipped something expensive. The metric is the trend, not the absolute number.&lt;/p&gt;

&lt;p&gt;So the dashboard shows more than a green/red light. SSL, domain, and performance each get their own row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check interval is a plan limit, not a setting
&lt;/h2&gt;

&lt;p&gt;The free plan checks every 300 seconds. Paid plans check every 60. The interval is &lt;strong&gt;clamped&lt;/strong&gt; to the plan minimum server-side and capped at 3600:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested: 10s   → clamped to 300s (Free) or 60s (paid)
requested: 7200s → capped at 3600s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is deliberate. The interval is the single biggest driver of infrastructure cost — a monitor checked every 10 seconds across thousands of endpoints is a different product economically than one checked every 5 minutes. If the client could just request whatever it wanted, the free tier would quietly be the most expensive tier.&lt;/p&gt;

&lt;p&gt;The docs state the actual numbers rather than "varies by plan," because a monitoring tool that hides its resolution is not trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passwordless login over channels you already own
&lt;/h2&gt;

&lt;p&gt;There's no password. You enter your email, choose where a 6-digit code should arrive — Telegram, Slack, Discord, or email — and the code must land on a channel &lt;strong&gt;you already connected and verified&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The constraint that makes this safe: nothing is active until you verify it. Connect a channel, receive a 6-digit code on it, enter the code. Until then it sends nothing.&lt;/p&gt;

&lt;p&gt;It removes a whole category of problem. No password database to breach, no reset flow to build, no "we'll email you a link" that depends on the mail provider being up. If you can receive Telegram messages, you can sign in.&lt;/p&gt;

&lt;p&gt;Codes are single-use, expire after 10 minutes, and allow 3 attempts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The public status API, no key required
&lt;/h2&gt;

&lt;p&gt;Published status pages are exposed as read-only JSON with no account and no API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://pulseping.xyz/api/public/status?slug=your-slug
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No key is a security decision, not an oversight. Everything it returns is &lt;em&gt;already public&lt;/em&gt; — it's the same data rendered at &lt;code&gt;/status/your-slug&lt;/code&gt;. Anyone who can see the page can read the JSON. Adding an API key would create the illusion of protecting something that isn't secret, while adding a signing, rotation, and revocation system to maintain.&lt;/p&gt;

&lt;p&gt;Unpublished pages return 404, so drafts can't be scraped.&lt;/p&gt;

&lt;p&gt;This is what makes embeddable status work:&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;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://pulseping.xyz/status/your-slug"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or a fetch against the endpoint for a live badge in your README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven languages, one of them full RTL
&lt;/h2&gt;

&lt;p&gt;The UI ships in English, Arabic, French, German, Spanish, Portuguese, and Turkish — with Arabic as a genuine right-to-left layout, not a mirrored stylesheet with a few flipped icons.&lt;/p&gt;

&lt;p&gt;This is more work than it looks. &lt;code&gt;dir="rtl"&lt;/code&gt; handles text direction, but anything positioned with physical CSS properties (&lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, &lt;code&gt;margin-left&lt;/code&gt;) has to become logical (&lt;code&gt;inset-inline-start&lt;/code&gt;, &lt;code&gt;margin-inline-end&lt;/code&gt;). Icons that imply direction — a back arrow, a progress bar — need to flip. Charts need their axes reversed. It's the kind of work that's invisible when it's done right and immediately obvious when it wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest tradeoffs
&lt;/h2&gt;

&lt;p&gt;Things I'd do differently with more time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checks are sequential per monitor.&lt;/strong&gt; At high monitor counts this needs a real worker pool and concurrency limits. Right now it's fine at the scale I support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No multi-region probing.&lt;/strong&gt; Every check originates from one location, so a regional outage can look healthy. Real uptime services run checks from several regions and report the worst result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse on every interval is overkill.&lt;/strong&gt; I check it on a slow cadence, but the scheduling deserves to be first-class rather than a special case bolted onto the checker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention is unlimited and I should cap it.&lt;/strong&gt; Every check is stored. Over years that's a lot of rows for a service charging $12/month.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://pulseping.xyz/" rel="noopener noreferrer"&gt;UptimeCadet&lt;/a&gt; — free tier with one monitor, checks every 5 minutes, email alerts, no credit card. Pro is $12/month for 1-minute checks, a public status page, and Telegram/Slack/Discord alerts.&lt;/p&gt;

&lt;p&gt;Full documentation, including the alert rules and plan limits, is at &lt;a href="https://pulseping.xyz/docs" rel="noopener noreferrer"&gt;pulseping.xyz/docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you find a case where this design pages you for no reason — I'd genuinely like to hear it. That's the failure mode I'm most trying to avoid.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://www.saidtechnology.com/" rel="noopener noreferrer"&gt;saidtechnology&lt;/a&gt; — a one-person studio. I build privacy-first web and mobile tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>devops</category>
      <category>monitoring</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a QR Code Generator That Never Sends Your Data Anywhere</title>
      <dc:creator>Said Ouarrak</dc:creator>
      <pubDate>Thu, 01 Oct 2026 02:13:44 +0000</pubDate>
      <link>https://dev.to/saidtechnology/i-built-a-qr-code-generator-that-never-sends-your-data-anywhere-50i4</link>
      <guid>https://dev.to/saidtechnology/i-built-a-qr-code-generator-that-never-sends-your-data-anywhere-50i4</guid>
      <description>&lt;p&gt;Most "free QR code generators" work like this: you type something, it gets sent to a server, the server encodes it and sends an image back.&lt;/p&gt;

&lt;p&gt;That works fine. But think about what people actually encode. WiFi passwords. Contact details. URLs to internal documents. Pay-by-bank links. Sometimes literally a one-time link to something private.&lt;/p&gt;

&lt;p&gt;If the encoder runs on someone's server, then by definition that server can read it. Even if nobody is watching. Even if the privacy policy promises not to. The data simply arrives somewhere you don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "100% client-side" actually means
&lt;/h2&gt;

&lt;p&gt;When I built &lt;a href="https://www.qrgenerators.dev/" rel="noopener noreferrer"&gt;QR Generator&lt;/a&gt;, I set one rule up front: &lt;strong&gt;the QR encoding never leaves the browser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;POST&lt;/code&gt; to an API. No &lt;code&gt;POST&lt;/code&gt; to a serverless function. No queue that might log the payload somewhere. The input you type stays in JavaScript memory, gets turned into a matrix of modules, and is rendered into a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; or an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; that never goes anywhere.&lt;/p&gt;

&lt;p&gt;The app is built with Next.js (App Router, running on Turbopack), but for this particular feature "server" is almost a misleading word — the generation step is pure client-side computation. React handles the form state; everything else happens locally.&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;// The whole pipeline, roughly.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;QRCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;errorCorrectionLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// L / M / Q / H&lt;/span&gt;
  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bg&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;canvasRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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="s2"&gt;2d&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="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qr&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No network tab traffic. Open DevTools and watch it stay empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part: error correction vs. logos
&lt;/h2&gt;

&lt;p&gt;QR codes have four error-correction levels. In plain terms, they trade redundancy for robustness:&lt;/p&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;Redundancy&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;td&gt;7%&lt;/td&gt;
&lt;td&gt;Clean screen, plenty of room&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;Default. Good all-rounder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;Branded colors, print&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;Logo overlay, printed, possibly scuffed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A logo is the interesting case, because a logo &lt;em&gt;is&lt;/em&gt; damage. You are deliberately covering modules the scanner needs in order to read the code correctly.&lt;/p&gt;

&lt;p&gt;So when you upload a logo, the generator bumps the error correction level automatically — the bigger your logo, the more redundancy the code needs to survive it. It's a small detail, but it's the difference between a QR code that scans every time and one that fails on a printed sticker.&lt;/p&gt;

&lt;h2&gt;
  
  
  PNG vs SVG, and why it matters
&lt;/h2&gt;

&lt;p&gt;The other thing I care about is output quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PNG&lt;/strong&gt; is what you want for screens and social posts. Raster, pixel-perfect at the size you export it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SVG&lt;/strong&gt; is what you want for print — a logo on a business card, a sticker, a poster. It's resolution-independent, so it stays sharp at any scale, from a 20mm sticker to a two-metre banner.&lt;/p&gt;

&lt;p&gt;Shipping both costs nothing, and it means the tool is not forcing everyone into one compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I deliberately left out
&lt;/h2&gt;

&lt;p&gt;Some things you can add that you can't take back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No accounts.&lt;/strong&gt; No email, no password, no "your QR codes" dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No storage.&lt;/strong&gt; Nothing saved. Close the tab and it's gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No scan analytics.&lt;/strong&gt; This is the big one, and it was a real decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Analytics on a QR generator are tempting — you'd learn which codes people generate, from where, and when. But the whole premise of the tool is that the payload is private. Shipping behavioral tracking alongside a privacy promise would make the promise worthless. So: none.&lt;/p&gt;

&lt;p&gt;The honest tradeoff is that I can't tell you which features get used. I know what performs from search data and what users write to me about. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the generator
&lt;/h2&gt;

&lt;p&gt;The same site runs an &lt;a href="https://www.qrgenerators.dev/" rel="noopener noreferrer"&gt;Android app&lt;/a&gt; and a fairly large &lt;a href="https://www.qrgenerators.dev/blog" rel="noopener noreferrer"&gt;guides blog&lt;/a&gt; covering things like when NFC is actually the better choice than a QR code, scan-rate patterns, and how to verify where a QR code points before you scan it.&lt;/p&gt;

&lt;p&gt;That blog is a deliberate SEO play, but it's also the part I find genuinely useful — most QR content online repeats the same five steps. The interesting questions are the ones about &lt;em&gt;when not to use a QR code&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;A few things this approach doesn't solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload size is still bounded by what a QR code can physically hold. Trying to encode a novel will not work.&lt;/li&gt;
&lt;li&gt;On older mobile browsers, heavy payloads plus SVG rendering can feel sluggish.&lt;/li&gt;
&lt;li&gt;"Private" means "doesn't reach my server." It does not mean "safe" — generate a QR code for something dangerous and it's still dangerous.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://www.qrgenerators.dev/" rel="noopener noreferrer"&gt;QR Generator&lt;/a&gt; — free, no signup, no watermark, PNG and SVG, runs entirely in your browser.&lt;/p&gt;

&lt;p&gt;If you spot something broken or find an edge case I got wrong, I'd genuinely like to hear about it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://www.saidtechnology.com/" rel="noopener noreferrer"&gt;saidtechnology&lt;/a&gt; — a one-person studio. I build privacy-first web and mobile tools.&lt;/em&gt;&lt;/p&gt;

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