<?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: Hitesh M</title>
    <description>The latest articles on DEV Community by Hitesh M (@hitesh_m_c864380a9c70c417).</description>
    <link>https://dev.to/hitesh_m_c864380a9c70c417</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%2F3966873%2Fa20210cf-5c27-413e-b331-fbeee337a8b9.jpg</url>
      <title>DEV Community: Hitesh M</title>
      <link>https://dev.to/hitesh_m_c864380a9c70c417</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hitesh_m_c864380a9c70c417"/>
    <language>en</language>
    <item>
      <title># I built a $9 lifetime app with negligible server interaction — here's the architecture</title>
      <dc:creator>Hitesh M</dc:creator>
      <pubDate>Thu, 04 Jun 2026 19:49:57 +0000</pubDate>
      <link>https://dev.to/hitesh_m_c864380a9c70c417/-i-built-a-9-lifetime-app-with-no-server-heres-the-architecture-1kgo</link>
      <guid>https://dev.to/hitesh_m_c864380a9c70c417/-i-built-a-9-lifetime-app-with-no-server-heres-the-architecture-1kgo</guid>
      <description>&lt;p&gt;A few months ago I got tired of every bookmark tool asking me to create an account, sync to their cloud, and pay monthly for the privilege. I just wanted to save URLs and find them later. That's it.&lt;/p&gt;

&lt;p&gt;So I built one myself. It's called &lt;strong&gt;Squirrel&lt;/strong&gt; — a bookmark manager that runs entirely in your browser, stores everything locally, and costs $9 once. No subscription. No account. No server.&lt;/p&gt;

&lt;p&gt;This article is about how that works technically, the tradeoffs involved, and what I learned shipping it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "no server" actually means
&lt;/h2&gt;

&lt;p&gt;When I say no server, I mean the app has no backend of its own. There is no database I'm running somewhere. No API you're talking to. No cloud storing your data.&lt;/p&gt;

&lt;p&gt;Everything — your bookmarks, your sections, your settings — lives in your browser's built-in storage. When you close the tab and come back, your data is still there because the browser kept it, not because I have a server remembering it.&lt;/p&gt;

&lt;p&gt;There are exactly three places where the app makes an external request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loading the app itself&lt;/strong&gt; — from Cloudflare Pages, which is just static file hosting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt; — a cookieless Cloudflare beacon that counts page visits (no personal data)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License validation&lt;/strong&gt; — a single request to a Cloudflare Worker when you activate a premium key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is the only server-side logic in the entire product. Everything else is the browser doing the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The database that lives in your browser
&lt;/h2&gt;

&lt;p&gt;Most developers reach for a database when they need to store structured data. But browsers have had a capable built-in database for years — &lt;strong&gt;IndexedDB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;IndexedDB is not localStorage (which is just a key-value store, limited to strings). It's a real document database that runs natively inside the browser — no network requests, no server, no setup. Your data never leaves your machine because there's nowhere to send it.&lt;/p&gt;

&lt;p&gt;Squirrel uses IndexedDB with four stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bookmarks&lt;/code&gt; — your sections and links&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;settings&lt;/code&gt; — theme, preferences&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autoBackups&lt;/code&gt; — rolling 30-minute snapshots&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dailyBackups&lt;/code&gt; — one named backup per day, 7 days of history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The daily backup history is a premium feature. Which brings me to the hardest part of building this.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do you sell a $9 app with no server?
&lt;/h2&gt;

&lt;p&gt;If the app runs entirely offline and there's no account system, how do you gate premium features? The honest answer is: imperfectly, with a Cloudflare Worker as the one server-side component.&lt;/p&gt;

&lt;p&gt;When someone buys Squirrel on Gumroad, they get a license key in the format &lt;code&gt;BM-XXXX-XXXX-XXXX&lt;/code&gt;. When they activate it, the app sends the key to a Cloudflare Worker, which verifies it against Gumroad's API and returns valid or invalid. That's the entire backend.&lt;/p&gt;

&lt;p&gt;After successful validation, the result is cached locally with a tamper-evident integrity token — a salted hash of the key and timestamp. The app checks this cache on startup instead of calling the Worker every time. If someone manually edits their localStorage to fake the cache, the hash won't match and they get bounced back to the free tier. The salt is buried in the bundle, and the honest reality is: the kind of person who can reverse-engineer all that is probably not the kind of person who'd pay $9 anyway. Ship it and move on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security — because the browser is the boundary
&lt;/h2&gt;

&lt;p&gt;When there's no server, the browser becomes your security perimeter. That changes how you think about threats.&lt;/p&gt;

&lt;p&gt;On a traditional web app, user input gets sanitised server-side before it touches a database. Here, everything happens in the browser — which means I had to be deliberate about it on the client.&lt;/p&gt;

&lt;p&gt;A few things I hardened specifically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XSS prevention.&lt;/strong&gt; Every bookmark URL goes through a protocol allowlist before it's saved — &lt;code&gt;javascript:&lt;/code&gt; and &lt;code&gt;data:&lt;/code&gt; URLs are rejected outright. All user-supplied strings (section names, bookmark titles, import previews) are HTML-escaped before being rendered into the DOM. It sounds obvious, but it's easy to miss when you're moving fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Worker hardening.&lt;/strong&gt; The license validation Worker only accepts requests from &lt;code&gt;squirrel.aditco.in&lt;/code&gt; — the CORS header is locked to that origin, not a wildcard. It also rate-limits by IP: more than 10 validation attempts per minute from the same address gets a 429. This protects against brute-force key guessing without any additional infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security headers.&lt;/strong&gt; The Cloudflare Pages deployment sets HSTS, &lt;code&gt;X-Frame-Options: DENY&lt;/code&gt;, &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;, and a strict &lt;code&gt;Referrer-Policy&lt;/code&gt;. Small things individually, but they close a set of browser-level attack vectors that are trivial to block and easy to forget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No third-party favicon proxy.&lt;/strong&gt; An earlier version used Google's favicon service to show site icons — which meant Google could see every URL you bookmarked. Replaced with a direct fetch from each site's own &lt;code&gt;/favicon.ico&lt;/code&gt;. Your browsing habits don't leave your machine.&lt;/p&gt;

&lt;p&gt;None of this makes the app a fortress. Client-side security has real limits. But for a tool that makes a privacy promise to users, getting these details right matters more than the features do.&lt;/p&gt;




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

&lt;p&gt;The entire app is two JavaScript files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.js&lt;/code&gt; — UI, event handlers, rendering, all business logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;indexedDB.js&lt;/code&gt; — data persistence layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No framework. No build step for the app itself (only obfuscation for production). Vanilla JS, a bit of CSS, and a single HTML file.&lt;/p&gt;

&lt;p&gt;Why no framework? Partly because I wanted the app to be distributable as a literal single HTML file — download it, open it in any browser, works forever. Partly because a bookmark manager doesn't need React. The DOM manipulation is simple enough.&lt;/p&gt;

&lt;p&gt;The production build runs the source through &lt;code&gt;javascript-obfuscator&lt;/code&gt;, bundles everything into one self-contained &lt;code&gt;squirrel.html&lt;/code&gt;, and deploys to Cloudflare Pages via a GitHub Actions workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ship the article before the product, not after.&lt;/strong&gt; I launched on Product Hunt and got 200 visitors on day one with solid engagement. But I had no written content anywhere explaining how it was built or why. The people most likely to buy are developers who understand what they're getting — and developers read. This article should have existed on launch day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan for domain migration before it happens.&lt;/strong&gt; I renamed the product from BookMark Manager to Squirrel mid-launch and changed the domain from &lt;code&gt;bookmarks.aditco.in&lt;/code&gt; to &lt;code&gt;squirrel.aditco.in&lt;/code&gt;. What I didn't account for: IndexedDB is scoped to the origin. Every user who had saved bookmarks at the old domain had their data stay behind when the redirect kicked in. I had to disable the redirect, add migration banners to both domains, and give users a 48-hour window to export and re-import. A lesson learned the hard way about client-side storage and domain changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "no server" constraint is a feature, not just an architecture choice.&lt;/strong&gt; I started building this because I didn't want to run a backend. But users responded most strongly to the privacy angle — your data never leaves your browser. That's the pitch, not the implementation detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it is now
&lt;/h2&gt;

&lt;p&gt;Squirrel launched on Product Hunt in June 2026 and is live at &lt;a href="https://squirrel.aditco.in" rel="noopener noreferrer"&gt;squirrel.aditco.in&lt;/a&gt;. Free up to 25 bookmarks, $9 one-time for unlimited — no subscription, no account required.&lt;/p&gt;

&lt;p&gt;The top traffic countries so far are India, the US, and Turkey, which led me to enable purchasing power parity pricing on Gumroad so the $9 price point scales fairly across markets.&lt;/p&gt;

&lt;p&gt;If you're curious about the architecture or want to look at how the license validation works, feel free to ask in the comments. And if you've built something similar — a local-first app with a lightweight monetisation layer — I'd genuinely like to hear what tradeoffs you made.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with vanilla JS, IndexedDB, Cloudflare Pages, and one very small Cloudflare Worker.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
