<?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: Saad Mehmood</title>
    <description>The latest articles on DEV Community by Saad Mehmood (@iamsaadmehmood).</description>
    <link>https://dev.to/iamsaadmehmood</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%2F1131557%2F278b365c-f1ec-4939-81f8-7ab477d2949e.jpeg</url>
      <title>DEV Community: Saad Mehmood</title>
      <link>https://dev.to/iamsaadmehmood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamsaadmehmood"/>
    <language>en</language>
    <item>
      <title>Next.js vs React: Why Next.js Is the Better Choice for Most Web Projects</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 21 May 2026 12:58:13 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/nextjs-vs-react-why-nextjs-is-the-better-choice-for-most-web-projects-3njp</link>
      <guid>https://dev.to/iamsaadmehmood/nextjs-vs-react-why-nextjs-is-the-better-choice-for-most-web-projects-3njp</guid>
      <description>&lt;p&gt;If you search "Next.js vs React," you'll see a lot of articles that treat them like two competing frameworks. That's the wrong starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js is built on React.&lt;/strong&gt; You're not choosing React &lt;em&gt;or&lt;/em&gt; Next.js. You're choosing between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;plain React project&lt;/strong&gt; (Vite, Create React App, or a custom SPA setup), and&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;React project with Next.js&lt;/strong&gt; as the framework on top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After 8+ years building web apps — from MVPs to production platforms — I default to &lt;strong&gt;Next.js&lt;/strong&gt; for almost every serious web project. Not because React is bad. React is excellent. But for real products, Next.js gives you a full stack of decisions already made — and those decisions are usually the right ones.&lt;/p&gt;

&lt;p&gt;Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. SEO and First Load Are Built In, Not Bolted On
&lt;/h2&gt;

&lt;p&gt;A plain React SPA typically ships a mostly empty HTML shell. The browser downloads JavaScript, React mounts, data is fetched, and &lt;em&gt;then&lt;/em&gt; the user sees content.&lt;/p&gt;

&lt;p&gt;That works for dashboards behind login. It is painful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;portfolios&lt;/li&gt;
&lt;li&gt;landing pages&lt;/li&gt;
&lt;li&gt;blogs&lt;/li&gt;
&lt;li&gt;marketing sites&lt;/li&gt;
&lt;li&gt;anything that needs Google, LinkedIn previews, or fast first paint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js solves this at the framework level with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server Components&lt;/strong&gt; (App Router) — render on the server by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static generation (SSG)&lt;/strong&gt; — pre-build pages at deploy time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side rendering (SSR)&lt;/strong&gt; — render per request when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming&lt;/strong&gt; — send HTML in chunks while slower parts load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a plain React app, you &lt;em&gt;can&lt;/em&gt; add SSR — but you're assembling tools yourself (custom Node server, hydration setup, routing, caching). With Next.js, it's the default path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real impact:&lt;/strong&gt; better Core Web Vitals, better crawlability, better social sharing previews — without a separate backend just to render HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Routing Is a Feature, Not a Dependency You Wire Up
&lt;/h2&gt;

&lt;p&gt;In a plain React project, routing usually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install &lt;code&gt;react-router&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;define routes manually&lt;/li&gt;
&lt;li&gt;handle layouts yourself&lt;/li&gt;
&lt;li&gt;figure out code splitting per route&lt;/li&gt;
&lt;li&gt;add loading and error states yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Next.js, the file system &lt;em&gt;is&lt;/em&gt; the router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  page.tsx          → /
  about/page.tsx    → /about
  blog/page.tsx     → /blog
  layout.tsx        → shared layout
  loading.tsx       → loading UI
  not-found.tsx     → 404 page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nested layouts, route groups, dynamic segments, and error boundaries are first-class. You spend less time on plumbing and more time on product.&lt;/p&gt;

&lt;p&gt;I've shipped multi-page sites (portfolio, blog, project pages) where adding a new route is literally creating a folder and a &lt;code&gt;page.tsx&lt;/code&gt;. That velocity matters on client work.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Full-Stack in One Repo (Without a Separate API Project)
&lt;/h2&gt;

&lt;p&gt;Plain React is frontend-only. Need an API? You spin up Express, Nest.js, or Firebase Functions in another repo — then deal with CORS, auth, deployment coordination, and env var duplication.&lt;/p&gt;

&lt;p&gt;Next.js gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route Handlers&lt;/strong&gt; (&lt;code&gt;app/api/.../route.ts&lt;/code&gt;) — REST endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server Actions&lt;/strong&gt; — mutations from the client without writing a separate API layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side data fetching&lt;/strong&gt; — call your DB or third-party APIs directly in Server Components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many apps — contact forms, CMS fetches, auth callbacks, webhooks, internal tools — you don't need a standalone backend on day one.&lt;/p&gt;

&lt;p&gt;Example use cases I've used Next.js API routes for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blog post fetching&lt;/li&gt;
&lt;li&gt;form submissions&lt;/li&gt;
&lt;li&gt;webhook receivers&lt;/li&gt;
&lt;li&gt;server-side token exchange (keeping secrets off the client)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One repo. One deploy. Less context switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Performance Optimizations You'd Otherwise Skip
&lt;/h2&gt;

&lt;p&gt;Next.js ships with optimizations that teams often postpone in plain React projects because "we'll add it later" (and never do):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Plain React&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automatic code splitting&lt;/td&gt;
&lt;td&gt;Manual setup&lt;/td&gt;
&lt;td&gt;Built in per route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image optimization&lt;/td&gt;
&lt;td&gt;You pick a library&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font optimization&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;&lt;code&gt;next/font&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script loading strategy&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Script /&amp;gt;&lt;/code&gt; component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle analysis&lt;/td&gt;
&lt;td&gt;Extra tooling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@next/bundle-analyzer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; component alone is worth it on content-heavy sites. Lazy loading, responsive sizes, modern formats (WebP/AVIF), and layout shift prevention — handled.&lt;/p&gt;

&lt;p&gt;On a portfolio or product site with screenshots and hero images, this is a measurable win without extra libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Better Developer Experience for Production Apps
&lt;/h2&gt;

&lt;p&gt;Things that feel small in a demo but matter in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables&lt;/strong&gt; — &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; vs server-only vars are clearly separated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleware&lt;/strong&gt; — auth checks, redirects, geo routing at the edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in metadata API&lt;/strong&gt; — SEO tags per page without &lt;code&gt;react-helmet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental Static Regeneration (ISR)&lt;/strong&gt; — update static pages without full rebuilds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment story&lt;/strong&gt; — Vercel is first-class, but Next.js also runs on Netlify, AWS, Docker, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I build client projects, I need predictable structure. Next.js gives every page the same conventions: where data lives, where loading states go, where errors go. New developers on the project ramp up faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The App Router Changed the Game (2024–2026)
&lt;/h2&gt;

&lt;p&gt;If you looked at Next.js years ago and thought "it's just SSR for React," look again.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;App Router&lt;/strong&gt; (now the default) gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Server Components&lt;/li&gt;
&lt;li&gt;Streaming and Suspense boundaries&lt;/li&gt;
&lt;li&gt;Colocated loading/error UI&lt;/li&gt;
&lt;li&gt;Server Actions for forms and mutations&lt;/li&gt;
&lt;li&gt;Partial Prerendering (where supported)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not "React with extra steps." It's a different — and better — default for web apps that need both interactivity and performance.&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;"use client"&lt;/code&gt; only where I need hooks, browser APIs, or heavy interactivity. Everything else stays on the server. Less JavaScript to the browser. Faster pages. Simpler mental model once you learn it.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Plain React Still Has a Place (Being Honest)
&lt;/h2&gt;

&lt;p&gt;Next.js is not magic. I still reach for plain React (usually Vite) when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app is a &lt;strong&gt;logged-in dashboard&lt;/strong&gt; with zero SEO needs&lt;/li&gt;
&lt;li&gt;it's an &lt;strong&gt;embedded widget&lt;/strong&gt; inside another site&lt;/li&gt;
&lt;li&gt;the team already has a &lt;strong&gt;separate backend&lt;/strong&gt; and wants a thin SPA frontend&lt;/li&gt;
&lt;li&gt;the scope is a &lt;strong&gt;small internal tool&lt;/strong&gt; that will never be public-facing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even then, I ask: &lt;em&gt;will this ever need SEO, public pages, or server-side data?&lt;/em&gt; If yes, I start with Next.js anyway. Migrating from SPA to SSR later is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side: What You Get on Day One
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Plain React (Vite/CRA):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Fast dev server
✅ Full control
❌ No SSR/SSG out of the box
❌ No file-based routing
❌ No API layer
❌ No image/font optimization
❌ SEO requires extra work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Next.js:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Everything React gives you
✅ SSR, SSG, ISR, streaming
✅ File-based routing + layouts
✅ API routes + Server Actions
✅ Image, font, script optimization
✅ Metadata and SEO built in
✅ Production conventions from day one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Default Decision Framework
&lt;/h2&gt;

&lt;p&gt;When a new web project comes in, I ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does this need to rank on Google or look good when shared?&lt;/strong&gt; → Next.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Will we need server-side logic (forms, auth, webhooks)?&lt;/strong&gt; → Next.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is this a multi-page product site?&lt;/strong&gt; → Next.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is this a private admin panel with no public pages?&lt;/strong&gt; → Plain React is fine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For me, that means &lt;strong&gt;Next.js wins roughly 80–90% of the time.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Take
&lt;/h2&gt;

&lt;p&gt;React gives you the UI layer. Next.js gives you the &lt;strong&gt;product layer&lt;/strong&gt; — routing, rendering strategy, API, optimization, and deployment patterns that every real web app eventually needs.&lt;/p&gt;

&lt;p&gt;Starting with plain React and adding those pieces one by one works. I've done it. But it's slower, easier to get wrong, and you end up rebuilding toward what Next.js already provides.&lt;/p&gt;

&lt;p&gt;If you're starting a new web project in 2026 and you're not sure which to pick: &lt;strong&gt;use Next.js with the App Router.&lt;/strong&gt; You'll move faster, ship better-performing pages, and spend your time on features — not infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; | &lt;a href="https://dev.to/iamsaadmehmood"&gt;Dev.to&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Mentoring Junior Developers: What Actually Works</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 21 May 2026 12:33:50 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/mentoring-junior-developers-what-actually-works-4ho5</link>
      <guid>https://dev.to/iamsaadmehmood/mentoring-junior-developers-what-actually-works-4ho5</guid>
      <description>&lt;p&gt;Mentoring juniors has taught me as much as it’s taught them. Here’s what actually helps—and what doesn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Explain the “Why,” Not Just the “How”
&lt;/h2&gt;

&lt;p&gt;“We do it this way because…” sticks better than “just do it like this.” When you share context (e.g. “we use this pattern so we can test without the API”), they start to generalize and make better decisions on their own. One sentence of why can save a lot of repeated corrections.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Code Review as Teaching
&lt;/h2&gt;

&lt;p&gt;PR feedback is a chance to teach. Instead of only “change this,” add “because…” or “an alternative is X, which is better when Y.” Point them to a doc or example when you have one. Over time, they’ll anticipate the same points and need fewer comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Pair When It’s Sticky
&lt;/h2&gt;

&lt;p&gt;When someone’s stuck for a while, pair for 30–60 minutes. Share screen, talk through the problem, and debug together. You unblock them and see where they get confused—useful for docs or future onboarding. Don’t take over; guide so they drive.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Give Them Ownership of a Small Area
&lt;/h2&gt;

&lt;p&gt;Assign a small feature, module, or bug area they own. They get to design, implement, and maintain it. They’ll ask questions and make mistakes—that’s how they learn. You stay available for review and unblocking, but they lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Normalize “I Don’t Know”
&lt;/h2&gt;

&lt;p&gt;Make it clear that nobody knows everything. Saying “I’m not sure, let’s look it up” or “good question, I’ll find out” models that learning is ongoing. That reduces the pressure to pretend they know and encourages asking questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Set Clear Expectations
&lt;/h2&gt;

&lt;p&gt;Spell out what “good” looks like: “We expect PRs to have tests for new logic,” “we comment complex bits,” “we update the README when we change setup.” When expectations are clear, they can self-check and improve without guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Don’t Fix Everything Yourself
&lt;/h2&gt;

&lt;p&gt;If you always jump in and fix their code, they don’t learn. Let them fix their bugs (with hints if needed). Step in when it’s blocking the team or when they’re truly stuck, but default to “what have you tried? what do you think we should do next?”&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Check In Regularly
&lt;/h2&gt;

&lt;p&gt;Short, regular check-ins (“how’s the task? any blockers?”) beat long, rare meetings. They get help when they need it, and you notice when they’re stuck or when scope is unclear.&lt;/p&gt;

&lt;p&gt;Mentoring well takes time, but it pays off: the team gets stronger, and you get a clearer picture of how your systems and docs work for someone new. The goal isn’t to create clones of you—it’s to help them think and ship with confidence.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mentorship</category>
      <category>career</category>
      <category>leadership</category>
      <category>learning</category>
    </item>
    <item>
      <title>What I Wish I'd Known as a Junior Full-Stack Developer</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 21 May 2026 12:29:53 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/what-i-wish-id-known-as-a-junior-full-stack-developer-4c70</link>
      <guid>https://dev.to/iamsaadmehmood/what-i-wish-id-known-as-a-junior-full-stack-developer-4c70</guid>
      <description>&lt;p&gt;Looking back at my first years as a full-stack developer, here’s what would have saved me time, stress, and a few mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. You Don’t Have to Know Everything
&lt;/h2&gt;

&lt;p&gt;It’s normal to feel overwhelmed. Senior devs don’t know every framework or language—they know how to find answers, read docs, and debug. Focus on one stack first, get good at it, then expand. “I don’t know, but I’ll find out” is a valid and professional answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ask Questions Early
&lt;/h2&gt;

&lt;p&gt;Asking “how does this work?” or “what’s the expected behavior?” early is better than guessing and building the wrong thing. Write down answers so you don’t ask the same thing twice. Good teams expect questions; they’d rather clarify than rework.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Estimates Are Hard—That’s Okay
&lt;/h2&gt;

&lt;p&gt;Estimates will be wrong at first. Break tasks into small pieces (a few hours each), add buffer for unknowns, and say “I’ll need to spike this before I can give a number” when it’s genuinely unclear. Updating the team when you learn something new is part of the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Code Review Feedback Is About the Code, Not You
&lt;/h2&gt;

&lt;p&gt;Critique on your PR is about the change, not your worth. Use it to learn patterns and constraints. Ask “what would you do instead?” when you don’t understand. Over time you’ll internalize the team’s style and get fewer comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tests and Documentation Pay Off Later
&lt;/h2&gt;

&lt;p&gt;Writing a test or a short doc feels slow until something breaks or someone (including you) forgets how it works. Even a few tests for critical paths and a README that explains how to run the app will save hours later. Start small and add more as you go.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Production Is Different From Local
&lt;/h2&gt;

&lt;p&gt;Things that work on your machine can fail in production: env vars, timeouts, scale, caching. Learn how the app is deployed, where logs go, and how to debug production issues (with care). That skill is as important as writing features.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Communication Is Part of the Job
&lt;/h2&gt;

&lt;p&gt;Updates, clear messages, and “I’m blocked on X” matter as much as code. A short message or standup update keeps everyone aligned. Don’t disappear for days on a hard problem without saying so.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. It’s Okay to Rest
&lt;/h2&gt;

&lt;p&gt;Burnout doesn’t help you or the project. Take breaks, protect focus time, and don’t feel guilty for not coding 24/7. Sustainable pace beats hero mode.&lt;/p&gt;

&lt;p&gt;If you’re early in your career: you’re not supposed to have it all figured out. Learn in public, ask questions, and get a little better every week. That’s enough.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>fullstack</category>
      <category>advice</category>
      <category>programming</category>
    </item>
    <item>
      <title>PostgreSQL vs MongoDB: When I Pick Which for the Backend</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 09 Apr 2026 14:43:08 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/postgresql-vs-mongodb-when-i-pick-which-for-the-backend-kei</link>
      <guid>https://dev.to/iamsaadmehmood/postgresql-vs-mongodb-when-i-pick-which-for-the-backend-kei</guid>
      <description>&lt;p&gt;PostgreSQL and MongoDB are both capable. The choice usually comes down to data shape, query patterns, team experience, and how much you care about schema and relations. Here’s how I decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Gives You
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; — Relational model: tables, rows, foreign keys, ACID transactions, and SQL. Strong for structured data, joins, and reporting. Schema is explicit (migrations); you get constraints, indexes, and a mature ecosystem (PostGIS, extensions). Fits when your domain has clear entities and relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; — Document model: collections and JSON-like documents. Flexible schema; you can evolve documents over time without formal migrations. Good for nested, variable-shaped data (e.g. profiles with optional fields, event payloads). Denormalization is common; you design documents for how you read them. Horizontal scaling and sharding are first-class.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I Choose PostgreSQL
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured domain with clear relations&lt;/strong&gt; — Users, orders, products, roles, permissions. When the data is naturally tabular and you need joins (e.g. “orders with customer and line items”), PostgreSQL is a natural fit. Normalized schema reduces duplication and keeps consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactions and consistency&lt;/strong&gt; — When a single operation must update several rows or tables atomically (e.g. debit one account, credit another), relational DBs and transactions are what you want. PostgreSQL’s transaction model is well understood and robust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting and analytics&lt;/strong&gt; — SQL is built for aggregations, filters, and reporting. When the product or stakeholders need “list X by Y, sum Z, group by W,” PostgreSQL (and tools that speak SQL) make this straightforward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team knows SQL and relational design&lt;/strong&gt; — Many backends and agencies are comfortable with tables, migrations, and ORMs (Prisma, TypeORM, etc.). PostgreSQL fits that skillset and has great tooling (Supabase, Neon, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nest.js or type-first backends&lt;/strong&gt; — Nest.js and TypeScript pair well with Prisma/TypeORM and PostgreSQL. You get types from schema, migrations, and a clear data model. I default to PostgreSQL for new Nest.js APIs unless the data is clearly document-shaped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When I Choose MongoDB
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document-shaped or variable structure&lt;/strong&gt; — Content with optional fields, user-generated forms, or events where each document can have different attributes. When the schema varies by document or changes often, MongoDB’s flexibility can reduce migration churn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nested data read as a unit&lt;/strong&gt; — When you often load “one thing and everything inside it” (e.g. a project with tasks and comments embedded or in sub-docs), documents can match that read pattern. You avoid joins at the cost of some duplication and careful design to avoid huge documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team prefers document APIs&lt;/strong&gt; — Some teams are more productive with documents and aggregation pipelines than with SQL. When that’s the case and the data fits, MongoDB is a good fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already in the stack&lt;/strong&gt; — If the project or client already uses MongoDB and the data is working, we don’t switch to Postgres without a strong reason (e.g. need for complex joins or reporting that’s painful in Mongo).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The “Hybrid” Option
&lt;/h2&gt;

&lt;p&gt;Sometimes we use both: PostgreSQL for core transactional data (users, billing, orders) and MongoDB (or another store) for logs, events, or flexible content. Not every app needs that—but when one part of the system is clearly relational and another is clearly document/event-based, splitting can make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Don’t Do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don’t pick MongoDB to avoid schema&lt;/strong&gt; — You still need to think about indexes, document shape, and how you’ll query. “Schema-less” doesn’t mean “no design.” Bad document design leads to slow queries and messy code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t pick PostgreSQL only because “relational is correct”&lt;/strong&gt; — For highly variable or nested data that’s always read as a whole, documents can be simpler. Choose by data shape and access patterns, not ideology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t migrate without a clear problem&lt;/strong&gt; — If the current database is working, we don’t switch to the other just to try it. We switch when we hit real limits (e.g. reporting in Mongo is painful, or we need strict relations and transactions in a doc store).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: I default to PostgreSQL for most backends—structured data, relations, transactions, and reporting. I choose MongoDB when the data is document-shaped, variable, or read as a unit, and when the team and stack align with it. Both are production-ready; pick by domain shape, query patterns, and team.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>mongodb</category>
      <category>backend</category>
    </item>
    <item>
      <title>State Management in 2026: Redux vs Context vs TanStack Query</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 09 Apr 2026 10:01:04 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/state-management-in-2026-redux-vs-context-vs-tanstack-query-1b0b</link>
      <guid>https://dev.to/iamsaadmehmood/state-management-in-2026-redux-vs-context-vs-tanstack-query-1b0b</guid>
      <description>&lt;p&gt;State management in React (and React Native) is clearer than ever in 2026. Here’s how I use Redux, Context, and TanStack Query now, and when I pick each.&lt;/p&gt;

&lt;h2&gt;
  
  
  TanStack Query — Server State First
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use for:&lt;/strong&gt; Anything that comes from the server: API data, lists, details, pagination, and background refetching.&lt;/p&gt;

&lt;p&gt;TanStack Query handles loading, error, caching, refetching, and invalidation. I keep server data in the query cache and avoid duplicating it in Redux or Context unless there is a strong product reason (for example, special offline behavior).&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="kd"&gt;const&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;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I use it in 2026:&lt;/strong&gt; For nearly all async/server data. It reduces boilerplate, keeps fetching logic consistent, and scales well in both React and React Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context — Global UI and App Config
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use for:&lt;/strong&gt; Theme, locale, auth session reference, feature flags, and dependency providers.&lt;/p&gt;

&lt;p&gt;Context is best for small, stable, read-heavy global values. Keep provider values minimal and memoized so you avoid unnecessary tree-wide re-renders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When I use it in 2026:&lt;/strong&gt; When state is simple and widely shared. If updates become frequent or logic gets complex, I move to a dedicated store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redux Toolkit — Complex Client State
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use for:&lt;/strong&gt; Complex client state: multi-step flows, editor-like UIs, undo/redo, persistence, and strict state transitions.&lt;/p&gt;

&lt;p&gt;Redux Toolkit keeps Redux practical with slices and solid conventions. I use it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app has a lot of interdependent client state (wizards, dashboards, editors).&lt;/li&gt;
&lt;li&gt;I need deterministic flows and explicit actions for debugging.&lt;/li&gt;
&lt;li&gt;I need persistence/rehydration and predictable transitions across screens.&lt;/li&gt;
&lt;li&gt;The team is already comfortable with Redux and the project size justifies it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I avoid it:&lt;/strong&gt; For simple apps or when most data is server-driven. In those cases, TanStack Query + Context + local state is usually enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick 2026 Decision Rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server state&lt;/strong&gt; (API-backed) -&amp;gt; TanStack Query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global app config&lt;/strong&gt; (theme/locale/session ref) -&amp;gt; Context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex client workflows&lt;/strong&gt; (wizards/editors/undo) -&amp;gt; Redux Toolkit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component-only UI state&lt;/strong&gt; (modal open/input value) -&amp;gt; &lt;code&gt;useState&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How They Coexist in Real Projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt; for server/cache state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; for app-wide configuration and providers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redux Toolkit&lt;/strong&gt; only where client state is complex enough to justify it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;useState&lt;/code&gt;&lt;/strong&gt; for local UI concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I avoid “one tool for everything.” In 2026, the cleanest setups separate state by purpose: server data in query cache, shared config in Context, and complex client workflows in a store. That split keeps code easier to debug and faster to scale.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactnative</category>
      <category>redux</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Next.js API Routes: Patterns That Scale</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Mon, 06 Apr 2026 05:36:29 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/nextjs-api-routes-patterns-that-scale-2edj</link>
      <guid>https://dev.to/iamsaadmehmood/nextjs-api-routes-patterns-that-scale-2edj</guid>
      <description>&lt;p&gt;Next.js API routes are quick to add but easy to outgrow. Here are patterns I use to keep them maintainable as the app grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Centralized Error Handling
&lt;/h2&gt;

&lt;p&gt;Don’t repeat try/catch and status codes in every route. Use a small wrapper or middleware:&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/api-handler.ts (concept)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withErrorHandling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal Server Error&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it so each route focuses on success logic; errors are handled in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Validate Input
&lt;/h2&gt;

&lt;p&gt;Validate query and body with Zod (or Yup). Return 400 with clear messages instead of blowing up later:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;perPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&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;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;perPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea for &lt;code&gt;POST&lt;/code&gt;/&lt;code&gt;PUT&lt;/code&gt; body: parse JSON, then validate with Zod.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use the Right HTTP Methods and Status Codes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt; — read; idempotent. Use 200 (with body) or 204 (no body).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; — create. Return 201 and &lt;code&gt;Location&lt;/code&gt; header when you create a resource.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT&lt;/code&gt;/&lt;code&gt;PATCH&lt;/code&gt; — update. 200 with body or 204.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE&lt;/code&gt; — 204 on success.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Return 400 for bad input, 401 for unauthenticated, 403 for forbidden, 404 when the resource doesn’t exist, 429 when rate-limited.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Cache Where It Makes Sense
&lt;/h2&gt;

&lt;p&gt;For public or semi-static data, use Next.js caching:&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;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ISR: revalidate every 60 seconds&lt;/span&gt;
&lt;span class="c1"&gt;// or&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;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-static&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;For user-specific or private data, avoid caching or use short revalidation and proper auth checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Move Logic Out of the Route File
&lt;/h2&gt;

&lt;p&gt;Keep &lt;code&gt;route.ts&lt;/code&gt; thin: parse request, validate, call a service, return response. Put business logic in &lt;code&gt;lib/&lt;/code&gt; or &lt;code&gt;services/&lt;/code&gt; so you can test and reuse it. Same for DB access: use a repo or client in &lt;code&gt;lib/&lt;/code&gt;, not raw queries in the route.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Auth and Rate Limiting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check auth (e.g. session or JWT) in middleware or at the start of the handler. Return 401/403 early.&lt;/li&gt;
&lt;li&gt;Add rate limiting (e.g. Upstash Redis or a simple in-memory store) on public or sensitive endpoints so one client can’t overwhelm the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns keep API routes readable and consistent as you add more endpoints and team members.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>api</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building Real-Time Features with Firebase (or Supabase) in React Native</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Mon, 06 Apr 2026 05:34:52 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/building-real-time-features-with-firebase-or-supabase-in-react-native-1cbg</link>
      <guid>https://dev.to/iamsaadmehmood/building-real-time-features-with-firebase-or-supabase-in-react-native-1cbg</guid>
      <description>&lt;p&gt;Real-time updates—chat, live data, presence—are common in mobile apps. Here’s how I approach them with Firebase or Supabase in React Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Firebase or Supabase?
&lt;/h2&gt;

&lt;p&gt;Both give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Realtime listeners&lt;/strong&gt; — Subscribe to a path or query; get updates when data changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt; — So you can secure data per user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt; — Firebase has built-in persistence; Supabase can work with local SQLite or similar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use Firebase when the team already uses it or needs other Google services; Supabase when we want Postgres and a more SQL-friendly API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase Firestore: Realtime Listeners
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subscribe to a collection or query&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;firestore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chats&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;doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&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;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle errors, show user-friendly message&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Cleanup on unmount&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;limit()&lt;/code&gt; and paginate; don’t load entire collections.&lt;/li&gt;
&lt;li&gt;Enable offline persistence so the app works without network.&lt;/li&gt;
&lt;li&gt;Secure with Firestore rules: validate &lt;code&gt;request.auth&lt;/code&gt; and restrict reads/writes by user or role.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Supabase: Realtime with Postgres
&lt;/h2&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;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres_changes&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="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`chat_id=eq.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chatId&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;span class="p"&gt;},&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get realtime from Postgres changes (insert/update/delete). Enable Realtime on the tables you need in the Supabase dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth First
&lt;/h2&gt;

&lt;p&gt;Both platforms tie data to users. Ensure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User is signed in before subscribing.&lt;/li&gt;
&lt;li&gt;Listeners are scoped to the current user (e.g. &lt;code&gt;userId&lt;/code&gt; in the path or filter).&lt;/li&gt;
&lt;li&gt;On auth state change, unsubscribe from old listeners and subscribe with the new user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Offline and UX
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Show a “connecting” or “offline” indicator when the client is disconnected.&lt;/li&gt;
&lt;li&gt;Queue critical actions (e.g. send message) and retry when back online.&lt;/li&gt;
&lt;li&gt;For Firebase, use persistence; for Supabase, consider local caching (e.g. React Query + persistence) for a smooth offline experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-time doesn’t have to mean “complex.” Start with one collection or table, get the listener and cleanup right, then expand from there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>firebase</category>
      <category>supabase</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Improve React Native App Security: 10 Practices to Evaluate Your Project</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Wed, 11 Mar 2026 15:31:58 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/improve-react-native-app-security-10-practices-to-evaluate-your-project-2a89</link>
      <guid>https://dev.to/iamsaadmehmood/improve-react-native-app-security-10-practices-to-evaluate-your-project-2a89</guid>
      <description>&lt;p&gt;After working with React Native for several years, I've put together key security practices you can use to evaluate your project. Apps can't be 100% secure, but you can make hacking difficult and expensive. Here's a checklist that covers the main attack surfaces and what to do about them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. SSL Pinning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; API calls can be intercepted with tools like Burp Suite or Charles Proxy, exposing request payloads and responses—including tokens and sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Implement certificate pinning so the app only trusts your server's certificate (or public key). That way, even if someone installs a custom CA, man-in-the-middle traffic won't be accepted.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;react-native-ssl-pinning&lt;/strong&gt; (or a similar library) to pin your API domain. Pin the certificate or public key hashes and fail closed if they don't match. Remember to update pins before cert rotation so the app doesn't break.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Reverse Engineering
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; APK (Android) and IPA (iOS) can be decompiled using tools like APKTool, jadx, or Hopper, revealing business logic, API shapes, and sometimes secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Enable &lt;strong&gt;ProGuard&lt;/strong&gt; or &lt;strong&gt;R8&lt;/strong&gt; in release builds to obfuscate and shrink code. Remove debug symbols and strip unnecessary metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Strip symbols in release builds (Xcode does this by default when you build for release). Avoid storing secrets or critical logic in the client when possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General:&lt;/strong&gt; Move sensitive logic and decisions to the backend. Treat the client as untrusted; validate and authorize on the server.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Secure Local Storage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; &lt;code&gt;AsyncStorage&lt;/code&gt; (and similar unencrypted storage) can be read on rooted (Android) or jailbroken (iOS) devices, leaking tokens and other sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use platform-backed secure storage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Keychain (encrypted, tied to the device and optionally to biometrics).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Keystore (hardware-backed when available).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Libraries like &lt;strong&gt;react-native-keychain&lt;/strong&gt; or &lt;strong&gt;react-native-encrypted-storage&lt;/strong&gt; expose a single API for both platforms. Store tokens, refresh tokens, and other secrets there—not in AsyncStorage or plain files.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Screenshot / Screen Recording
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Sensitive screens (OTP, payment forms, private content) can be captured via screenshots or screen recording and leak outside the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Prevent screenshots on sensitive screens using &lt;code&gt;FLAG_SECURE&lt;/code&gt; (or packages like &lt;strong&gt;react-native-screenshot-prevent&lt;/strong&gt;) so the system doesn't allow screenshots or recording of that window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; Detect screen recording using &lt;code&gt;UIScreen.isCaptured&lt;/code&gt; and hide or blur sensitive views (e.g. OTP field, card number) while recording is active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;react-native-screen-capture-secure&lt;/strong&gt; can help detect and block screen capture on both platforms. Use it on screens that show PII, payment details, or one-time codes.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Root / Jailbreak Detection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Rooted (Android) or jailbroken (iOS) devices can bypass app protections, read memory or storage, and tamper with the runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Detect compromised devices using a library like &lt;strong&gt;react-native-jail-monkey&lt;/strong&gt; (or similar). Use the result to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block access entirely for high-risk apps (e.g. banking), or
&lt;/li&gt;
&lt;li&gt;Limit features / show a warning and log the event for analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Balance security with UX: some users root for legitimate reasons, so define a clear policy (block vs. warn vs. allow with reduced functionality).&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Code Obfuscation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The JS bundle and native code can be inspected to extract API keys, endpoints, or business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript:&lt;/strong&gt; Use &lt;strong&gt;javascript-obfuscator&lt;/strong&gt; (or a Metro/bundler plugin that integrates it) to obfuscate the release bundle. Don't rely on obfuscation to protect real secrets—move those to the backend or use short-lived tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native:&lt;/strong&gt; On Android, ProGuard/R8 obfuscate Java/Kotlin. On iOS, strip symbols and avoid embedding secrets in the binary. Obfuscation raises the bar; it doesn't make reverse engineering impossible.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. API Security
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Exposed API keys, long-lived tokens, or unvalidated requests can be abused by attackers who extract them from the app or intercept traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;short-lived tokens&lt;/strong&gt; (e.g. JWT with short expiry) and refresh them via a secure, authenticated flow. Don't put long-lived API keys in the client if they can be moved to the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate every request on the backend&lt;/strong&gt;—auth, authorization, and input validation. Never trust the client.&lt;/li&gt;
&lt;li&gt;Optionally &lt;strong&gt;sign payloads&lt;/strong&gt; (e.g. HMAC) for critical operations so the server can detect tampering.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Runtime Integrity / Tamper Detection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The APK or IPA can be modified (re-signed, patched) to bypass checks, remove root detection, or inject code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Verify app signature (and optionally integrity) at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; Check that the app is signed with your release keystore (e.g. compare signature to a known value).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS:&lt;/strong&gt; The system enforces code signing; you can add checks for jailbreak and for tampering with key resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the app appears modified or running in an unexpected environment, block or limit functionality and report. This makes casual tampering harder; determined attackers may still find ways around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Secure Deep Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Malicious apps or links can trigger your deep links with crafted parameters and try to open sensitive screens (e.g. password reset, payment) without proper auth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate all parameters&lt;/strong&gt;—type, format, and allowed values. Reject or sanitize anything suspicious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate the user&lt;/strong&gt; before opening sensitive screens. Don't rely on the link alone to grant access; verify session or token on the server if needed.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;verified app links&lt;/strong&gt; (Android App Links / iOS Universal Links) so only your domain can open your app, reducing phishing via custom URL schemes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. CodePush / OTA Updates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Over-the-air updates (e.g. CodePush, EAS Update) can deliver new JS (and sometimes assets). If the update channel or signing isn't enforced, an attacker could push malicious code to users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only allow signed updates&lt;/strong&gt; from your own backend or the official CodePush/EAS endpoint. Verify the signature or token before applying an update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the source&lt;/strong&gt; of the update (e.g. same app ID, same deployment key) and use HTTPS and certificate pinning for the update endpoint.&lt;/li&gt;
&lt;li&gt;Restrict who can publish updates and audit release history. Treat OTA as a privileged pipeline.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;th&gt;Key mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SSL&lt;/td&gt;
&lt;td&gt;MITM, traffic interception&lt;/td&gt;
&lt;td&gt;Certificate/public key pinning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Reverse engineering&lt;/td&gt;
&lt;td&gt;Logic and secrets exposed&lt;/td&gt;
&lt;td&gt;ProGuard/R8, strip symbols, move logic to backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Local storage&lt;/td&gt;
&lt;td&gt;Token/data leak on compromised devices&lt;/td&gt;
&lt;td&gt;Keychain / Keystore (react-native-keychain, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Screenshot/recording&lt;/td&gt;
&lt;td&gt;Sensitive UI captured&lt;/td&gt;
&lt;td&gt;FLAG_SECURE, UIScreen.isCaptured, blur/hide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Root/jailbreak&lt;/td&gt;
&lt;td&gt;Bypass of app protections&lt;/td&gt;
&lt;td&gt;Detect and block or limit (e.g. react-native-jail-monkey)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Code obfuscation&lt;/td&gt;
&lt;td&gt;Keys and logic extracted&lt;/td&gt;
&lt;td&gt;JS obfuscator, ProGuard/R8; no secrets in client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Abuse of keys and endpoints&lt;/td&gt;
&lt;td&gt;Short-lived tokens, server-side validation, signing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Runtime integrity&lt;/td&gt;
&lt;td&gt;Tampered app bypasses checks&lt;/td&gt;
&lt;td&gt;Signature verification, tamper detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Deep links&lt;/td&gt;
&lt;td&gt;Unauthorized access to screens&lt;/td&gt;
&lt;td&gt;Validate params, authenticate user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;OTA updates&lt;/td&gt;
&lt;td&gt;Malicious update delivery&lt;/td&gt;
&lt;td&gt;Signed updates only, verify source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use this list to review your React Native app and prioritize the items that matter most for your data and threat model. If you've got practices that have worked well for you, share them in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; | &lt;a href="https://dev.to/iamsaadmehmood"&gt;Dev.to&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>security</category>
      <category>mobile</category>
      <category>ios</category>
    </item>
    <item>
      <title>React Native Performance: What I Measure and Fix First</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 17 Feb 2026 09:39:10 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/react-native-performance-what-i-measure-and-fix-first-3279</link>
      <guid>https://dev.to/iamsaadmehmood/react-native-performance-what-i-measure-and-fix-first-3279</guid>
      <description>&lt;p&gt;When a React Native app feels slow, it’s easy to guess. Here’s what I measure first and the fixes I reach for most often.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Startup time&lt;/strong&gt; — Time from tap to first meaningful paint (e.g. home screen visible). I use Flipper, React Native’s built-in perf tools, or a simple timestamp in native code. I compare before/after changes (e.g. new libs, more initial data) so we don’t regress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frame rate (FPS)&lt;/strong&gt; — Especially on scroll and animation. React Native’s “Show Perf Monitor” or Flipper shows JS and UI thread FPS. Drops below 60 (or 120 on capable devices) mean jank. I note which screen or list causes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List scroll&lt;/strong&gt; — Long lists are a common bottleneck. I check: Are we using &lt;code&gt;FlatList&lt;/code&gt; (or similar)? Are we rendering too many items or heavy components per row? Are we doing work on the JS thread during scroll?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; — I watch for leaks (memory growing over time) and big allocations. Flipper or Xcode/Android Studio profilers show what’s growing. Often it’s images, caches, or listeners not cleaned up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundle size&lt;/strong&gt; — Large JS bundles slow startup. I check the output of the build (e.g. &lt;code&gt;npx react-native bundle --dev false&lt;/code&gt; and inspect size). I look for heavy dependencies that could be lazy-loaded or replaced.&lt;/p&gt;

&lt;p&gt;I don’t optimize everything—I focus on startup and the screens users hit most (e.g. home, main list). Fix the biggest wins first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline per release&lt;/strong&gt; — Track a small set of numbers per release (e.g. startup time, list FPS on the main feed, memory after 5 minutes). Store them in a doc or CI. When a new feature lands, compare against the baseline so regressions are obvious instead of “it feels slower.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes I Reach For First
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lists&lt;/strong&gt; — Use &lt;code&gt;FlatList&lt;/code&gt; (or &lt;code&gt;FlashList&lt;/code&gt; if we need more throughput). Implement &lt;code&gt;getItemLayout&lt;/code&gt; when item height is fixed so we skip measurement. Use &lt;code&gt;windowSize&lt;/code&gt; and &lt;code&gt;maxToRenderPerBatch&lt;/code&gt; to limit how many items are in the tree. Memoize list item components so they don’t re-render on every scroll tick. Avoid inline functions and object literals in &lt;code&gt;renderItem&lt;/code&gt;; pass stable props.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images&lt;/strong&gt; — Use &lt;code&gt;resizeMode&lt;/code&gt; appropriately. Serve correctly sized images from the backend or use a CDN that resizes. Consider &lt;code&gt;react-native-fast-image&lt;/code&gt; for caching if the default cache isn’t enough. Lazy-load off-screen images in lists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Re-renders&lt;/strong&gt; — Find unnecessary re-renders with React DevTools Profiler or “Highlight updates.” Often the fix is: memoize components (&lt;code&gt;React.memo&lt;/code&gt;), stabilize callbacks (&lt;code&gt;useCallback&lt;/code&gt;) and objects/arrays (&lt;code&gt;useMemo&lt;/code&gt;), or lift state so only the part that needs to re-render does. Avoid setting state in render or in effects that run too often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JS thread&lt;/strong&gt; — Move heavy work off the JS thread: use native modules for heavy computation, or do work in batches (e.g. &lt;code&gt;InteractionManager.runAfterInteractions&lt;/code&gt;). Don’t do large sync operations on the main JS thread during startup or scroll.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup&lt;/strong&gt; — Reduce initial JS: lazy-load screens (e.g. React.lazy + code splitting where supported), defer non-critical imports, and trim dependencies. Use Hermes if you’re not already; it often improves startup and memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleanup&lt;/strong&gt; — Unsubscribe from listeners, clear timers, and cancel requests in &lt;code&gt;useEffect&lt;/code&gt; cleanup. Remove event listeners and close connections so we don’t leak memory or keep unnecessary work running.&lt;/p&gt;

&lt;p&gt;I fix one area at a time, measure again, and repeat. Small, verified improvements add up; random “optimizations” without measurement often don’t.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>performance</category>
      <category>mobile</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From MVP to Production: A React Native Checklist</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Tue, 17 Feb 2026 09:18:09 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/from-mvp-to-production-a-react-native-checklist-5982</link>
      <guid>https://dev.to/iamsaadmehmood/from-mvp-to-production-a-react-native-checklist-5982</guid>
      <description>&lt;p&gt;You've built the features. The app works on your machine. Now what?&lt;/p&gt;

&lt;p&gt;The gap between "it works on my machine" and "it's live in the store and we can maintain it" is where a lot of projects get stuck—or ship anyway and pay for it later with broken builds, wrong environments, and "why is staging hitting production?" incidents. After shipping multiple React Native apps to the stores (including large-scale event apps), I've learned that a small set of habits and checks makes that transition predictable instead of painful.&lt;/p&gt;

&lt;p&gt;This post is the checklist I use for every new app and the one I recommend when joining a project that's about to go to production. It's not exhaustive—you'll add items for your stack and compliance needs—but it's the baseline that keeps builds, environments, and releases under control. Use it as a starting point and tighten each area as the product and team grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment &amp;amp; Config
&lt;/h2&gt;

&lt;p&gt;Getting environment and build variants right early avoids the classic mistakes: hardcoded API URLs, staging builds accidentally talking to production, and "it worked in debug but not in release." Spend a little time here and the rest of the pipeline stays sane.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Environment variables&lt;/strong&gt; — Use &lt;code&gt;react-native-config&lt;/code&gt; or Expo's env scheme so API URLs, feature flags, and keys live outside the codebase. Never hardcode them. Have separate configs for dev, staging, and prod so each build talks to the right backend and you can change URLs without a new app release.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Build variants&lt;/strong&gt; — Android: product flavors. iOS: schemes + configurations. Ensure each variant points to the right API and config (see below).&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Versioning&lt;/strong&gt; — Bump &lt;code&gt;versionCode&lt;/code&gt; (Android) and &lt;code&gt;CFBundleShortVersionString&lt;/code&gt; / &lt;code&gt;CFBundleVersion&lt;/code&gt; (iOS) for every release. Store submissions reject duplicate version numbers, and users (and crash reports) need to know which build they're on. Consider automating bumps in CI so you never ship the same version twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product flavors (Android) and schemes (iOS) — Dev, Staging, Production
&lt;/h3&gt;

&lt;p&gt;Use separate build variants so you can install dev, staging, and production on the same device and keep API/env configs isolated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android — Product flavors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;android/app/build.gradle&lt;/code&gt;, define flavors and wire them to env (e.g. &lt;code&gt;react-native-config&lt;/code&gt; or &lt;code&gt;BuildConfig&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flavorDimensions&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
    &lt;span class="n"&gt;productFlavors&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".dev"&lt;/span&gt;
            &lt;span class="n"&gt;versionNameSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-dev"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp Dev"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api-dev.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;staging&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;applicationIdSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".staging"&lt;/span&gt;
            &lt;span class="n"&gt;versionNameSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-staging"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp Staging"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api-staging.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt;
            &lt;span class="n"&gt;resValue&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"app_name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"MyApp"&lt;/span&gt;
            &lt;span class="n"&gt;buildConfigField&lt;/span&gt; &lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"API_BASE_URL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"\"https://api.example.com\""&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dev&lt;/strong&gt; — &lt;code&gt;.dev&lt;/code&gt; suffix so it installs alongside staging/prod. Use dev API and debug-friendly app name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;staging&lt;/strong&gt; — &lt;code&gt;.staging&lt;/code&gt; suffix, staging API. Use for QA and pre-release testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;production&lt;/strong&gt; — No suffix; this is the store build. Production API only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build with: &lt;code&gt;npx react-native run-android --variant=devDebug&lt;/code&gt; (or &lt;code&gt;stagingRelease&lt;/code&gt;, &lt;code&gt;productionRelease&lt;/code&gt;, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS — Schemes and configurations&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configurations&lt;/strong&gt; — In Xcode, duplicate &lt;strong&gt;Debug&lt;/strong&gt; and &lt;strong&gt;Release&lt;/strong&gt; (e.g. &lt;strong&gt;Debug-Dev&lt;/strong&gt;, &lt;strong&gt;Release-Staging&lt;/strong&gt;, &lt;strong&gt;Release-Production&lt;/strong&gt;) or use xcconfig files that set &lt;code&gt;API_BASE_URL&lt;/code&gt; and other env per configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schemes&lt;/strong&gt; — Create schemes &lt;strong&gt;Dev&lt;/strong&gt;, &lt;strong&gt;Staging&lt;/strong&gt;, &lt;strong&gt;Production&lt;/strong&gt;. In each scheme, set the &lt;strong&gt;Run&lt;/strong&gt; and &lt;strong&gt;Archive&lt;/strong&gt; actions to use the right configuration (e.g. Dev → Debug-Dev; Staging → Release-Staging; Production → Release).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle ID suffix&lt;/strong&gt; — In the project’s Build Settings (or per configuration), set &lt;strong&gt;Product Bundle Identifier&lt;/strong&gt; so dev/staging have a suffix (e.g. &lt;code&gt;com.yourapp.dev&lt;/code&gt;, &lt;code&gt;com.yourapp.staging&lt;/code&gt;). That lets you install dev, staging, and production side by side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display name&lt;/strong&gt; — Set &lt;strong&gt;Display Name&lt;/strong&gt; per configuration (e.g. “MyApp Dev”, “MyApp Staging”) so you can tell them apart on the home screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run with: &lt;strong&gt;Product → Scheme → Dev&lt;/strong&gt; (or Staging/Production), then &lt;strong&gt;Run&lt;/strong&gt; or &lt;strong&gt;Archive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you use EAS and &lt;code&gt;app.config.js&lt;/code&gt;, use &lt;strong&gt;environment&lt;/strong&gt; (or &lt;code&gt;APP_ENV&lt;/code&gt;) and different &lt;code&gt;extra&lt;/code&gt; / env in &lt;code&gt;eas.json&lt;/code&gt; profiles (e.g. &lt;code&gt;development&lt;/code&gt;, &lt;code&gt;preview&lt;/code&gt;, &lt;code&gt;production&lt;/code&gt;) so each build gets the correct API URL and app name. You can still use dev-client builds for dev/staging and production builds for the store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing (Minimum Viable)
&lt;/h2&gt;

&lt;p&gt;You don't need 100% coverage on day one—but you do need a safety net for the paths that matter. The goal here is to catch regressions before they reach users and to validate the build you're actually going to submit, not just the debug one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Critical flows&lt;/strong&gt; — At least one E2E or integration test for login/signup and the main user journey. Detox or Maestro can run in CI.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;No obvious regressions&lt;/strong&gt; — Manual test on at least one physical device per platform before release.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Release build&lt;/strong&gt; — Test the exact build you’ll submit (not just debug). ProGuard/minification can hide bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;p&gt;Store builds should come from a repeatable pipeline, not from someone's laptop. That means builds run in the cloud, signing is managed in one place, and submission to TestFlight or Play is a step in the pipeline—not a manual upload after "it built on my machine."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Builds in the cloud&lt;/strong&gt; — Use EAS Build, Bitrise, GitHub Actions, or similar so every build runs in the same environment. No "it works on my machine" for store builds; if the pipeline passes, the build is reproducible and auditable.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Signing&lt;/strong&gt; — Android: keystore stored securely (e.g. in CI secrets or a secrets manager), never in the repo. iOS: certificates and provisioning managed in Apple Developer and wired through EAS or Fastlane so the build step doesn't depend on a single Mac or keychain.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Automated submit&lt;/strong&gt; — After a successful build, automatically submit to TestFlight and Play Internal Testing (e.g. via Fastlane or EAS Submit). Optionally, trigger production submission on a specific tag or branch so releases are traceable and consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Store &amp;amp; Compliance
&lt;/h2&gt;

&lt;p&gt;Stores and regulators care about privacy, permissions, and clear communication. Getting this right up front avoids rejections and builds trust with users.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Privacy&lt;/strong&gt; — Provide a privacy policy URL and accurate data collection disclosure in App Store Connect and Play Console. If you use analytics, crash reporting, or third-party SDKs that collect data, say so. Vague or missing disclosures can lead to rejection or legal risk.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Permissions&lt;/strong&gt; — Request only the permissions you actually need, and explain why in permission rationale strings (Android) and Info.plist usage descriptions (iOS). Users are more likely to grant access when they understand the reason; stores may reject apps that ask for broad permissions without justification.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Store assets&lt;/strong&gt; — Screenshots, description, and keywords affect discoverability and conversion. Invest in clear copy and representative screenshots. If you care about install rates, consider A/B testing store assets (where the platform allows) to see what resonates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;Once the app is live, you need to know when it breaks and how it behaves. Without crash reporting and some visibility into usage, you're flying blind—and users will tell you about bugs before your metrics do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Crash reporting&lt;/strong&gt; — Integrate Firebase Crashlytics, Sentry, or similar so every crash generates a report with stack trace and device info. Crashes should create issues (or alerts), not go unnoticed. Triage and fix high-impact crashes before they accumulate bad reviews.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Analytics&lt;/strong&gt; — Track key events that matter for product decisions: signup, purchase, core actions. Don’t over-instrument; focus on events you'll actually use to understand funnels and behavior. Too many events add noise and can complicate privacy compliance.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Performance&lt;/strong&gt; — Monitor startup time and key screens (e.g. time to interactive, list scroll performance). Set a baseline and fix big regressions before they hit production. Slow apps get uninstalled; catching regressions in staging or beta keeps production healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Good docs reduce "how do I run this?" and "how do we release?" to a few minutes instead of a day of digging. They also make it possible for someone else to own the release when you're not around.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;README&lt;/strong&gt; — Document how to install dependencies, run the app locally, and build for dev/staging/prod (including which variant or scheme to use). A new team member should be able to get the app running from the README alone, without tribal knowledge.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Runbook&lt;/strong&gt; — Document how to cut a release: which branch or tag to use, how to trigger the CI build, and how to promote from TestFlight/Internal Testing to production. Include who to contact if the build fails or if signing/credentials need to be rotated. Keep it in the repo or in a shared wiki so it's always one link away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list is a starting point, not a final rulebook. Add what fits your stack, compliance (e.g. HIPAA, PCI), and team. Use it as a baseline, ship your MVP, then improve each area as you go. If something on your checklist has saved you more than once, share it in the comments or in your own post—tag it so others can find it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>checklist</category>
      <category>devops</category>
    </item>
    <item>
      <title>What "Clean Code" Means to Me in a React Codebase</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 12 Feb 2026 18:38:55 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/what-clean-code-means-to-me-in-a-react-codebase-565j</link>
      <guid>https://dev.to/iamsaadmehmood/what-clean-code-means-to-me-in-a-react-codebase-565j</guid>
      <description>&lt;p&gt;“Clean code” can mean anything. In a React (or React Native) codebase, here’s what I actually aim for—concrete, not vague.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One Job Per Component
&lt;/h2&gt;

&lt;p&gt;A component should do one thing: render a list, show a form, display a card. If it’s doing “fetch data + transform + render + handle three different states,” I split it. Smaller components are easier to read, test, and reuse. I extract hooks for logic (data fetching, form state) so the component stays mostly presentational.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Names That Describe Purpose
&lt;/h2&gt;

&lt;p&gt;Components: &lt;code&gt;UserCard&lt;/code&gt;, &lt;code&gt;OrderSummary&lt;/code&gt;, &lt;code&gt;LoginForm&lt;/code&gt;—names that say what they are. Functions: &lt;code&gt;formatCurrency&lt;/code&gt;, &lt;code&gt;getUserDisplayName&lt;/code&gt;, &lt;code&gt;validateEmail&lt;/code&gt;—names that say what they do. I avoid &lt;code&gt;Wrapper&lt;/code&gt;, &lt;code&gt;Container&lt;/code&gt;, or &lt;code&gt;Component1&lt;/code&gt; unless there’s no better name. Good names reduce the need for comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Fewer Props, Clear Contracts
&lt;/h2&gt;

&lt;p&gt;I keep the prop list short. If a component needs many props, I consider: is it doing too much? Can some props be grouped (e.g. &lt;code&gt;user: { name, avatar }&lt;/code&gt;)? I use TypeScript so the contract is explicit. Optional props are optional for a reason; I don’t add “just in case” props.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No Magic in the Tree
&lt;/h2&gt;

&lt;p&gt;I avoid inline object/array literals and inline functions in JSX when they’re used as props to children. They break referential equality and cause unnecessary re-renders. I use &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt; when the value or handler is passed to a memoized child; otherwise I keep them outside the tree or in stable refs. No “it works but I don’t know why” performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. State in the Right Place
&lt;/h2&gt;

&lt;p&gt;I keep state as close as possible to where it’s used. If only one component needs it, it lives there. If two siblings need it, I lift to the parent (or use a small context). I don’t put everything in a global store “for later.” Server state goes in TanStack Query (or similar), not in Redux or Context by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Boundaries for Side Effects
&lt;/h2&gt;

&lt;p&gt;Data fetching, subscriptions, and other side effects live in &lt;code&gt;useEffect&lt;/code&gt; (or in a library like TanStack Query). I don’t fetch in render or mix side effects with pure rendering logic. Cleanup (unsubscribe, abort) goes in the effect’s return. That keeps components predictable and avoids leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Consistency Over Personal Taste
&lt;/h2&gt;

&lt;p&gt;I follow the project’s existing patterns: file structure, naming, where state lives, how we handle errors. I don’t introduce a new pattern “because I like it” without team agreement. Consistency makes the codebase navigable for everyone.&lt;/p&gt;

&lt;p&gt;Clean code, to me, is: one job per piece, clear names, explicit contracts, minimal magic, state and effects in the right place, and consistency. Boring and repeatable—and that’s the point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>cleancode</category>
      <category>frontend</category>
      <category>bestofdev</category>
    </item>
    <item>
      <title>Firestore vs Realtime Database: Which Is Best and Why?</title>
      <dc:creator>Saad Mehmood</dc:creator>
      <pubDate>Thu, 05 Feb 2026 06:07:26 +0000</pubDate>
      <link>https://dev.to/iamsaadmehmood/firestore-vs-realtime-database-which-is-best-and-why-1h2c</link>
      <guid>https://dev.to/iamsaadmehmood/firestore-vs-realtime-database-which-is-best-and-why-1h2c</guid>
      <description>&lt;p&gt;Firebase offers two databases: &lt;strong&gt;Realtime Database&lt;/strong&gt; (the original JSON tree) and &lt;strong&gt;Cloud Firestore&lt;/strong&gt; (the newer document database). Both sync in real time and work offline, but they’re built for different needs. Here’s how they compare and which one I pick—and why.fi&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Realtime Database&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Firestore&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single JSON tree; data nested under paths&lt;/td&gt;
&lt;td&gt;Collections + documents; each doc can have subcollections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep path reads; no real “query language”&lt;/td&gt;
&lt;td&gt;Rich queries: where, orderBy, limit, compound indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single region; scales but with limits on fan-out&lt;/td&gt;
&lt;td&gt;Multi-region; designed to scale with automatic sharding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full offline with persistence&lt;/td&gt;
&lt;td&gt;Full offline with persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per GB stored + bandwidth; often cheaper at tiny scale&lt;/td&gt;
&lt;td&gt;Per read/write/delete + storage; can get costly with heavy reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very low (single tree, simple reads)&lt;/td&gt;
&lt;td&gt;Slightly higher (more flexible but more work per query)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Realtime Database: What It Is and When It Shines
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt; — One big JSON tree. You read and write at paths like &lt;code&gt;/users/uid/profile&lt;/code&gt; or &lt;code&gt;/chats/chatId/messages&lt;/code&gt;. Data is nested; you listen to a path and get everything under it (or use shallow reads to limit depth). There’s no “query by field” — you structure the tree so that the path &lt;em&gt;is&lt;/em&gt; your query (e.g. “messages for this chat” = one path).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; — No collections, no indexes. You just read/write paths. Great for small apps and prototypes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very low latency&lt;/strong&gt; — Single tree, direct path access. Good for high-frequency updates (e.g. presence, cursor position, live scores).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheap at small scale&lt;/strong&gt; — If you have little data and moderate traffic, storage + bandwidth pricing can be lower than Firestore’s read/write model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny SDK&lt;/strong&gt; — Smaller bundle than Firestore; matters for very constrained environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No real queries&lt;/strong&gt; — You can’t “get all users where role = admin” or “messages where createdAt &amp;gt; X.” You either read a path and filter in the client or denormalize heavily and maintain multiple paths. Complex querying gets messy fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling and depth&lt;/strong&gt; — Deep trees and wide “fan-out” (e.g. one write that touches many paths) don’t scale as well. You hit limits and have to flatten or split data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; — Everything is a tree. Relations and many-to-many are awkward; you end up with duplicated data and sync logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Simple real-time apps (presence, simple chat, live counters), prototypes, or when the data is naturally a tree and query needs are minimal. Also when the team already has a Realtime Database app and migration isn’t worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firestore: What It Is and When It Shines
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is&lt;/strong&gt; — A document database. Data lives in &lt;strong&gt;collections&lt;/strong&gt; (e.g. &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;chats&lt;/code&gt;); each &lt;strong&gt;document&lt;/strong&gt; has an ID and key-value (and nested) data. You can have &lt;strong&gt;subcollections&lt;/strong&gt; (e.g. &lt;code&gt;chats/chatId/messages&lt;/code&gt;). You query with &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;orderBy&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, and compound indexes. Realtime listeners work on collections or query results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real queries&lt;/strong&gt; — “Users where role == 'admin',” “messages where chatId == X orderBy createdAt limit 50.” You model data once and query it many ways. Compound indexes are explicit and predictable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scales better&lt;/strong&gt; — Designed for large apps. Multi-region, automatic scaling. No single-tree bottleneck. Better for many collections and complex access patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clearer data model&lt;/strong&gt; — Documents and subcollections map well to “entities” and relations. Less denormalization than Realtime Database for the same flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt; — Same strong offline support; queries and listeners work offline with persistence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pricing can bite&lt;/strong&gt; — You pay per read/write/delete. Heavy read traffic (e.g. “list all items” on every open) can get expensive if you don’t cache or paginate. You need to think about read volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More setup&lt;/strong&gt; — Indexes, security rules, and data modeling take more thought than “just a path.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slightly higher latency&lt;/strong&gt; — More flexible queries mean a bit more work per request than a single path read. Usually negligible for most apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Most new Firebase apps. Anything that needs “list by X,” “filter by Y,” pagination, or multiple views over the same data. Apps that will grow in data and query complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side: Key Differences
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data structure&lt;/strong&gt; — Realtime Database = one tree; Firestore = collections + documents + subcollections. Firestore fits relational-ish and multi-entity models better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Querying&lt;/strong&gt; — Realtime Database = path-based (and maybe client-side filter); Firestore = server-side queries with where/orderBy/limit and indexes. Firestore wins for anything beyond “give me this path.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling&lt;/strong&gt; — Realtime Database scales but has a single-tree and fan-out limits; Firestore is built for scale and multi-region. For growth, Firestore is the safer bet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; — At tiny scale and low reads, Realtime Database can be cheaper (GB + bandwidth). At higher read/write volume or complex queries, Firestore’s model is easier to reason about and often comparable or better if you design for it (pagination, cache, avoid unnecessary reads).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline&lt;/strong&gt; — Both support offline persistence and sync. No clear winner; both are good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Is Best? My Recommendation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For most apps today: Firestore is the better default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Querying&lt;/strong&gt; — Almost every app eventually needs “get items by condition” or “ordered list with pagination.” Firestore does that natively; Realtime Database forces workarounds and denormalization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt; — If the app grows, Firestore scales with you. Realtime Database can hit structural limits and require big refactors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data model&lt;/strong&gt; — Documents and collections match how we think about users, chats, posts, etc. Easier to maintain and onboard others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem and future&lt;/strong&gt; — Google is investing in Firestore (multi-region, better tooling). Realtime Database is stable but not where new features land first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When Realtime Database is still the best choice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Very simple real-time only&lt;/strong&gt; — Presence, live counters, or a tiny tree with almost no query needs. Simplicity and latency matter more than flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict cost at tiny scale&lt;/strong&gt; — You have almost no reads/writes and want the smallest bill; Realtime Database’s pricing can be lower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing Realtime Database app&lt;/strong&gt; — Migration has a cost. If the current app is simple and stable, staying on Realtime Database is fine until you need Firestore’s query and scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Use &lt;strong&gt;Firestore&lt;/strong&gt; for new projects and anything that needs queries or will grow. Use &lt;strong&gt;Realtime Database&lt;/strong&gt; for simple, path-based real-time apps or when you’re already on it and migration isn’t justified. In practice, “which is best” = Firestore for most cases; Realtime Database for a narrow but real set of use cases.&lt;/p&gt;




&lt;p&gt;*Saad Mehmood — &lt;a href="https://iamsaadmehmood.com" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>mobile</category>
      <category>programming</category>
      <category>firestore</category>
    </item>
  </channel>
</rss>
