<?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: Mohd Salahudeen</title>
    <description>The latest articles on DEV Community by Mohd Salahudeen (@salahxd).</description>
    <link>https://dev.to/salahxd</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%2F2486134%2Fd0befe46-62f7-4b25-b6c2-e82f771ad077.jpeg</url>
      <title>DEV Community: Mohd Salahudeen</title>
      <link>https://dev.to/salahxd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/salahxd"/>
    <language>en</language>
    <item>
      <title>See Through Any Web App</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:36:49 +0000</pubDate>
      <link>https://dev.to/salahxd/see-through-any-web-app-40cg</link>
      <guid>https://dev.to/salahxd/see-through-any-web-app-40cg</guid>
      <description>&lt;p&gt;For most of software's history, writing code was the bottleneck. Today, understanding it is.&lt;/p&gt;

&lt;p&gt;AI can generate thousands of lines in seconds. Frameworks abstract away the hard parts, libraries hide their internals, and a single page pulls in dependencies that almost nobody ever opens and reads. We got very good at producing software, and we did almost nothing to make it easier to understand. That gap is the problem I kept running into — so I built a tool for it: Archify.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that should take seconds
&lt;/h2&gt;

&lt;p&gt;Every day, developers lose hours to questions that have exact answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What component is rendering this?&lt;/li&gt;
&lt;li&gt;Where is this data coming from?&lt;/li&gt;
&lt;li&gt;Which API powers this feature?&lt;/li&gt;
&lt;li&gt;What third-party scripts are running on this page?&lt;/li&gt;
&lt;li&gt;What can actually reach user data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers all exist. They're just buried under layers of abstraction — scattered across DevTools panels, source maps, network tabs, and a repository you may not even have access to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding software shouldn't feel like archaeology
&lt;/h2&gt;

&lt;p&gt;When you land on an unfamiliar app, the routine is always the same. Open DevTools. Inspect elements. Dig through network requests. Search the source maps. Trace the API calls. Jump between docs, repos, and a dozen browser tabs until a mental model finally clicks into place.&lt;/p&gt;

&lt;p&gt;The information was never the hard part. Modern tooling is excellent at exposing implementation details — the individual requests, the minified bundles, the raw responses. It's far worse at exposing architecture: how those pieces actually connect. That's the part you end up reconstructing by hand, every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Archify does
&lt;/h2&gt;

&lt;p&gt;Archify is a browser extension that shows you what's actually happening behind any web app. Hover over an element and you instantly see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component rendering it&lt;/li&gt;
&lt;li&gt;The framework or library behind it&lt;/li&gt;
&lt;li&gt;The API connections powering it&lt;/li&gt;
&lt;li&gt;The scripts running on the page&lt;/li&gt;
&lt;li&gt;How those parts relate to each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of spending an afternoon tracing behavior across tabs and files, you get the architecture directly — not just what exists, but how it fits together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for understanding, not surveillance
&lt;/h2&gt;

&lt;p&gt;Most developer tools start from the assumption that your data should leave your machine. Archify doesn't. It runs entirely inside your browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No cloud processing&lt;/li&gt;
&lt;li&gt;No server-side analysis&lt;/li&gt;
&lt;li&gt;No source code uploads&lt;/li&gt;
&lt;li&gt;No accounts&lt;/li&gt;
&lt;li&gt;No hidden telemetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The systems you explore stay yours. That isn't a marketing line — it's the architecture. There's nothing on the other end to send your code to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;The industry is mid-shift. Generating code is becoming nearly free, but generated code still has to be reviewed, dependencies still have to be trusted, and applications still have to be understood. The ability to build is accelerating. The ability to comprehend isn't. Every day, that gap widens.&lt;/p&gt;

&lt;p&gt;I think the next generation of developer tools won't be judged by how much code they can write. They'll be judged by how quickly they help you understand what already exists. That's what Archify is for: making the act of understanding a piece of software as fast as the act of using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming soon
&lt;/h2&gt;

&lt;p&gt;Archify is getting ready for launch, and I'm inviting early users in now. If you've ever opened DevTools and wished the app would just explain itself, you're exactly who I'm building this for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://archify.salahxd.dev" rel="noopener noreferrer"&gt;Join the waitlist at archify.salahxd.dev&lt;/a&gt; — be among the first to try it, and help shape what it becomes. Because software shouldn't be a black box.&lt;/p&gt;

</description>
      <category>developertools</category>
      <category>extensions</category>
      <category>devtools</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My Site Has No Database, But It Has a CMS, Comments, Likes, and Views</title>
      <dc:creator>Mohd Salahudeen</dc:creator>
      <pubDate>Thu, 11 Jun 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/salahxd/this-site-has-no-database-but-it-has-a-cms-comments-likes-and-views-317b</link>
      <guid>https://dev.to/salahxd/this-site-has-no-database-but-it-has-a-cms-comments-likes-and-views-317b</guid>
      <description>&lt;p&gt;The title isn't a trick. My &lt;a href="https://salahxd.dev" rel="noopener noreferrer"&gt;site&lt;/a&gt; has a full CMS with an admin panel at &lt;code&gt;/keystatic&lt;/code&gt;, real comments under every post, a like button that remembers you, and view counters that tick up. And there is no database behind any of it — at least, none that I run.&lt;/p&gt;

&lt;p&gt;No server I deploy, no database I provisioned. I've never written a migration for this site, never tuned a connection pool, and there's no disk that can fill up at 3am and page me.&lt;/p&gt;

&lt;p&gt;That last part was the whole design goal. I'm building a startup, and whatever attention I have left over is not going toward babysitting infrastructure for a personal blog. So I gave myself one rule when building this: if a feature needs me to &lt;em&gt;operate&lt;/em&gt; something, it doesn't ship.&lt;/p&gt;

&lt;p&gt;What surprised me is how little I had to give up. The whole thing builds to static files and sits on a CDN — but every piece of state you can touch here still gets saved somewhere durable. The state didn't disappear. I just stopped owning the places where it lands.&lt;/p&gt;

&lt;p&gt;This is not a genius-architecture post. Most of it is me being lazy in a useful direction. But it works well enough that people ask about it, so here's how each piece fits together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CMS is just git
&lt;/h2&gt;

&lt;p&gt;The blog runs on &lt;a href="https://keystatic.com/" rel="noopener noreferrer"&gt;Keystatic&lt;/a&gt;. When I open &lt;code&gt;/keystatic&lt;/code&gt; in production and hit save, it doesn't write to a database. It makes a commit to this repo through a GitHub App:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// keystatic.config.ts&lt;/span&gt;
&lt;span class="nx"&gt;storage&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Salah-XD/personal-portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In dev it writes files to my local disk. In production it commits straight to GitHub. The post you're reading is a &lt;code&gt;.md&lt;/code&gt; file in &lt;code&gt;src/content/blog/&lt;/code&gt;. My &lt;code&gt;/now&lt;/code&gt; and &lt;code&gt;/uses&lt;/code&gt; pages are JSON files in the same repo, edited through the same admin UI.&lt;/p&gt;

&lt;p&gt;So my "content database" is the git history. Publishing is a commit. Fixing a typo is a commit. Undoing a bad edit is &lt;code&gt;git revert&lt;/code&gt;. Vercel sees the push and rebuilds. I get diffs, blame, and backups for free, from the version control I was going to use anyway.&lt;/p&gt;

&lt;p&gt;The catch: writes are slow in the way a deploy is slow. Hitting save kicks off a rebuild, so "publish" takes about a minute. For a blog, I genuinely don't care. If that minute bothered me, this whole setup would be the wrong choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comments are GitHub Discussions in a trench coat
&lt;/h2&gt;

&lt;p&gt;The comment box at the bottom of each post is &lt;a href="https://giscus.app/" rel="noopener noreferrer"&gt;Giscus&lt;/a&gt;, a thin shell over GitHub Discussions. Each post maps to a Discussion thread in the repo. When you comment, you're posting to GitHub, signed in as yourself.&lt;/p&gt;

&lt;p&gt;So my "comments table" is a Discussions tab, my spam protection is GitHub's, and my auth is OAuth somebody else built. If the env vars aren't wired up, the component renders a placeholder instead of breaking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{giscusReady ? (
  &amp;lt;Comments ... /&amp;gt;
) : (
  &amp;lt;p&amp;gt;Comments will appear here once Giscus is configured.&amp;lt;/p&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a real downside here and I want to be upfront about it: you need a GitHub account to comment. For a blog where the readers are mostly developers, that filter costs me almost nothing and kills spam dead. If I were writing for a general audience, it would be a terrible choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, confession: the like button does touch a server
&lt;/h2&gt;

&lt;p&gt;The likes and views from the title are the one kind of state on this site I couldn't fake at build time — a view happens when a person shows up, and there's no way around counting it live. So those two features hit &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash Redis&lt;/a&gt; through the only two routes on the site that aren't static:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this route becomes a Vercel Function&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&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;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`likes:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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 the entire data model. Two integer keys per post — &lt;code&gt;likes:slug&lt;/code&gt; and &lt;code&gt;views:slug&lt;/code&gt; — and the only operation is &lt;code&gt;INCR&lt;/code&gt;. No schema, no ORM, no tables. Deduping is a cookie, not a query: 30 minutes for views, a year for likes. Someone clearing their cookies can like a post twice. I can live with that.&lt;/p&gt;

&lt;p&gt;And the Redis layer is optional. If the keys aren't set, it degrades instead of crashing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/redis.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_REST_API_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPSTASH_REDIS_REST_URL&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_REST_API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UPSTASH_REDIS_REST_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;url&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&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;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_redis&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;const&lt;/span&gt; &lt;span class="nx"&gt;isRedisConfigured&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_redis&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yes — strictly speaking, "no backend" is false. There are two serverless functions and a managed key-value store. But Upstash speaks plain HTTP and bills per request, so there's nothing to keep alive between visitors. I provisioned it by clicking a button on a marketplace page. I have never operated it. That feels meaningfully different from "running a database," even if a pedant would disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything else happens before anyone visits
&lt;/h2&gt;

&lt;p&gt;The rest of what looks dynamic is work done once, at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; is &lt;a href="https://pagefind.app/" rel="noopener noreferrer"&gt;Pagefind&lt;/a&gt; — it indexes the built HTML and ships a static index the browser queries. No search server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The OG image&lt;/strong&gt; on every post is rendered with satori + resvg during the build. No screenshot service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSS and the sitemap&lt;/strong&gt; are just generated files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a request comes in, almost none of this code runs. It already ran, once, on my machine and on Vercel's build servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually costs me
&lt;/h2&gt;

&lt;p&gt;None of this is free, it's just paid in a different currency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No instant writes.&lt;/strong&gt; Publishing is a deploy. Fine for a blog, useless for anything interactive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No queries.&lt;/strong&gt; I can increment a counter. I can't ask "which posts did people who liked X also read." If I ever want that, Redis with two keys per post isn't the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More vendors, not fewer.&lt;/strong&gt; Instead of one database I run, I depend on GitHub, Upstash, Vercel, and Buttondown for the newsletter. My bet is that each of them runs their slice better than I'd run all of it — and if one of them dies or gets unbearable, I'm swapping one small piece, not migrating a monolith.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When I'd throw this whole thing out
&lt;/h2&gt;

&lt;p&gt;The moment this site needs user accounts, per-user data, anything transactional, or writes that have to show up in the same second — I'd set up a real database and not feel bad about it. This setup works &lt;em&gt;because&lt;/em&gt; a personal blog is read-heavy, write-rare, and has exactly one author. Those are narrow conditions.&lt;/p&gt;

&lt;p&gt;But they describe most personal sites and content sites. And for those, the best backend is the one you stop thinking about after the first week. Mine has been a rebuild and an &lt;code&gt;INCR&lt;/code&gt; ever since, and I haven't thought about it once.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>opensource</category>
      <category>git</category>
    </item>
  </channel>
</rss>
