<?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: Safdar Ali</title>
    <description>The latest articles on DEV Community by Safdar Ali (@safdarali25).</description>
    <link>https://dev.to/safdarali25</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2398981%2F54da62cd-54f1-41f2-8343-10121c73537c.jpg</url>
      <title>DEV Community: Safdar Ali</title>
      <link>https://dev.to/safdarali25</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/safdarali25"/>
    <language>en</language>
    <item>
      <title>Next.js App Router Complete Beginner Guide 2026</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Fri, 10 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/nextjs-app-router-complete-beginner-guide-2026-4j6n</link>
      <guid>https://dev.to/safdarali25/nextjs-app-router-complete-beginner-guide-2026-4j6n</guid>
      <description>&lt;p&gt;The &lt;strong&gt;nextjs app router tutorial&lt;/strong&gt; gap is real: official docs are reference-heavy, and beginners still think in Pages Router (&lt;code&gt;pages/index.tsx&lt;/code&gt;). App Router uses folders in &lt;code&gt;app/&lt;/code&gt; where the file name tells Next.js what to do. I migrated client sites in 2024–2025 and teach this sequence on my YouTube channel. Follow these steps in order — layout, page, loading, then server fetch.&lt;/p&gt;
&lt;h2 id="mental-model"&gt;Mental model — folders are routes&lt;/h2&gt;
&lt;p&gt;Every folder under &lt;code&gt;app/&lt;/code&gt; maps to a URL segment. Special files: &lt;code&gt;page.tsx&lt;/code&gt; = UI, &lt;code&gt;layout.tsx&lt;/code&gt; = shared shell, &lt;code&gt;loading.tsx&lt;/code&gt; = skeleton while slow segments load.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/&lt;br&gt;
  layout.tsx      → wraps ALL routes&lt;br&gt;
  page.tsx        → URL: /&lt;br&gt;
  about/&lt;br&gt;
    page.tsx      → URL: /about&lt;br&gt;
  blog/&lt;br&gt;
    page.tsx      → URL: /blog&lt;br&gt;
    [slug]/&lt;br&gt;
      page.tsx    → URL: /blog/my-post&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No React Router install. Rename a folder, you rename a route. That is the core App Router win.&lt;/p&gt;
&lt;p&gt;If you are coming from Create React App, delete the mental model of a single &lt;code&gt;App.tsx&lt;/code&gt; with a Switch. You will have multiple layout.tsx files at different depths — root for html/body, nested for dashboard sidebars. Each layout wraps only its subtree.&lt;/p&gt;
&lt;h2 id="step-layout"&gt;Step 1 — Root layout (HTML shell once)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/layout.tsx&lt;br&gt;
import type { Metadata } from "next";&lt;br&gt;
import "./globals.css";

&lt;p&gt;export const metadata: Metadata = {&lt;br&gt;
  title: "My App",&lt;br&gt;
  description: "Beginner App Router site",&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;export default function RootLayout({&lt;br&gt;
  children,&lt;br&gt;
}: {&lt;br&gt;
  children: React.ReactNode;&lt;br&gt;
}) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;html lang="en"&amp;gt;&lt;br&gt;
      &amp;lt;body className="min-h-screen bg-white text-neutral-900"&amp;gt;&lt;br&gt;
        &amp;lt;header className="border-b p-4"&amp;gt;My Site&amp;lt;/header&amp;gt;&lt;br&gt;
        &amp;lt;main&amp;gt;{children}&amp;lt;/main&amp;gt;&lt;br&gt;
      &amp;lt;/body&amp;gt;&lt;br&gt;
    &amp;lt;/html&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Layouts do not remount when navigating between child pages — perfect for nav bars and fonts. Keep providers that must persist here (theme, analytics).&lt;/p&gt;
&lt;h2 id="step-page"&gt;Step 2 — First page&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/page.tsx — home route&lt;br&gt;
export default function HomePage() {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;section className="p-8"&amp;gt;&lt;br&gt;
      &amp;lt;h1 className="text-3xl font-bold"&amp;gt;Welcome&amp;lt;/h1&amp;gt;&lt;br&gt;
      &amp;lt;p&amp;gt;Your first App Router page — no use client needed.&amp;lt;/p&amp;gt;&lt;br&gt;
    &amp;lt;/section&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Default export must be a component. This is a Server Component unless you add &lt;code&gt;"use client"&lt;/code&gt; at the top.&lt;/p&gt;
&lt;h2 id="step-loading"&gt;Step 3 — loading.tsx for perceived speed&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/blog/loading.tsx — shows while blog segment loads&lt;br&gt;
export default function BlogLoading() {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;div className="animate-pulse space-y-4 p-8"&amp;gt;&lt;br&gt;
      &amp;lt;div className="h-8 w-48 rounded bg-neutral-200" /&amp;gt;&lt;br&gt;
      &amp;lt;div className="h-4 w-full rounded bg-neutral-200" /&amp;gt;&lt;br&gt;
      &amp;lt;div className="h-4 w-3/4 rounded bg-neutral-200" /&amp;gt;&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next.js wraps the segment in Suspense automatically. Users see skeletons instead of frozen UI — critical on Indian mobile networks.&lt;/p&gt;
&lt;h2 id="step-fetch"&gt;Step 4 — Server Component fetch (no useEffect)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/blog/page.tsx&lt;br&gt;
type Post = { slug: string; title: string };

&lt;p&gt;async function getPosts(): Promise&amp;lt;Post[]&amp;gt; {&lt;br&gt;
  const res = await fetch("&lt;a href="https://api.example.com/posts" rel="noopener noreferrer"&gt;https://api.example.com/posts&lt;/a&gt;", {&lt;br&gt;
    next: { revalidate: 3600 },&lt;br&gt;
  });&lt;br&gt;
  if (!res.ok) throw new Error("Failed to load posts");&lt;br&gt;
  return res.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export default async function BlogPage() {&lt;br&gt;
  const posts = await getPosts();&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;ul className="space-y-2 p-8"&amp;gt;&lt;br&gt;
      {posts.map((post) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={post.slug}&amp;gt;&lt;br&gt;
          &amp;lt;a href={"/blog/" + post.slug}&amp;gt;{post.title}&amp;lt;/a&amp;gt;&lt;br&gt;
        &amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — Pages Router habit (client fetch)&lt;br&gt;
"use client";&lt;br&gt;
useEffect(() =&amp;gt; fetch("/api/posts").then(/* ... */), []);

&lt;p&gt;// AFTER — async server component&lt;br&gt;
// Data ready before HTML ships — better SEO and LCP&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deeper caching rules: &lt;a href="https://safdarali.in/blog/ssr-ssg-isr-nextjs-explained" rel="noopener noreferrer"&gt;SSR vs SSG vs ISR&lt;/a&gt;. Performance tuning: &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;60% load time case study&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="comparison"&gt;App Router files — quick comparison table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Required?&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;layout.tsx&lt;/td&gt;
&lt;td&gt;Shared UI wrapper&lt;/td&gt;
&lt;td&gt;Root required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;page.tsx&lt;/td&gt;
&lt;td&gt;Route UI&lt;/td&gt;
&lt;td&gt;Yes per route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loading.tsx&lt;/td&gt;
&lt;td&gt;Loading skeleton&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error.tsx&lt;/td&gt;
&lt;td&gt;Error boundary&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;not-found.tsx&lt;/td&gt;
&lt;td&gt;404 UI&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;route.ts&lt;/td&gt;
&lt;td&gt;API endpoint&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="client-leaf"&gt;Step 5 — Add client components only as leaves&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// components/CounterButton.tsx&lt;br&gt;
"use client";&lt;br&gt;
import { useState } from "react";

&lt;p&gt;export function CounterButton() {&lt;br&gt;
  const [n, setN] = useState(0);&lt;br&gt;
  return &amp;lt;button onClick={() =&amp;gt; setN(n + 1)}&amp;gt;Clicked {n} times&amp;lt;/button&amp;gt;;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// app/page.tsx — server page imports client leaf&lt;br&gt;
import { CounterButton } from "@/components/CounterButton";&lt;/p&gt;

&lt;p&gt;export default function HomePage() {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;section&amp;gt;&lt;br&gt;
      &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;&lt;br&gt;
      &amp;lt;CounterButton /&amp;gt;&lt;br&gt;
    &amp;lt;/section&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components&lt;/a&gt; before marking whole pages as client — that is the beginner mistake that balloons bundle size.&lt;/p&gt;
&lt;h2 id="dynamic-route"&gt;Step 6 — Dynamic routes and params&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/blog/[slug]/page.tsx&lt;br&gt;
type Props = { params: Promise&amp;lt;{ slug: string }&amp;gt; };

&lt;p&gt;export default async function PostPage({ params }: Props) {&lt;br&gt;
  const { slug } = await params;&lt;br&gt;
  const post = await getPost(slug);&lt;br&gt;
  if (!post) return &amp;lt;p&amp;gt;Not found&amp;lt;/p&amp;gt;;&lt;br&gt;
  return &amp;lt;article&amp;gt;&amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;&amp;lt;/article&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Next.js 15, params is a Promise — await it. TypeScript strict mode helps catch forgotten awaits — &lt;a href="https://safdarali.in/blog/typescript-strict-mode-guide-2026" rel="noopener noreferrer"&gt;strict mode guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="learn-path"&gt;Learning path after this guide&lt;/h2&gt;
&lt;p&gt;Week 1: layouts + pages + loading. Week 2: server fetch + one dynamic route. Week 3: one client form with Server Action. Week 4: deploy to Vercel and run Lighthouse. If you are choosing between stacks first, read &lt;a href="https://safdarali.in/blog/nextjs-vs-react-which-to-learn-2026" rel="noopener noreferrer"&gt;Next.js vs React&lt;/a&gt;. Organise folders early with &lt;a href="https://safdarali.in/blog/nextjs-project-structure-guide-2026" rel="noopener noreferrer"&gt;project structure guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For AI-assisted coding, see &lt;a href="https://safdarali.in/blog/cursor-claude-react-workflow-2026" rel="noopener noreferrer"&gt;Cursor + Claude workflow&lt;/a&gt; — but build this hello-world tree by hand once so file conventions stick.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production I scaffold with root layout, route groups for marketing vs app, loading.tsx on slow segments, and async server pages for public content. This portfolio follows the same pattern — thin &lt;code&gt;app/blog/.../page.tsx&lt;/code&gt; files, heavy logic elsewhere.&lt;/p&gt;
&lt;p&gt;At my day job, beginners who complete these six steps ship their first internal page in a week — faster than learning Pages Router and relearning later.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;App Router = folders + special files.&lt;/strong&gt; Layout once, page per route, loading for UX, async server fetch for data. Client components are seasoning, not the main dish.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/react-19-features-production-guide" rel="noopener noreferrer"&gt;React 19 features&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in&lt;br&gt;%0A/blog/free-video-thumbnail-generator-online-no-upload" rel="noopener noreferrer"&gt; &lt;h3&gt;Free Video Thumbnail Generator Online — No Upload&lt;/h3&gt;
&lt;p&gt;Free video thumbnail generator online — extract frames from video without upload. Browser-based, no watermark, YouTube thumbnail from MP4 workflow.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Jun 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/nextjs-15-still-slow-react-19-hydration-fix" rel="noopener noreferrer"&gt; &lt;h3&gt;Why Your Next.js 15 App is Still Slow (And How to Fix the React 19 Hydration Lag)&lt;/h3&gt;
&lt;p&gt;Next.js 15 performance optimization — fix INP, LCP layout shifts, React 19 hydration errors, and React Compiler gaps with a production DevTools workflow.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>WCAG 2.2 Accessibility for React Developers — Practical Guide</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Thu, 09 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/wcag-22-accessibility-for-react-developers-practical-guide-1b7o</link>
      <guid>https://dev.to/safdarali25/wcag-22-accessibility-for-react-developers-practical-guide-1b7o</guid>
      <description>&lt;p&gt;I'm &lt;a href="https://safdarali.in" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;, a frontend engineer in Bengaluru. Last quarter I audited a client dashboard that looked polished — clean Tailwind, smooth transitions, Lighthouse performance in the 90s — and failed basic keyboard navigation in under two minutes. Tab order jumped randomly, modals trapped nothing, and icon-only buttons had no labels. WCAG 2.2 is not a legal checkbox for enterprise contracts alone. It is how you ship React UI that works for everyone: screen reader users, keyboard-only users, people on slow 4G with zoom enabled, and your future self debugging at 11pm. This guide covers the wcag 2.2 react patterns I run before every merge.&lt;/p&gt;

&lt;p&gt;Why WCAG 2.2 matters for React in 2026&lt;br&gt;
WCAG 2.2 added criteria that directly affect React apps: focus not obscured, dragging movements, target size minimums, and consistent help. React's component model makes accessibility both easier and easier to break — you can encapsulate good patterns in a shared Dialog component, but you can also copy-paste a div-with-onClick button across forty files.&lt;/p&gt;

&lt;p&gt;The legal landscape in India is catching up. Government portals and fintech products increasingly require accessibility audits before launch. Even when nobody asks, inclusive UI reduces support tickets — unclear error messages and broken focus management generate more "the form is broken" emails than actual backend failures.&lt;/p&gt;

&lt;p&gt;React does not ship accessible components by default. A  is focusable; a  is not, unless you wire it. Your job is to make the accessible path the default path in your design system.&lt;/p&gt;


&lt;p&gt;Focus traps — modals that actually work&lt;br&gt;
A focus trap keeps keyboard focus inside a modal until the user dismisses it. Without one, Tab sends focus to elements behind the overlay — confusing for sighted keyboard users and disorienting for screen reader users who hear content from two layers at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://safdarali.in/blog/wcag-22-react-accessibility-guide" rel="noopener noreferrer"&gt;Continue Reading...&lt;/a&gt;&lt;/p&gt;


</description>
      <category>wcag</category>
      <category>a11y</category>
      <category>react</category>
    </item>
    <item>
      <title>React useCallback vs useMemo — When You Actually Need Them</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Wed, 08 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/react-usecallback-vs-usememo-when-you-actually-need-them-1bef</link>
      <guid>https://dev.to/safdarali25/react-usecallback-vs-usememo-when-you-actually-need-them-1bef</guid>
      <description>&lt;p&gt;Junior PRs in my reviews often wrap every function in &lt;code&gt;useCallback&lt;/code&gt; and every array in &lt;code&gt;useMemo&lt;/code&gt; because a blog post said so. The React Profiler showed the opposite: more memoisation, more comparison work, slower interactions. This &lt;strong&gt;usecallback vs usememo&lt;/strong&gt; guide is profiler-first — what each hook does, when it helps, and the over-memoising mistake I made on a real table.&lt;/p&gt;
&lt;h2 id="definitions"&gt;What each hook actually does&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;useMemo&lt;/strong&gt; caches a &lt;em&gt;computed value&lt;/em&gt; between renders when dependencies are unchanged. &lt;strong&gt;useCallback&lt;/strong&gt; caches a &lt;em&gt;function reference&lt;/em&gt; — it is useMemo for functions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useMemo, useCallback } from "react";

&lt;p&gt;function Dashboard({ rows }: { rows: Row[] }) {&lt;br&gt;
  const total = useMemo(() =&amp;gt; rows.reduce((sum, r) =&amp;gt; sum + r.amount, 0), [rows]);&lt;/p&gt;

&lt;p&gt;const handleExport = useCallback(() =&amp;gt; {&lt;br&gt;
    downloadCsv(rows);&lt;br&gt;
  }, [rows]);&lt;/p&gt;

&lt;p&gt;return &amp;lt;Toolbar total={total} onExport={handleExport} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither hook prevents re-renders by itself. They only help when a child skips render because props are referentially equal — usually with &lt;code&gt;React.memo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A common confusion: developers think useMemo stops the parent re-rendering. It does not. The parent still runs its function body every time state changes — useMemo only skips recomputing one expression inside that run. useCallback is the same for function identity. If your performance problem is "the whole page re-renders on every keystroke," move state down or split context before touching memo hooks.&lt;/p&gt;
&lt;h2 id="over-memo"&gt;The over-memoising mistake I made&lt;/h2&gt;
&lt;p&gt;On a 500-row admin table I wrapped every cell formatter in useMemo and passed useCallback handlers to each row. Scroll jank got worse — dependency checks on every scroll event dominated.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — memo soup, slower scroll&lt;br&gt;
const formatted = useMemo(&lt;br&gt;
  () =&amp;gt; rows.map((r) =&amp;gt; formatCurrency(r.amount)),&lt;br&gt;
  [rows]&lt;br&gt;
);&lt;br&gt;
const onRowClick = useCallback((id: string) =&amp;gt; navigate("/row/" + id), []);

&lt;p&gt;// AFTER — virtualise the list, drop per-row memo&lt;br&gt;
import { useVirtualizer } from "@tanstack/react-virtual";&lt;br&gt;
// Only memo expensive derived data used by memoised children&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fix was virtualisation + smaller components, not more hooks. Lesson: measure before memoising.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — unstable inline object recreates every parent render&lt;br&gt;
&amp;lt;HeavyChart config={{ theme: "dark", showGrid: true }} /&amp;gt;

&lt;p&gt;// AFTER — stable reference only if HeavyChart is memoised&lt;br&gt;
const config = useMemo(() =&amp;gt; ({ theme: "dark", showGrid: true }), []);&lt;br&gt;
&amp;lt;HeavyChart config={config} /&amp;gt;&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without React.memo on HeavyChart, the AFTER snippet still re-renders the chart — you paid for useMemo and got nothing. That is the over-memoising pattern: hooks without a memoised consumer.&lt;/p&gt;
&lt;h2 id="profiler"&gt;How I use the React Profiler&lt;/h2&gt;
&lt;p&gt;Chrome React DevTools → Profiler → record interaction → look for components with long render times and high render counts. Yellow bars mean wasted renders. I fix structure first (split context, move state down, virtual lists), then add memo where a memoised child still re-renders with stable props.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Wrap only when Profiler shows child re-renders are expensive&lt;br&gt;
import { memo } from "react";

&lt;p&gt;const HeavyChart = memo(function HeavyChart({ data }: { data: Point[] }) {&lt;br&gt;
  // expensive canvas draw&lt;br&gt;
  return &amp;lt;canvas /&amp;gt;;&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// Parent must pass stable data reference&lt;br&gt;
const data = useMemo(() =&amp;gt; computePoints(raw), [raw]);&lt;br&gt;
return &amp;lt;HeavyChart data={data} /&amp;gt;;&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On marketing sites built with Server Components, much of the tree never hydrates — memo hooks on the server branch are pointless. See &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="when-table"&gt;When to use which — table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;useMemo&lt;/th&gt;
&lt;th&gt;useCallback&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expensive calculation&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable prop to memo child&lt;/td&gt;
&lt;td&gt;For objects/arrays&lt;/td&gt;
&lt;td&gt;For functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;useEffect dependency&lt;/td&gt;
&lt;td&gt;Rarely needed&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primitive props to memo child&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List without virtualisation&lt;/td&gt;
&lt;td&gt;Fix list first&lt;/td&gt;
&lt;td&gt;Fix list first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context provider value&lt;/td&gt;
&lt;td&gt;Often yes (object)&lt;/td&gt;
&lt;td&gt;Callbacks inside value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="usecallback-example"&gt;useCallback — legitimate use with memoised child&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;"use client";&lt;br&gt;
import { memo, useCallback, useState } from "react";

&lt;p&gt;const SearchInput = memo(function SearchInput({&lt;br&gt;
  onSearch,&lt;br&gt;
}: {&lt;br&gt;
  onSearch: (q: string) =&amp;gt; void;&lt;br&gt;
}) {&lt;br&gt;
  console.log("SearchInput render");&lt;br&gt;
  return &amp;lt;input onChange={(e) =&amp;gt; onSearch(e.target.value)} /&amp;gt;;&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export function FilterBar() {&lt;br&gt;
  const [query, setQuery] = useState("");&lt;br&gt;
  const onSearch = useCallback((q: string) =&amp;gt; setQuery(q), []);&lt;br&gt;
  return &amp;lt;SearchInput onSearch={onSearch} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without useCallback, SearchInput re-renders whenever FilterBar state unrelated to search changes — only worth fixing if Profiler proves SearchInput is costly.&lt;/p&gt;
&lt;h2 id="usememo-example"&gt;useMemo — filtering large lists&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const filtered = useMemo(&lt;br&gt;
  () =&amp;gt; products.filter((p) =&amp;gt; p.name.toLowerCase().includes(query.toLowerCase())),&lt;br&gt;
  [products, query]&lt;br&gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the filter runs in under a millisecond on your data size, skip useMemo — the hook overhead can cost more than the loop.&lt;/p&gt;
&lt;h2 id="rule-thumb"&gt;Rule of thumb I teach in reviews&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Default: no memo hooks.&lt;/strong&gt; Add when Profiler shows a problem. useMemo for expensive derived data consumed by memoised children. useCallback for stable handlers passed to those children. Never memoise to silence ESLint exhaustive-deps without understanding why deps change.&lt;/p&gt;
&lt;p&gt;React 19 and the future compiler may auto-memoise — until then, stay boring. Performance wins on public sites still come from server rendering and smaller bundles — &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;Next.js performance guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production I memoise chart components and heavy derived selectors in dashboards. Marketing pages: almost zero useCallback/useMemo. At my day job I comment why when memo is added — "Profiler: Chart 40ms → 8ms" — so the next dev does not delete it blindly.&lt;/p&gt;
&lt;p&gt;When mentoring in Bengaluru, I ask juniors to screenshot Profiler flamegraphs in PRs that add memo hooks — not because I love process, but because it proves they measured. Copy-pasting useCallback from Stack Overflow without a memo child is the most common wasted line I delete in review.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;useCallback stabilises functions; useMemo stabilises values.&lt;/strong&gt; Both only matter when referential equality blocks wasted work. Profile first, memo second.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/react-19-features-production-guide" rel="noopener noreferrer"&gt;React 19 features&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/frontend-developer-portfolio-guide-india-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;How to Build a Frontend Developer Portfolio That Stands Out&lt;/h3&gt;
&lt;p&gt;Frontend developer portfolio guide for India — sections, React/Next.js examples, SEO, performance, personal branding, FAQ, and checklist to build and rank.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt; &lt;h3&gt;React Server Components vs Client Components — When to Use Which&lt;/h3&gt;
&lt;p&gt;Practical RSC vs client guide for Next.js App Router — when to use each, real code, bundle before/after, and performance impact.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>usecallback</category>
      <category>usememo</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Structure Large Next.js Projects — Folder Architecture Guide</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Wed, 08 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/how-i-structure-large-nextjs-projects-folder-architecture-guide-2f2i</link>
      <guid>https://dev.to/safdarali25/how-i-structure-large-nextjs-projects-folder-architecture-guide-2f2i</guid>
      <description>&lt;p&gt;Bad &lt;strong&gt;nextjs folder structure&lt;/strong&gt; does not show up on day one. It shows up at month six when three developers search for the checkout form hook and find four copies. I reorganised a client dashboard after exactly that — this guide is the tree I use now on large App Router projects, why each folder exists, mistakes from my first Next.js apps, and the &lt;strong&gt;10-second findability rule&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="tree"&gt;Real folder tree — production-shaped layout&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;my-app/&lt;br&gt;
├── app/                    # routes only — thin pages&lt;br&gt;
│   ├── (marketing)/        # route group — shared layout, no URL segment&lt;br&gt;
│   │   ├── layout.tsx&lt;br&gt;
│   │   ├── page.tsx&lt;br&gt;
│   │   └── pricing/page.tsx&lt;br&gt;
│   ├── (dashboard)/&lt;br&gt;
│   │   ├── layout.tsx&lt;br&gt;
│   │   └── orders/page.tsx&lt;br&gt;
│   ├── api/                # route handlers&lt;br&gt;
│   │   └── webhooks/stripe/route.ts&lt;br&gt;
│   ├── layout.tsx          # root layout&lt;br&gt;
│   └── globals.css&lt;br&gt;
├── components/             # shared UI — buttons, cards, shell&lt;br&gt;
│   ├── ui/&lt;br&gt;
│   └── layout/&lt;br&gt;
├── features/               # business domains — colocated logic&lt;br&gt;
│   ├── auth/&lt;br&gt;
│   │   ├── components/&lt;br&gt;
│   │   ├── hooks/&lt;br&gt;
│   │   └── actions.ts&lt;br&gt;
│   └── orders/&lt;br&gt;
│       ├── components/&lt;br&gt;
│       ├── api.ts&lt;br&gt;
│       └── types.ts&lt;br&gt;
├── lib/                    # server + shared utilities&lt;br&gt;
│   ├── db.ts&lt;br&gt;
│   └── env.ts&lt;br&gt;
├── hooks/                  # truly global client hooks&lt;br&gt;
├── types/                  # global TS types&lt;br&gt;
├── data/                   # static data, blog posts list&lt;br&gt;
└── public/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Routes live in &lt;code&gt;app/&lt;/code&gt;. Business logic lives in &lt;code&gt;features/&lt;/code&gt;. Generic design system pieces live in &lt;code&gt;components/ui&lt;/code&gt;. That separation is the whole game.&lt;/p&gt;
&lt;h2 id="why-each"&gt;Why each folder exists&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Folder&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Do not put here&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;app/&lt;/td&gt;
&lt;td&gt;URLs, layouts, loading.tsx&lt;/td&gt;
&lt;td&gt;Fat business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;features/&lt;/td&gt;
&lt;td&gt;Domain modules (orders, auth)&lt;/td&gt;
&lt;td&gt;Generic Button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;components/ui&lt;/td&gt;
&lt;td&gt;Reusable primitives&lt;/td&gt;
&lt;td&gt;Order-specific tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lib/&lt;/td&gt;
&lt;td&gt;DB clients, env validation&lt;/td&gt;
&lt;td&gt;React components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app/api&lt;/td&gt;
&lt;td&gt;Webhooks, REST edge cases&lt;/td&gt;
&lt;td&gt;Every form POST (prefer actions)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="thin-pages"&gt;Thin pages — route files under 40 lines&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/(dashboard)/orders/page.tsx — orchestration only&lt;br&gt;
import { OrderTable } from "@/features/orders/components/OrderTable";&lt;br&gt;
import { getOrders } from "@/features/orders/api";

&lt;p&gt;export default async function OrdersPage() {&lt;br&gt;
  const orders = await getOrders();&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;section&amp;gt;&lt;br&gt;
      &amp;lt;h1&amp;gt;Orders&amp;lt;/h1&amp;gt;&lt;br&gt;
      &amp;lt;OrderTable rows={orders} /&amp;gt;&lt;br&gt;
    &amp;lt;/section&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your page file exceeds a screen, extract to features. Pages should read like a table of contents.&lt;/p&gt;
&lt;p&gt;Thin pages also make Server Component boundaries obvious: data fetching stays in api.ts or the page, UI in feature components, client interactivity in a single leaf with "use client". When a page mixes all three, reviews slow down and hydration bugs hide in the middle of a 200-line file.&lt;/p&gt;
&lt;h2 id="findability"&gt;The 10-second findability rule&lt;/h2&gt;
&lt;p&gt;The rule applies to you six months later, not only new hires. I have opened my own repos and lost minutes searching for a webhook handler buried under utils — that shame is why I enforce feature folders now.&lt;/p&gt;
&lt;p&gt;Ask: "Where is the code that sends the password reset email?" If you cannot answer in ten seconds, the structure failed. Correct answer in my tree: &lt;code&gt;features/auth/actions.ts&lt;/code&gt; or &lt;code&gt;features/auth/emails.ts&lt;/code&gt; — not scattered across utils, hooks, and app/api.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Naming convention that helps findability&lt;br&gt;
features/orders/&lt;br&gt;
  components/   → OrderTable.tsx, OrderFilters.tsx&lt;br&gt;
  hooks/        → useOrderFilters.ts (client)&lt;br&gt;
  api.ts        → getOrders, createOrder (server-safe)&lt;br&gt;
  types.ts      → Order, OrderStatus&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="mistakes"&gt;First project mistakes I made&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;components/&lt;/strong&gt; dump with 80 files and no subfolders. &lt;strong&gt;utils.ts&lt;/strong&gt; at 1,200 lines. Duplicating fetch logic in every page instead of feature api modules. Putting Server Actions inside random page files instead of colocated actions.ts.&lt;/p&gt;
&lt;p&gt;Another mistake: mirroring REST URLs in folder names under components — &lt;code&gt;components/api/users/get.tsx&lt;/code&gt; — which fights App Router conventions. Routes belong in app/; business logic belongs in features/. Mixing them creates two sources of truth for the same URL.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — everything in components/&lt;br&gt;
components/&lt;br&gt;
  OrderTable.tsx&lt;br&gt;
  UserAvatar.tsx&lt;br&gt;
  pricing-card.tsx&lt;br&gt;
  helper.ts

&lt;p&gt;// AFTER — domain colocation&lt;br&gt;
features/orders/components/OrderTable.tsx&lt;br&gt;
features/users/components/UserAvatar.tsx&lt;br&gt;
app/(marketing)/pricing/page.tsx  // thin&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="route-groups"&gt;Route groups for different shells&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/(marketing)/layout.tsx — public nav, footer&lt;br&gt;
// app/(dashboard)/layout.tsx — sidebar, auth gate

&lt;p&gt;// URL stays /pricing and /orders — parentheses omit segment&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pair with rendering choices from &lt;a href="https://safdarali.in/blog/ssr-ssg-isr-nextjs-explained" rel="noopener noreferrer"&gt;SSR vs SSG vs ISR guide&lt;/a&gt; — marketing group ISR, dashboard group client-heavy.&lt;/p&gt;
&lt;h2 id="imports"&gt;Path aliases and import direction&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// tsconfig paths — "@/&lt;em&gt;": ["./&lt;/em&gt;"]

&lt;p&gt;// Allowed import flow:&lt;br&gt;
// app → features → components/ui → lib&lt;br&gt;
// features → lib&lt;br&gt;
// Never: lib → features (inverts layers)&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enforce with ESLint import rules when the team grows past three people.&lt;/p&gt;
&lt;p&gt;I also keep a single &lt;code&gt;README.md&lt;/code&gt; at the repo root with a one-paragraph map: where routes live, where features live, how to run migrations. The tree in this article is the visual version of that map — new contractors read it before their first PR.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production on this portfolio and client sites: route groups for marketing vs app shells, features/ for blog and contact logic, components/ui for sparkles and buttons. TypeScript strict — see &lt;a href="https://safdarali.in/blog/typescript-strict-mode-guide-2026" rel="noopener noreferrer"&gt;strict mode guide&lt;/a&gt;. New engineers onboard by reading features/ first, not app/ line by line.&lt;/p&gt;
&lt;p&gt;At my day job, we added a CODEOWNERS file per feature folder — reviews stay scoped. Structure is team policy, not personal taste.&lt;/p&gt;
&lt;p&gt;When a feature folder grows past ~15 files, split subdomains — &lt;code&gt;features/orders/checkout/&lt;/code&gt; vs &lt;code&gt;features/orders/history/&lt;/code&gt; — before you invent a second top-level folder that duplicates the domain name.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Routes in app/, domains in features/, primitives in components/ui.&lt;/strong&gt; If findability fails the 10-second test, refactor before adding the next feature.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/nextjs-app-router-complete-guide-2026" rel="noopener noreferrer"&gt;App Router beginner guide&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/frontend-developer-portfolio-guide-india-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;How to Build a Frontend Developer Portfolio That Stands Out&lt;/h3&gt;
&lt;p&gt;Frontend developer portfolio guide for India — sections, React/Next.js examples, SEO, performance, personal branding, FAQ, and checklist to build and rank.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt; &lt;h3&gt;React Server Components vs Client Components — When to Use Which&lt;/h3&gt;
&lt;p&gt;Practical RSC vs client guide for Next.js App Router — when to use each, real code, bundle before/after, and performance impact.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Zustand vs Redux Toolkit — Which State Manager in 2026?</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Tue, 07 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/zustand-vs-redux-toolkit-which-state-manager-in-2026-1dee</link>
      <guid>https://dev.to/safdarali25/zustand-vs-redux-toolkit-which-state-manager-in-2026-1dee</guid>
      <description>&lt;p&gt;Global state still matters in 2026 — but most pages should not need it. When they do, the debate is &lt;strong&gt;zustand vs redux 2026&lt;/strong&gt;: Redux Toolkit (RTK) is the enterprise default; Zustand is the minimal store juniors actually read. I have shipped both on dashboards in production. This article implements the same counter plus async user fetch in each, compares bundle size, and ends with what I pick for new repos.&lt;/p&gt;
&lt;h2 id="zustand-counter"&gt;Zustand — counter and async fetch&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// store/useAppStore.ts&lt;br&gt;
import { create } from "zustand";

&lt;p&gt;type User = { id: string; name: string } | null;&lt;/p&gt;

&lt;p&gt;type State = {&lt;br&gt;
  count: number;&lt;br&gt;
  user: User;&lt;br&gt;
  loading: boolean;&lt;br&gt;
  increment: () =&amp;gt; void;&lt;br&gt;
  fetchUser: () =&amp;gt; Promise&amp;lt;void&amp;gt;;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;export const useAppStore = create&amp;lt;State&amp;gt;((set) =&amp;gt; ({&lt;br&gt;
  count: 0,&lt;br&gt;
  user: null,&lt;br&gt;
  loading: false,&lt;br&gt;
  increment: () =&amp;gt; set((s) =&amp;gt; ({ count: s.count + 1 })),&lt;br&gt;
  fetchUser: async () =&amp;gt; {&lt;br&gt;
    set({ loading: true });&lt;br&gt;
    const res = await fetch("/api/me");&lt;br&gt;
    const user = await res.json();&lt;br&gt;
    set({ user, loading: false });&lt;br&gt;
  },&lt;br&gt;
}));&lt;/p&gt;

&lt;p&gt;// components/CounterPanel.tsx&lt;br&gt;
"use client";&lt;br&gt;
import { useAppStore } from "@/store/useAppStore";&lt;/p&gt;

&lt;p&gt;export function CounterPanel() {&lt;br&gt;
  const count = useAppStore((s) =&amp;gt; s.count);&lt;br&gt;
  const increment = useAppStore((s) =&amp;gt; s.increment);&lt;br&gt;
  return &amp;lt;button onClick={increment}&amp;gt;Count: {count}&amp;lt;/button&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No providers, no slices folder — one file, selective subscriptions via selectors. That is why Zustand spreads on small teams.&lt;/p&gt;
&lt;p&gt;The async fetch above is intentionally boring — no thunk middleware, just async/await inside the store action. For error handling you extend with try/catch and an error field; for retries you either wrap fetch or move the request to TanStack Query. Zustand does not prescribe async patterns, which is freedom or chaos depending on team discipline.&lt;/p&gt;
&lt;h2 id="rtk-counter"&gt;Redux Toolkit — same features, more structure&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// store/appSlice.ts&lt;br&gt;
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

&lt;p&gt;export const fetchUser = createAsyncThunk("app/fetchUser", async () =&amp;gt; {&lt;br&gt;
  const res = await fetch("/api/me");&lt;br&gt;
  return res.json();&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const appSlice = createSlice({&lt;br&gt;
  name: "app",&lt;br&gt;
  initialState: { count: 0, user: null as null | { id: string; name: string }, loading: false },&lt;br&gt;
  reducers: {&lt;br&gt;
    increment: (state) =&amp;gt; { state.count += 1; },&lt;br&gt;
  },&lt;br&gt;
  extraReducers: (builder) =&amp;gt; {&lt;br&gt;
    builder&lt;br&gt;
      .addCase(fetchUser.pending, (state) =&amp;gt; { state.loading = true; })&lt;br&gt;
      .addCase(fetchUser.fulfilled, (state, action) =&amp;gt; {&lt;br&gt;
        state.user = action.payload;&lt;br&gt;
        state.loading = false;&lt;br&gt;
      });&lt;br&gt;
  },&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export const { increment } = appSlice.actions;&lt;br&gt;
export default appSlice.reducer;&lt;/p&gt;

&lt;p&gt;// app/providers.tsx + useSelector in components&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RTK removes classic Redux boilerplate but still needs a store provider, typed hooks, and slice conventions — worth it when ten engineers touch the same state graph.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// components/UserPanel.tsx — RTK async usage&lt;br&gt;
"use client";&lt;br&gt;
import { useEffect } from "react";&lt;br&gt;
import { useAppDispatch, useAppSelector } from "@/store/hooks";&lt;br&gt;
import { fetchUser, increment } from "@/store/appSlice";

&lt;p&gt;export function UserPanel() {&lt;br&gt;
  const dispatch = useAppDispatch();&lt;br&gt;
  const count = useAppSelector((s) =&amp;gt; s.app.count);&lt;br&gt;
  const user = useAppSelector((s) =&amp;gt; s.app.user);&lt;br&gt;
  const loading = useAppSelector((s) =&amp;gt; s.app.loading);&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
    dispatch(fetchUser());&lt;br&gt;
  }, [dispatch]);&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &amp;lt;div&amp;gt;&lt;br&gt;
      &amp;lt;button onClick={() =&amp;gt; dispatch(increment())}&amp;gt;Count: {count}&amp;lt;/button&amp;gt;&lt;br&gt;
      {loading ? &amp;lt;p&amp;gt;Loading…&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;{user?.name ?? "Guest"}&amp;lt;/p&amp;gt;}&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same UX as the Zustand panel — more files, clearer audit trail in Redux DevTools when a bug report says "count jumped to 99 after refresh." That traceability is why enterprise codebases keep RTK despite smaller alternatives.&lt;/p&gt;
&lt;h2 id="bundle"&gt;Bundle size comparison (approximate, gzipped)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Measured on a minimal Next.js 15 client chunk (2026, rough)&lt;br&gt;
// zustand@5 alone          ~ 1.2 kB gzip&lt;br&gt;
// @reduxjs/toolkit + react-redux ~ 12–14 kB gzip&lt;br&gt;
// Note: RTK buys DevTools, middleware patterns, large-team norms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Numbers vary with tree-shaking and what you import. For a marketing site with one modal flag, neither library may be necessary — React context or URL state is enough. For a data-heavy dashboard, 12 kB might be cheap compared to engineering consistency.&lt;/p&gt;
&lt;p&gt;Before you optimise kilobytes, profile real user metrics — I document that workflow in &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;Next.js performance case study&lt;/a&gt;. A 10 kB store library rarely matters next to an unvirtualised table or a chart library. Still, greenfield SPAs with tight mobile budgets in India often pick Zustand because every gram of JS counts on 4G.&lt;/p&gt;
&lt;h2 id="table"&gt;10-criteria comparison table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Zustand&lt;/th&gt;
&lt;th&gt;Redux Toolkit&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boilerplate&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Structured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevTools&lt;/td&gt;
&lt;td&gt;Plugin available&lt;/td&gt;
&lt;td&gt;Excellent native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middleware&lt;/td&gt;
&lt;td&gt;Custom, light&lt;/td&gt;
&lt;td&gt;Mature ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async patterns&lt;/td&gt;
&lt;td&gt;You write it&lt;/td&gt;
&lt;td&gt;createAsyncThunk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team scale&lt;/td&gt;
&lt;td&gt;Small–medium&lt;/td&gt;
&lt;td&gt;Medium–large&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js App Router&lt;/td&gt;
&lt;td&gt;Client-only store&lt;/td&gt;
&lt;td&gt;Client-only store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selectors&lt;/td&gt;
&lt;td&gt;Inline functions&lt;/td&gt;
&lt;td&gt;reselect / createSelector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Easy store reset&lt;/td&gt;
&lt;td&gt;Well-documented patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hiring familiarity in India&lt;/td&gt;
&lt;td&gt;Growing fast&lt;/td&gt;
&lt;td&gt;Still very common&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="before-after"&gt;Before and after — prop drilling vs store&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — theme passed through five layers&lt;br&gt;
&amp;lt;Layout theme={theme} setTheme={setTheme}&amp;gt;&lt;br&gt;
  &amp;lt;Sidebar theme={theme} setTheme={setTheme}&amp;gt;&lt;br&gt;
    &amp;lt;Nav theme={theme} setTheme={setTheme} /&amp;gt;

&lt;p&gt;// AFTER — Zustand (or RTK) at leaves only&lt;br&gt;
const theme = useAppStore((s) =&amp;gt; s.theme);&lt;br&gt;
const setTheme = useAppStore((s) =&amp;gt; s.setTheme);&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not reach for a global store because props are annoying once — reach when multiple distant trees share writable state that is not server data.&lt;/p&gt;
&lt;h2 id="server-state"&gt;Server state is not Redux or Zustand&lt;/h2&gt;
&lt;p&gt;API lists, pagination, cache invalidation — use TanStack Query or Server Components + &lt;code&gt;fetch&lt;/code&gt; with cache tags. I see teams stuff fetch results into Redux out of habit; that duplicates what Next.js already solves on public pages — see &lt;a href="https://safdarali.in/blog/ssr-ssg-isr-nextjs-explained" rel="noopener noreferrer"&gt;SSR vs SSG vs ISR&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;New dashboard in 2026: Zustand for UI chrome (sidebar, filters, wizard step). RTK when joining a legacy codebase that already exports slices and middleware. At my day job, the RTK codebase had time-travel debugging worth the bytes; greenfield internal tools get Zustand in under an hour.&lt;/p&gt;
&lt;p&gt;Pair with &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC boundaries&lt;/a&gt; — stores are client-only; never import them into Server Components.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Zustand for new small/medium apps; RTK for large coordinated teams.&lt;/strong&gt; Measure bundle impact, but optimise for maintainability. Most state should stay local or on the server.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/usecallback-vs-usememo-react-guide" rel="noopener noreferrer"&gt;useCallback vs useMemo&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/tailwind-css-vs-css-modules-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;Tailwind CSS vs CSS Modules — What I Use in Production&lt;/h3&gt;
&lt;p&gt;Tailwind vs CSS Modules 2026 — side-by-side component, 8-criteria table, and what Safdar Ali ships in production.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Jun 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/free-video-thumbnail-generator-online-no-upload" rel="noopener noreferrer"&gt; &lt;h3&gt;Free Video Thumbnail Generator Online — No Upload&lt;/h3&gt;
&lt;p&gt;Free video thumbnail generator online — extract frames from video without upload. Browser-based, no watermark, YouTube thumbnail from MP4 workflow.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Jun 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zustand</category>
      <category>redux</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Tailwind CSS vs CSS Modules — What I Use in Production</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Mon, 06 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/tailwind-css-vs-css-modules-what-i-use-in-production-3cap</link>
      <guid>https://dev.to/safdarali25/tailwind-css-vs-css-modules-what-i-use-in-production-3cap</guid>
      <description>&lt;p&gt;Every new project sparks the same debate: utility classes or scoped CSS files? I ship &lt;strong&gt;Tailwind CSS&lt;/strong&gt; on most Next.js marketing sites — including &lt;a href="https://safdarali.in/" rel="noopener noreferrer"&gt;safdarali.in&lt;/a&gt; — but I still reach for &lt;strong&gt;CSS Modules&lt;/strong&gt; when design tokens, animations, or designer handoff demand real stylesheets. This &lt;strong&gt;tailwind vs css modules&lt;/strong&gt; comparison uses the same profile card in both approaches so you judge ergonomics, not different UIs.&lt;/p&gt;
&lt;h2 id="tailwind-version"&gt;Same component — Tailwind version&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// components/ProfileCard.tsx&lt;br&gt;
type Props = { name: string; role: string; avatarUrl: string };

&lt;p&gt;export function ProfileCardTailwind({ name, role, avatarUrl }: Props) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;article className="flex gap-4 rounded-2xl border border-neutral-200 bg-white p-5 shadow-sm dark:border-white/10 dark:bg-neutral-900"&amp;gt;&lt;br&gt;
      &amp;lt;img&lt;br&gt;
        src={avatarUrl}&lt;br&gt;
        alt=""&lt;br&gt;
        className="h-14 w-14 rounded-full object-cover ring-2 ring-neutral-200 dark:ring-white/20"&lt;br&gt;
      /&amp;gt;&lt;br&gt;
      &amp;lt;div&amp;gt;&lt;br&gt;
        &amp;lt;h3 className="font-semibold text-neutral-950 dark:text-white"&amp;gt;{name}&amp;lt;/h3&amp;gt;&lt;br&gt;
        &amp;lt;p className="text-sm text-neutral-600 dark:text-neutral-400"&amp;gt;{role}&amp;lt;/p&amp;gt;&lt;br&gt;
      &amp;lt;/div&amp;gt;&lt;br&gt;
    &amp;lt;/article&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No context switch — layout, colour, and dark mode sit on the JSX. Colocation is excellent when you iterate alone. Class strings get long on complex grids; I break into smaller components instead of one 200-character line.&lt;/p&gt;
&lt;p&gt;On a Bengaluru startup landing page last quarter, the designer changed card padding three times in one sprint. With Tailwind I adjusted utilities in the same PR as copy changes — no hunting a separate CSS file. That speed is why I default to utilities for marketing. The tradeoff is readability in code review: reviewers must know common class names or rely on preview deploys.&lt;/p&gt;
&lt;h2 id="modules-version"&gt;Same component — CSS Modules version&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// components/ProfileCard.module.css&lt;br&gt;
.card {&lt;br&gt;
  display: flex;&lt;br&gt;
  gap: 1rem;&lt;br&gt;
  padding: 1.25rem;&lt;br&gt;
  border-radius: 1rem;&lt;br&gt;
  border: 1px solid var(--border);&lt;br&gt;
  background: var(--surface);&lt;br&gt;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);&lt;br&gt;
}&lt;br&gt;
.avatar {&lt;br&gt;
  width: 3.5rem;&lt;br&gt;
  height: 3.5rem;&lt;br&gt;
  border-radius: 9999px;&lt;br&gt;
  object-fit: cover;&lt;br&gt;
}&lt;br&gt;
.name { font-weight: 600; }&lt;br&gt;
.role { font-size: 0.875rem; color: var(--muted); }&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// components/ProfileCard.tsx&lt;br&gt;
import styles from "./ProfileCard.module.css";

&lt;p&gt;export function ProfileCardModules({ name, role, avatarUrl }: Props) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;article className={styles.card}&amp;gt;&lt;br&gt;
      &amp;lt;img src={avatarUrl} alt="" className={styles.avatar} /&amp;gt;&lt;br&gt;
      &amp;lt;div&amp;gt;&lt;br&gt;
        &amp;lt;h3 className={styles.name}&amp;gt;{name}&amp;lt;/h3&amp;gt;&lt;br&gt;
        &amp;lt;p className={styles.role}&amp;gt;{role}&amp;lt;/p&amp;gt;&lt;br&gt;
      &amp;lt;/div&amp;gt;&lt;br&gt;
    &amp;lt;/article&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Designers reading Figma specs often prefer this — class names map to a stylesheet they can search. Theme variables live in one CSS file instead of duplicating dark: prefixes.&lt;/p&gt;
&lt;p&gt;CSS Modules shine when you inherit a brand system documented as SCSS or plain CSS. I once joined a project where every spacing token lived in variables — rewriting into Tailwind would have taken weeks for zero user benefit. Modules let us wrap legacy styles with scoped class names while new React components shipped.&lt;/p&gt;
&lt;h2 id="comparison"&gt;8-criteria comparison table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;Tailwind CSS&lt;/th&gt;
&lt;th&gt;CSS Modules&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Colocation with JSX&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Split files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design system tokens&lt;/td&gt;
&lt;td&gt;tailwind.config theme&lt;/td&gt;
&lt;td&gt;CSS variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;Purged utilities (small)&lt;/td&gt;
&lt;td&gt;Only used classes ship&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex animations&lt;/td&gt;
&lt;td&gt;Verbose in utilities&lt;/td&gt;
&lt;td&gt;Natural in CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding juniors&lt;/td&gt;
&lt;td&gt;Learn utility names&lt;/td&gt;
&lt;td&gt;Learn CSS + scoping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readable diffs&lt;/td&gt;
&lt;td&gt;Long class strings&lt;/td&gt;
&lt;td&gt;Cleaner CSS diffs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third-party overrides&lt;/td&gt;
&lt;td&gt;
&lt;a class="mentioned-user" href="https://dev.to/apply"&gt;@apply&lt;/a&gt; or arbitrary&lt;/td&gt;
&lt;td&gt;:global() when needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js default vibe&lt;/td&gt;
&lt;td&gt;Very common&lt;/td&gt;
&lt;td&gt;Built-in support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="when-tailwind-loses"&gt;When Tailwind loses&lt;/h2&gt;
&lt;p&gt;Tailwind is not weak — it is the wrong tool when the stylesheet is the product. Long-form editorial layouts, printable invoices, and white-label themes where each tenant ships different CSS files are easier to reason about in modules or global layers than in thousands of arbitrary utility strings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keyframe-heavy animations&lt;/strong&gt; — staggered reveals and complex hover chains read better in a module file. &lt;strong&gt;Print stylesheets&lt;/strong&gt; — Tailwind can do it; CSS is clearer. &lt;strong&gt;Legacy design systems&lt;/strong&gt; already documented in SCSS variables — rewriting into utilities is waste. &lt;strong&gt;Highly bespoke art-directed pages&lt;/strong&gt; where every section has unique spacing not in your token scale — fighting arbitrary values is slower than writing CSS.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* ProfileCard.module.css — animation Tailwind would fight */&lt;br&gt;
@keyframes riseIn {&lt;br&gt;
  from { opacity: 0; transform: translateY(12px); }&lt;br&gt;
  to { opacity: 1; transform: translateY(0); }&lt;br&gt;
}&lt;br&gt;
.cardAnimated {&lt;br&gt;
  animation: riseIn 0.4s ease-out both;&lt;br&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="before-after"&gt;Migrating styles — before and after&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — global CSS leaked into production&lt;br&gt;
.button {&lt;br&gt;
  background: blue;&lt;br&gt;
}&lt;br&gt;
// Every &amp;lt;button&amp;gt; on the site turned blue

&lt;p&gt;// AFTER — CSS Modules scope automatically&lt;br&gt;
// Button.module.css&lt;br&gt;
.root { background: var(--brand); }&lt;br&gt;
// Only &amp;lt;button className={styles.root}&amp;gt;&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tailwind avoids global leakage differently — utilities are atomic. Both beat unstructured global CSS from 2018 Create React App projects.&lt;/p&gt;
&lt;h2 id="hybrid"&gt;The hybrid I use on client sites&lt;/h2&gt;
&lt;p&gt;In production: Tailwind for layout, spacing, typography, responsive grids. CSS Modules (or a single globals.css) for animations, rare third-party overrides, and print rules. On a recent marketing rebuild, that split kept Lighthouse CSS payload small while designers still got a module for the hero animation — details in &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;performance case study&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;Default stack: Next.js + TypeScript strict + Tailwind + &lt;code&gt;cn()&lt;/code&gt; helper for conditional classes. I add modules per feature folder when a component accrues more than ~15 lines of custom CSS. At my day job, we banned new global selectors except resets.&lt;/p&gt;
&lt;p&gt;Neither choice fixes bad component architecture — see &lt;a href="https://safdarali.in/blog/nextjs-project-structure-guide-2026" rel="noopener noreferrer"&gt;Next.js folder structure guide&lt;/a&gt; for how I colocate styles with features.&lt;/p&gt;
&lt;p&gt;I also run Prettier with the Tailwind class sorter plugin on teams that use utilities — consistent order makes long class strings diff cleanly. CSS Module projects get stylelint for nesting and variable naming instead.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tailwind for speed; CSS Modules for complexity.&lt;/strong&gt; Pick per component, not per religion. Same card, two implementations — choose the file you will actually maintain six months from now.&lt;/p&gt;
&lt;p&gt;Interview tip I give juniors in India: neither answer is wrong in isolation. Ask what the team already uses, whether designers pair with you daily, and whether the page is marketing or a long-lived dashboard. Match the toolchain to the delivery cadence, not Twitter polls.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/projects" rel="noopener noreferrer"&gt;Projects&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/zustand-vs-redux-toolkit-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;Zustand vs Redux Toolkit — Which State Manager in 2026?&lt;/h3&gt;
&lt;p&gt;Zustand vs Redux Toolkit 2026 — comparison table, side-by-side code, bundle size, and production recommendation.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Jun 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/free-video-thumbnail-generator-online-no-upload" rel="noopener noreferrer"&gt; &lt;h3&gt;Free Video Thumbnail Generator Online — No Upload&lt;/h3&gt;
&lt;p&gt;Free video thumbnail generator online — extract frames from video without upload. Browser-based, no watermark, YouTube thumbnail from MP4 workflow.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Jun 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tailwindcss</category>
      <category>css</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>React 19 Features — What Actually Changed and What I Use</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 05 Jul 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/react-19-features-what-actually-changed-and-what-i-use-m8l</link>
      <guid>https://dev.to/safdarali25/react-19-features-what-actually-changed-and-what-i-use-m8l</guid>
      <description>&lt;p&gt;React 19 shipped a laundry list of features. Twitter threads treated every hook like mandatory. In production on client sites and this portfolio, I adopted a subset — the ones that remove real bugs or UX jank — and ignored the rest until the ecosystem caught up. This is my honest &lt;strong&gt;react 19 features&lt;/strong&gt; guide: what changed, code you can paste, and what I am still waiting on.&lt;/p&gt;
&lt;h2 id="headline-changes"&gt;What actually changed at a high level&lt;/h2&gt;
&lt;p&gt;React 19 stabilised the Actions model (forms and mutations with pending state), added &lt;code&gt;useOptimistic&lt;/code&gt; for instant UI feedback, introduced the &lt;code&gt;use()&lt;/code&gt; hook for reading promises and context, improved hydration error messages, and made ref-as-prop cleaner. The compiler (React Forget) is separate — exciting, not required to upgrade.&lt;/p&gt;
&lt;p&gt;Upgrade path: Next.js 15 projects already pin compatible React versions. Read &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components&lt;/a&gt; before mixing Actions with Server Components — boundaries still matter.&lt;/p&gt;
&lt;h2 id="hooks-table"&gt;React 19 hooks and APIs — quick reference table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Client / Server&lt;/th&gt;
&lt;th&gt;I use in prod?&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;useOptimistic&lt;/td&gt;
&lt;td&gt;Optimistic UI while mutation runs&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;use()&lt;/td&gt;
&lt;td&gt;Read promise or context&lt;/td&gt;
&lt;td&gt;Both (with Suspense)&lt;/td&gt;
&lt;td&gt;Yes (with RSC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;useActionState&lt;/td&gt;
&lt;td&gt;Form action state&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;useFormStatus&lt;/td&gt;
&lt;td&gt;Pending from parent form&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ref as prop&lt;/td&gt;
&lt;td&gt;No forwardRef boilerplate&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Gradual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document metadata&lt;/td&gt;
&lt;td&gt;title, meta in components&lt;/td&gt;
&lt;td&gt;Client (limited)&lt;/td&gt;
&lt;td&gt;Prefer Next.js metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="use-optimistic"&gt;useOptimistic — instant feedback without lying to the user forever&lt;/h2&gt;
&lt;p&gt;Cart quantity updates, like buttons, todo toggles — users expect instant UI. &lt;code&gt;useOptimistic&lt;/code&gt; shows the next state while the server catches up, then reconciles on success or rolls back on error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;"use client";&lt;br&gt;
import { useOptimistic, useTransition } from "react";&lt;br&gt;
import { updateQuantity } from "./actions";

&lt;p&gt;type Item = { id: string; qty: number };&lt;/p&gt;

&lt;p&gt;export function CartLine({ item }: { item: Item }) {&lt;br&gt;
  const [optimisticQty, setOptimisticQty] = useOptimistic(item.qty);&lt;br&gt;
  const [isPending, startTransition] = useTransition();&lt;/p&gt;

&lt;p&gt;function changeQty(next: number) {&lt;br&gt;
    startTransition(async () =&amp;gt; {&lt;br&gt;
      setOptimisticQty(next);&lt;br&gt;
      await updateQuantity(item.id, next);&lt;br&gt;
    });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &amp;lt;div&amp;gt;&lt;br&gt;
      &amp;lt;button onClick={() =&amp;gt; changeQty(optimisticQty + 1)} disabled={isPending}&amp;gt;&lt;br&gt;
        +&lt;br&gt;
      &amp;lt;/button&amp;gt;&lt;br&gt;
      &amp;lt;span&amp;gt;{optimisticQty}&amp;lt;/span&amp;gt;&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — manual optimistic state with footguns&lt;br&gt;
const [qty, setQty] = useState(item.qty);&lt;br&gt;
const [pending, setPending] = useState(false);

&lt;p&gt;async function bump() {&lt;br&gt;
  const prev = qty;&lt;br&gt;
  setQty(qty + 1); // optimistic&lt;br&gt;
  setPending(true);&lt;br&gt;
  try {&lt;br&gt;
    await updateQuantity(item.id, qty + 1);&lt;br&gt;
  } catch {&lt;br&gt;
    setQty(prev); // easy to forget rollback paths&lt;br&gt;
  } finally {&lt;br&gt;
    setPending(false);&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// AFTER — useOptimistic + transition: rollback wired correctly&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="use-hook"&gt;use() — promises and context without useEffect hacks&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Server Component passes a promise to client child&lt;br&gt;
import { use } from "react";

&lt;p&gt;type Product = { id: string; name: string };&lt;/p&gt;

&lt;p&gt;function ProductList({ productsPromise }: { productsPromise: Promise&amp;lt;Product[]&amp;gt; }) {&lt;br&gt;
  const products = use(productsPromise); // suspends until resolved&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;ul&amp;gt;&lt;br&gt;
      {products.map((p) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Parent (Server Component) creates the promise once&lt;br&gt;
export default function Page() {&lt;br&gt;
  const productsPromise = getProducts(); // do not await here&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading products…&amp;lt;/p&amp;gt;}&amp;gt;&lt;br&gt;
      &amp;lt;ProductList productsPromise={productsPromise} /&amp;gt;&lt;br&gt;
    &amp;lt;/Suspense&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In production I use &lt;code&gt;use()&lt;/code&gt; with Server Components streaming data — same mental model as async server fetch, less client &lt;code&gt;useEffect&lt;/code&gt; spaghetti.&lt;/p&gt;
&lt;h2 id="actions"&gt;Actions and forms — less boilerplate than manual fetch&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;"use client";&lt;br&gt;
import { useActionState } from "react";&lt;br&gt;
import { subscribe } from "./actions";

&lt;p&gt;export function NewsletterForm() {&lt;br&gt;
  const [state, formAction, pending] = useActionState(subscribe, { ok: false, message: "" });&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &amp;lt;form action={formAction}&amp;gt;&lt;br&gt;
      &amp;lt;input name="email" type="email" required /&amp;gt;&lt;br&gt;
      &amp;lt;button disabled={pending}&amp;gt;{pending ? "Sending…" : "Subscribe"}&amp;lt;/button&amp;gt;&lt;br&gt;
      {state.message &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{state.message}&amp;lt;/p&amp;gt;}&lt;br&gt;
    &amp;lt;/form&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pair with Next.js Server Actions for mutations without a separate API route file — still validate on the server, still treat client state as untrusted.&lt;/p&gt;
&lt;h2 id="adopted"&gt;What I immediately adopted&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;useOptimistic&lt;/strong&gt; on any user-facing mutation where latency is felt on Indian mobile networks. &lt;strong&gt;useActionState / useFormStatus&lt;/strong&gt; on marketing forms — fewer lines than custom pending flags. &lt;strong&gt;use()&lt;/strong&gt; with Suspense boundaries on catalog sections fed from server promises. &lt;strong&gt;Better hydration errors&lt;/strong&gt; — saved me an hour debugging a client-only chart imported into a Server Component (fixed by splitting the leaf).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ref as prop — dropped forwardRef on new components&lt;br&gt;
type ButtonProps = React.ComponentProps&amp;lt;"button"&amp;gt; &amp;amp; { ref?: React.Ref&amp;lt;HTMLButtonElement&amp;gt; };

&lt;p&gt;export function Button({ ref, ...props }: ButtonProps) {&lt;br&gt;
  return &amp;lt;button ref={ref} {...props} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="waiting"&gt;What I'm waiting on&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;React Compiler (Forget)&lt;/strong&gt; — I will enable it per-route after stable Next.js integration docs, not on day one of React 19. &lt;strong&gt;Document metadata in client trees&lt;/strong&gt; — I still use Next.js &lt;code&gt;generateMetadata&lt;/code&gt; for SEO. &lt;strong&gt;Full ecosystem typings&lt;/strong&gt; — some third-party libs lagged React 19 types for weeks; I pinned versions until they caught up.&lt;/p&gt;
&lt;p&gt;I am also not rewriting every &lt;code&gt;forwardRef&lt;/code&gt; component overnight — new code uses ref-as-prop; old code migrates on touch.&lt;/p&gt;
&lt;p&gt;Waiting is a strategy, not laziness. The compiler will change how much manual memo we write — see my &lt;a href="https://safdarali.in/blog/usecallback-vs-usememo-react-guide" rel="noopener noreferrer"&gt;useCallback vs useMemo guide&lt;/a&gt; for why I am not adding more memo hooks while the ecosystem catches up.&lt;/p&gt;
&lt;h2 id="production"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production: React 19 + Next.js 15, Server Components by default, React 19 Actions on forms that need pending UX, optimistic updates on commerce interactions. Performance work still lives in caching and bundle size — see &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;Next.js performance case study&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When experimenting, I use the workflow in &lt;a href="https://safdarali.in/blog/cursor-claude-react-workflow-2026" rel="noopener noreferrer"&gt;Cursor + Claude for React&lt;/a&gt; — AI suggests React 19 APIs quickly, but I verify against official release notes before merge.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;React 19 is not a rewrite mandate.&lt;/strong&gt; Adopt optimistic UI, Actions, and &lt;code&gt;use()&lt;/code&gt; where they solve problems you already have. Wait on compiler and metadata experiments until your stack documents them.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/nextjs-vs-react-which-to-learn-2026" rel="noopener noreferrer"&gt;Next.js vs React learning path&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/frontend-developer-portfolio-guide-india-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;How to Build a Frontend Developer Portfolio That Stands Out&lt;/h3&gt;
&lt;p&gt;Frontend developer portfolio guide for India — sections, React/Next.js examples, SEO, performance, personal branding, FAQ, and checklist to build and rank.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt; &lt;h3&gt;React Server Components vs Client Components — When to Use Which&lt;/h3&gt;
&lt;p&gt;Practical RSC vs client guide for Next.js App Router — when to use each, real code, bundle before/after, and performance impact.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>SSR vs SSG vs ISR in Next.js — Plain English Explanation</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 05 Jul 2026 15:42:29 +0000</pubDate>
      <link>https://dev.to/safdarali25/ssr-vs-ssg-vs-isr-in-nextjs-plain-english-explanation-12mo</link>
      <guid>https://dev.to/safdarali25/ssr-vs-ssg-vs-isr-in-nextjs-plain-english-explanation-12mo</guid>
      <description>&lt;p&gt;I'm &lt;a href="https://safdarali.in/about" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;. For six months I treated every Next.js page like SSR because "server" sounded safest. Marketing pages were slower than they needed to be; dashboard pages were over-cached. &lt;strong&gt;SSR vs SSG in Next.js&lt;/strong&gt; is not a popularity contest — it is a delivery choice: when HTML is built, how often it refreshes, and who pays the compute bill. This article is the plain-English map I wish I had, with code for all three modes and a flowchart you can screenshot.&lt;/p&gt;
&lt;h2 id="definitions"&gt;Three acronyms, one question: when is HTML built?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SSR (Server-Side Rendering)&lt;/strong&gt; builds HTML on each request (or per-request cache miss). Fresh data, higher server load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSG (Static Site Generation)&lt;/strong&gt; builds HTML at deploy time. Fastest CDN delivery; stale until you redeploy unless you add revalidation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ISR (Incremental Static Regeneration)&lt;/strong&gt; is SSG plus background refresh — static speed with a TTL. In App Router this is mostly &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;next.revalidate&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Mental model — same page, three delivery timings

&lt;p&gt;// SSG:  HTML built at deploy ──────────────────► CDN serves file&lt;br&gt;
// ISR:  HTML built at deploy ──► stale ──► regen in background after N sec&lt;br&gt;
// SSR:  HTML built when user hits URL ─────────► server sends fresh HTML&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;None of these replace React. They describe when your React tree becomes HTML the browser can paint before hydration.&lt;/p&gt;
&lt;h2 id="flowchart"&gt;Decision flowchart — which mode to pick&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;                    START: New Next.js page&lt;br&gt;
                              |&lt;br&gt;
                              v&lt;br&gt;
                    Does Google need HTML&lt;br&gt;
                    with real content on&lt;br&gt;
                    first request?&lt;br&gt;
                         /        \&lt;br&gt;
                       NO          YES&lt;br&gt;
                       |            |&lt;br&gt;
                       v            v&lt;br&gt;
              Authenticated      Is data identical&lt;br&gt;
              dashboard / SPA?   for all users?&lt;br&gt;
                    |                 /      \&lt;br&gt;
                   YES               NO      YES&lt;br&gt;
                    |                |        |&lt;br&gt;
                    v                v        v&lt;br&gt;
            "use client" +       SSR or      Changes&lt;br&gt;
            client fetch         per-user    every hour+&lt;br&gt;
            (no SSR win)         data?          /    \&lt;br&gt;
                                /    \        NO    YES&lt;br&gt;
                              YES    NO        |      |&lt;br&gt;
                               |      |        v      v&lt;br&gt;
                               v      v       SSG    ISR&lt;br&gt;
                              SSR   ISR      (rare) (revalidate)&lt;br&gt;
                                    or SSG&lt;br&gt;
                                    + short&lt;br&gt;
                                    revalidate&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Print that flowchart. When you are unsure, default marketing to ISR with a sensible &lt;code&gt;revalidate&lt;/code&gt;, default logged-in dashboards to client rendering.&lt;/p&gt;
&lt;h2 id="ssg-code"&gt;SSG — build once, serve from the edge&lt;/h2&gt;
&lt;p&gt;Use SSG for pages where data changes only when you deploy — legal pages, about pages, rarely updated landing heroes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/about/page.tsx — static by default (no fetch cache opts)&lt;br&gt;
export default function AboutPage() {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;main&amp;gt;&lt;br&gt;
      &amp;lt;h1&amp;gt;About Safdar Ali&amp;lt;/h1&amp;gt;&lt;br&gt;
      &amp;lt;p&amp;gt;Frontend engineer, Bengaluru — available for React / Next.js work.&amp;lt;/p&amp;gt;&lt;br&gt;
    &amp;lt;/main&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}

&lt;p&gt;// Dynamic routes can still be SSG if you export generateStaticParams&lt;br&gt;
export async function generateStaticParams() {&lt;br&gt;
  const slugs = await getAllBlogSlugs();&lt;br&gt;
  return slugs.map((slug) =&amp;gt; ({ slug }));&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tradeoff: a price change on an e-commerce SKU list will not appear until redeploy or ISR kicks in. For truly static content, that is a feature — zero origin load.&lt;/p&gt;
&lt;h2 id="isr-code"&gt;ISR — static speed, controlled freshness&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/products/page.tsx — ISR via fetch revalidate (App Router)&lt;br&gt;
type Product = { id: string; name: string; price: number };

&lt;p&gt;async function getProducts(): Promise&amp;lt;Product[]&amp;gt; {&lt;br&gt;
  const res = await fetch("&lt;a href="https://api.example.com/products" rel="noopener noreferrer"&gt;https://api.example.com/products&lt;/a&gt;", {&lt;br&gt;
    next: { revalidate: 3600 }, // ISR: refresh at most every hour&lt;br&gt;
  });&lt;br&gt;
  if (!res.ok) throw new Error("Failed to load products");&lt;br&gt;
  return res.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export default async function ProductsPage() {&lt;br&gt;
  const products = await getProducts();&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;ul&amp;gt;&lt;br&gt;
      {products.map((p) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={p.id}&amp;gt;{p.name} — ₹{p.price}&amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First visitor after TTL might still see stale HTML while regeneration runs — acceptable for catalog pages, unacceptable for live stock tickers. Know your freshness requirement before picking ISR.&lt;/p&gt;
&lt;h2 id="ssr-code"&gt;SSR — per-request HTML when data must be fresh&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// app/dashboard/summary/page.tsx — SSR: no cache on fetch&lt;br&gt;
async function getSummary(userId: string) {&lt;br&gt;
  const res = await fetch("&lt;a href="https://api.example.com/summary/" rel="noopener noreferrer"&gt;https://api.example.com/summary/&lt;/a&gt;" + userId, {&lt;br&gt;
    cache: "no-store", // SSR — always fresh for this user&lt;br&gt;
  });&lt;br&gt;
  return res.json();&lt;br&gt;
}

&lt;p&gt;export default async function SummaryPage() {&lt;br&gt;
  const session = await getSession();&lt;br&gt;
  const summary = await getSummary(session.userId);&lt;br&gt;
  return &amp;lt;SummaryClient data={summary} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SSR costs more at scale. Use it when personalisation or real-time correctness beats CDN economics — not because "server sounds professional."&lt;/p&gt;
&lt;h2 id="comparison"&gt;SSR vs SSG vs ISR — comparison table&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;SSG&lt;/th&gt;
&lt;th&gt;ISR&lt;/th&gt;
&lt;th&gt;SSR&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When HTML is built&lt;/td&gt;
&lt;td&gt;Deploy time&lt;/td&gt;
&lt;td&gt;Deploy + periodic regen&lt;/td&gt;
&lt;td&gt;Each request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTFB on CDN&lt;/td&gt;
&lt;td&gt;Fastest&lt;/td&gt;
&lt;td&gt;Fast (often cached)&lt;/td&gt;
&lt;td&gt;Slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data freshness&lt;/td&gt;
&lt;td&gt;Stale until deploy&lt;/td&gt;
&lt;td&gt;TTL-based&lt;/td&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server cost&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;Low–medium&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-user data&lt;/td&gt;
&lt;td&gt;Poor fit&lt;/td&gt;
&lt;td&gt;Poor fit&lt;/td&gt;
&lt;td&gt;Good fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App Router signal&lt;/td&gt;
&lt;td&gt;Default static page&lt;/td&gt;
&lt;td&gt;revalidate: N&lt;/td&gt;
&lt;td&gt;cache: no-store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2 id="marketing-vs-dashboard"&gt;Marketing site vs dashboard — what I actually ship&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Marketing site&lt;/strong&gt; (public, SEO, shared content): ISR for blog and product listings, SSG for about/legal, Server Components for HTML. On a client rebuild I moved the blog to &lt;code&gt;revalidate: 86400&lt;/code&gt; and saw origin requests drop 70% — part of the story in &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;my Next.js performance case study&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dashboard&lt;/strong&gt; (authenticated, personal): client components, SWR or WebSockets, no ISR fantasy. Trying to ISR user-specific charts is how teams waste a sprint.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — marketing page forced to SSR "just because"&lt;br&gt;
export default async function PricingPage() {&lt;br&gt;
  const plans = await fetch("&lt;a href="https://api.example.com/plans" rel="noopener noreferrer"&gt;https://api.example.com/plans&lt;/a&gt;", { cache: "no-store" });&lt;br&gt;
  // Every visitor hits origin — same JSON for everyone&lt;br&gt;
}

&lt;p&gt;// AFTER — ISR: shared plans, CDN-friendly&lt;br&gt;
export default async function PricingPage() {&lt;br&gt;
  const res = await fetch("&lt;a href="https://api.example.com/plans" rel="noopener noreferrer"&gt;https://api.example.com/plans&lt;/a&gt;", { next: { revalidate: 3600 } });&lt;br&gt;
  const plans = await res.json();&lt;br&gt;
  // ...&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="personal"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production I label routes in file comments — &lt;code&gt;ISR 1h&lt;/code&gt; or &lt;code&gt;SSR session&lt;/code&gt; — so the next developer does not "optimise" a dashboard into ISR by accident. Pair this with &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components&lt;/a&gt; — rendering mode and component boundary are two separate decisions.&lt;/p&gt;
&lt;p&gt;At my day job, the mistake I see most is SSR everywhere because the team learned Pages Router &lt;code&gt;getServerSideProps&lt;/code&gt; first. App Router caching is more granular — use it.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pick rendering by freshness and audience, not acronym prestige.&lt;/strong&gt; Marketing: ISR + SSG. Dashboards: client. Personalised SSR only when you mean it.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/nextjs-app-router-complete-guide-2026" rel="noopener noreferrer"&gt;App Router complete beginner guide&lt;/a&gt;. &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/frontend-developer-portfolio-guide-india-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;How to Build a Frontend Developer Portfolio That Stands Out&lt;/h3&gt;
&lt;p&gt;Frontend developer portfolio guide for India — sections, React/Next.js examples, SEO, performance, personal branding, FAQ, and checklist to build and rank.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt; &lt;h3&gt;React Server Components vs Client Components — When to Use Which&lt;/h3&gt;
&lt;p&gt;Practical RSC vs client guide for Next.js App Router — when to use each, real code, bundle before/after, and performance impact.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssr</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TypeScript Strict Mode — Why I Use It in Every Project</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:07:05 +0000</pubDate>
      <link>https://dev.to/safdarali25/typescript-strict-mode-why-i-use-it-in-every-project-457</link>
      <guid>https://dev.to/safdarali25/typescript-strict-mode-why-i-use-it-in-every-project-457</guid>
      <description>&lt;p&gt;I'm &lt;a href="https://safdarali.in/about" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;, a frontend engineer in Bengaluru. Three years ago I turned off strict mode on a client dashboard because the migration felt noisy. Two weeks later, a user with an empty profile name crashed the settings page in production — exactly the class of bug &lt;code&gt;strictNullChecks&lt;/code&gt; would have flagged at compile time. Since then, &lt;strong&gt;typescript strict mode&lt;/strong&gt; is non-negotiable on every React and Next.js repo I touch. This guide is the tsconfig I copy, the flags I care about, and the errors that actually save you hours.&lt;/p&gt;
&lt;h2 id="what-strict"&gt;What "strict mode" actually means in TypeScript&lt;/h2&gt;
&lt;p&gt;Strict mode is not one switch — it is a family of compiler checks bundled under &lt;code&gt;strict: true&lt;/code&gt; in &lt;code&gt;tsconfig.json&lt;/code&gt;. When enabled, TypeScript refuses code that relies on implicit any, unchecked null access, or loose function types.&lt;/p&gt;
&lt;p&gt;The tradeoff is upfront friction: your first week on an old JavaScript codebase will surface hundreds of errors. The payoff is fewer 2am Slack messages and safer refactors when you rename a prop across forty components.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// tsconfig.json — baseline I paste into new Next.js projects&lt;br&gt;
{&lt;br&gt;
  "compilerOptions": {&lt;br&gt;
    "target": "ES2017",&lt;br&gt;
    "lib": ["dom", "dom.iterable", "esnext"],&lt;br&gt;
    "allowJs": true,&lt;br&gt;
    "skipLibCheck": true,&lt;br&gt;
    "strict": true,&lt;br&gt;
    "noEmit": true,&lt;br&gt;
    "esModuleInterop": true,&lt;br&gt;
    "module": "esnext",&lt;br&gt;
    "moduleResolution": "bundler",&lt;br&gt;
    "resolveJsonModule": true,&lt;br&gt;
    "isolatedModules": true,&lt;br&gt;
    "jsx": "preserve",&lt;br&gt;
    "incremental": true,&lt;br&gt;
    "plugins": [{ "name": "next" }],&lt;br&gt;
    "paths": { "@/&lt;em&gt;": ["./&lt;/em&gt;"] }&lt;br&gt;
  },&lt;br&gt;
  "include": ["next-env.d.ts", "&lt;strong&gt;/*.ts", "&lt;/strong&gt;/&lt;em&gt;.tsx", ".next/types/&lt;/em&gt;&lt;em&gt;/&lt;/em&gt;.ts"],&lt;br&gt;
  "exclude": ["node_modules"]&lt;br&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;strict: true&lt;/code&gt; turns on &lt;code&gt;strictNullChecks&lt;/code&gt;, &lt;code&gt;noImplicitAny&lt;/code&gt;, and several related flags. You can enable them individually, but I have never found a good reason to run half-strict on a greenfield app.&lt;/p&gt;
&lt;h2 id="flags-table"&gt;Strict flags — what each one does&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;th&gt;Pain level&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;strictNullChecks&lt;/td&gt;
&lt;td&gt;null / undefined used as values&lt;/td&gt;
&lt;td&gt;High on legacy APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noImplicitAny&lt;/td&gt;
&lt;td&gt;Parameters without types&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strictFunctionTypes&lt;/td&gt;
&lt;td&gt;Unsafe callback assignments&lt;/td&gt;
&lt;td&gt;Low–medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strictBindCallApply&lt;/td&gt;
&lt;td&gt;Wrong bind/call/apply args&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noImplicitThis&lt;/td&gt;
&lt;td&gt;Ambiguous this in functions&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alwaysStrict&lt;/td&gt;
&lt;td&gt;Emits "use strict" per file&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;useUnknownInCatchVariables&lt;/td&gt;
&lt;td&gt;catch (e) typed as any&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;If you are migrating an old repo, enable flags one at a time in CI — fix &lt;code&gt;noImplicitAny&lt;/code&gt; first, then &lt;code&gt;strictNullChecks&lt;/code&gt;. That order reduces the simultaneous error flood.&lt;/p&gt;
&lt;h2 id="three-errors"&gt;The 3 errors strict mode catches most often&lt;/h2&gt;
&lt;p&gt;After dozens of code reviews, these three patterns account for most of the bugs strict mode prevented before merge — not theoretical type pedantry, real user-facing failures.&lt;/p&gt;
&lt;h3&gt;1. Possibly undefined property access&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;type User = { id: string; displayName?: string };

&lt;p&gt;function greet(user: User) {&lt;br&gt;
  // Error under strictNullChecks: 'displayName' is possibly 'undefined'&lt;br&gt;
  return "Hello, " + user.displayName.toUpperCase();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Fix — narrow before use&lt;br&gt;
function greetSafe(user: User) {&lt;br&gt;
  const name = user.displayName ?? "Guest";&lt;br&gt;
  return "Hello, " + name.toUpperCase();&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Implicit any on event handlers&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — noImplicitAny flags the parameter&lt;br&gt;
function handleChange(e) {&lt;br&gt;
  setQuery(e.target.value);&lt;br&gt;
}

&lt;p&gt;// AFTER — explicit React type&lt;br&gt;
import type { ChangeEvent } from "react";&lt;/p&gt;

&lt;p&gt;function handleChange(e: ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) {&lt;br&gt;
  setQuery(e.target.value);&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Wrong API response shape assumed&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;type ApiOrder = { id: string; total: number; discount?: number };

&lt;p&gt;async function getOrder(id: string): Promise&amp;lt;ApiOrder&amp;gt; {&lt;br&gt;
  const res = await fetch("/api/orders/" + id);&lt;br&gt;
  return res.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function OrderSummary({ order }: { order: ApiOrder }) {&lt;br&gt;
  // strictNullChecks: discount may be undefined&lt;br&gt;
  const saved = order.discount ?? 0;&lt;br&gt;
  return &amp;lt;p&amp;gt;Total: ₹{order.total - saved}&amp;lt;/p&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Number three is why I type API responses at the boundary — not inside every child component. One wrong assumption on &lt;code&gt;fetch&lt;/code&gt; results propagates silently without strict types.&lt;/p&gt;
&lt;h2 id="bug-story"&gt;The production bug strict mode would have caught&lt;/h2&gt;
&lt;p&gt;At my day job, a checkout helper read &lt;code&gt;user.address.line2&lt;/code&gt; without checking if address existed. Indian users who signed up with phone-only onboarding had &lt;code&gt;address: null&lt;/code&gt;. The page white-screened on submit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — passed review, failed in production&lt;br&gt;
type User = { id: string; address: { line1: string; line2?: string } | null };

&lt;p&gt;function formatShipping(user: User) {&lt;br&gt;
  return user.address.line1 + ", " + user.address.line2;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// AFTER — compiler forces the guard&lt;br&gt;
function formatShipping(user: User) {&lt;br&gt;
  if (!user.address) return "Address required";&lt;br&gt;
  const line2 = user.address.line2 ?? "";&lt;br&gt;
  return user.address.line1 + (line2 ? ", " + line2 : "");&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We added the guard in a hotfix. With strict null checks enabled from day one, the first version would never have compiled. That single incident paid for every strict migration I have done since.&lt;/p&gt;
&lt;h2 id="before-after"&gt;Migrating a component — before and after strict&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — loose props, any sneaks in&lt;br&gt;
export function ProductCard(props) {&lt;br&gt;
  const { title, price, onAdd } = props;&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;button onClick={() =&amp;gt; onAdd(title)}&amp;gt;&lt;br&gt;
      {title} — ₹{price}&lt;br&gt;
    &amp;lt;/button&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}

&lt;p&gt;// AFTER — explicit contract&lt;br&gt;
type ProductCardProps = {&lt;br&gt;
  title: string;&lt;br&gt;
  price: number;&lt;br&gt;
  onAdd: (title: string) =&amp;gt; void;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;export function ProductCard({ title, price, onAdd }: ProductCardProps) {&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;button onClick={() =&amp;gt; onAdd(title)}&amp;gt;&lt;br&gt;
      {title} — ₹{price}&lt;br&gt;
    &amp;lt;/button&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The after version is longer by four lines. It is also grep-friendly: rename a prop and TypeScript lists every broken callsite instead of leaving undefined at runtime.&lt;/p&gt;
&lt;h2 id="with-nextjs"&gt;Strict mode with Next.js App Router&lt;/h2&gt;
&lt;p&gt;Next.js 15 projects ship with TypeScript by default. Server Components add one wrinkle: props and &lt;code&gt;params&lt;/code&gt; are often &lt;code&gt;Promise&lt;/code&gt;-wrapped. Strict typing there prevents awaiting the wrong shape.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/blog/[slug]/page.tsx&lt;br&gt;
type PageProps = { params: Promise&amp;lt;{ slug: string }&amp;gt; };

&lt;p&gt;export default async function BlogPostPage({ params }: PageProps) {&lt;br&gt;
  const { slug } = await params;&lt;br&gt;
  const post = await getPost(slug);&lt;br&gt;
  if (!post) return null;&lt;br&gt;
  return &amp;lt;Article post={post} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pair strict TypeScript with Server Components discipline from my &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components guide&lt;/a&gt; — types tell you what runs where; boundaries tell you what ships to the browser.&lt;/p&gt;
&lt;h2 id="when-relax"&gt;When I temporarily relax a rule (rarely)&lt;/h2&gt;
&lt;p&gt;Third-party libraries with broken types sometimes need &lt;code&gt;@ts-expect-error&lt;/code&gt; on one line — not &lt;code&gt;strict: false&lt;/code&gt; for the whole project. I also use &lt;code&gt;skipLibCheck: true&lt;/code&gt; so node_modules type noise does not block builds — that is standard, not cheating.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Scoped escape — document why&lt;br&gt;
// @ts-expect-error legacy chart lib ships wrong types until v4&lt;br&gt;
&amp;lt;LegacyChart data={metrics} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What I do not do: disable strict for "speed." Typing saves more calendar time than it costs once the team is past the first migration week.&lt;/p&gt;
&lt;h2 id="production-setup"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production I run &lt;code&gt;strict: true&lt;/code&gt;, ESLint &lt;code&gt;@typescript-eslint/recommended&lt;/code&gt;, and &lt;code&gt;tsc --noEmit&lt;/code&gt; in CI before every merge. On this portfolio and client marketing sites, that combo caught unused env vars and wrong metadata types before they hit Vercel.&lt;/p&gt;
&lt;p&gt;When I use AI-assisted refactors, I still read the diff — see my &lt;a href="https://safdarali.in/blog/cursor-claude-react-workflow-2026" rel="noopener noreferrer"&gt;Cursor + Claude workflow&lt;/a&gt; — but TypeScript strict mode is the safety net when autocomplete hallucinates a prop name.&lt;/p&gt;
&lt;p&gt;Junior developers in Bengaluru often ask if strict is "for senior devs." It is the opposite: strict mode teaches you the shape of data before runtime teaches you with user complaints.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TypeScript strict mode is cheap insurance.&lt;/strong&gt; Turn it on in &lt;code&gt;tsconfig.json&lt;/code&gt;, fix errors in order, and let the compiler catch null access, implicit any, and API drift before your users do.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href="https://safdarali.in/blog/nextjs-vs-react-which-to-learn-2026" rel="noopener noreferrer"&gt;Next.js vs React — what to learn first&lt;/a&gt;. Performance: &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;How I cut load time by 60% with Next.js&lt;/a&gt;. Questions: &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;safdarali.in/contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="related-posts-heading"&gt;Related reading&lt;/h2&gt;
&lt;p&gt;More guides on safdarali.in — same author, production-focused.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/frontend-developer-portfolio-guide-india-2026" rel="noopener noreferrer"&gt; &lt;h3&gt;How to Build a Frontend Developer Portfolio That Stands Out&lt;/h3&gt;
&lt;p&gt;Frontend developer portfolio guide for India — sections, React/Next.js examples, SEO, performance, personal branding, FAQ, and checklist to build and rank.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt; &lt;h3&gt;React Server Components vs Client Components — When to Use Which&lt;/h3&gt;
&lt;p&gt;Practical RSC vs client guide for Next.js App Router — when to use each, real code, bundle before/after, and performance impact.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;May 2026&lt;/span&gt;&lt;span&gt; · &lt;/span&gt;Read article →&lt;/p&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Next.js vs React — Which Should You Learn First in 2026?</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 05 Jul 2026 13:44:22 +0000</pubDate>
      <link>https://dev.to/safdarali25/nextjs-vs-react-which-should-you-learn-first-in-2026-2g0m</link>
      <guid>https://dev.to/safdarali25/nextjs-vs-react-which-should-you-learn-first-in-2026-2g0m</guid>
      <description>&lt;p&gt;I'm &lt;a href="https://safdarali.in/about" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;, a frontend engineer in Bengaluru. Last month a junior dev on LinkedIn asked me the same question I heard in 2022, 2024, and again this week: "Should I learn React or jump straight to Next.js?" After four years shipping both — marketing sites, dashboards, and this portfolio on &lt;a href="https://safdarali.in" rel="noopener noreferrer"&gt;safdarali.in&lt;/a&gt; — my answer is not "always Next.js." It's learn React first, then Next.js fast. Here's why, with code you can paste today.&lt;/p&gt;
&lt;h2 id="not-the-same"&gt;React and Next.js are not the same thing — stop comparing them like they are&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;React&lt;/strong&gt; is a UI library. It handles components, state, and rendering. It does not ship routing, data fetching conventions, or a production server model out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt; is a React framework. It sits on top of React and adds file-based routing, server rendering, image optimisation, and deployment defaults. When people search "nextjs vs react 2026," they usually mean "plain React + Vite" vs "Next.js App Router."&lt;/p&gt;
&lt;p&gt;The tradeoff is clear: React alone gives you control. Next.js gives you speed to production — if you already understand what React is doing under the hood.&lt;/p&gt;
&lt;p&gt;Think of it like driving. React is learning how an engine, steering, and brakes work. Next.js is a car that already has those parts assembled — plus GPS, airbags, and a service schedule. You can build a car from parts (React + Vite + React Router + your own SSR layer). Most product teams in 2026 don't — they buy the assembled car and focus on where they're going.&lt;/p&gt;
&lt;p&gt;That analogy breaks if you don't know what an engine is. Jump into Next.js without JSX, props, or state fundamentals and every error message feels like framework magic instead of JavaScript.&lt;/p&gt;
&lt;h2 id="same-feature"&gt;Same feature, two stacks — a product list page side by side&lt;/h2&gt;
&lt;p&gt;Take a simple product list: fetch data, render cards, link to detail pages. Here's the split between a typical React + client fetch setup and Next.js App Router with a Server Component.&lt;/p&gt;
&lt;h3&gt;React (Vite + client fetch) — you wire everything&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/ProductList.tsx — runs in the browser only&lt;br&gt;
import { useEffect, useState } from "react";

&lt;p&gt;type Product = { id: string; name: string; price: number };&lt;/p&gt;

&lt;p&gt;export function ProductList() {&lt;br&gt;
  const [products, setProducts] = useState&amp;lt;Product[]&amp;gt;([]);&lt;br&gt;
  const [loading, setLoading] = useState(true);&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
    // SEO problem: crawlers see empty HTML until JS runs&lt;br&gt;
    fetch("/api/products")&lt;br&gt;
      .then((res) =&amp;gt; res.json())&lt;br&gt;
      .then(setProducts)&lt;br&gt;
      .finally(() =&amp;gt; setLoading(false));&lt;br&gt;
  }, []);&lt;/p&gt;

&lt;p&gt;if (loading) return &amp;lt;p&amp;gt;Loading…&amp;lt;/p&amp;gt;;&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &amp;lt;ul&amp;gt;&lt;br&gt;
      {products.map((p) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={p.id}&amp;gt;{p.name} — ₹{p.price}&amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Next.js App Router — server fetch, less client JS&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// app/products/page.tsx — Server Component by default&lt;br&gt;
type Product = { id: string; name: string; price: number };

&lt;p&gt;async function getProducts(): Promise&amp;lt;Product[]&amp;gt; {&lt;br&gt;
  const res = await fetch("&lt;a href="https://api.example.com/products" rel="noopener noreferrer"&gt;https://api.example.com/products&lt;/a&gt;", {&lt;br&gt;
    next: { revalidate: 3600 }, // ISR-style cache — fresh enough for catalog pages&lt;br&gt;
  });&lt;br&gt;
  if (!res.ok) throw new Error("Failed to load products");&lt;br&gt;
  return res.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export default async function ProductsPage() {&lt;br&gt;
  const products = await getProducts();&lt;/p&gt;

&lt;p&gt;// HTML arrives with content — better LCP, better SEO&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;ul&amp;gt;&lt;br&gt;
      {products.map((p) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={p.id}&amp;gt;{p.name} — ₹{p.price}&amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same UI. Different delivery model. On a client marketing site I migrated to App Router, that shift alone contributed to LCP dropping from 4.2s to 1.7s — I break down the full stack in my &lt;a href="https://safdarali.in/blog/nextjs-performance-60-percent" rel="noopener noreferrer"&gt;Next.js performance case study&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the React version, you also own routing. You'd add React Router, configure a layout wrapper, and probably a loading skeleton component. None of that is hard — it's just three extra files before you write business logic. Next.js collapses routing into folder names: &lt;code&gt;app/products/page.tsx&lt;/code&gt; is both the route and the page. After four years of shipping React, that convention still saves me time on every new project.&lt;/p&gt;
&lt;p&gt;The React approach wins when your API is a separate team's problem and you only care about the browser. Next.js wins when HTML on first request matters — SEO, social previews, slow 4G in tier-2 Indian cities.&lt;/p&gt;
&lt;h2 id="comparison-table"&gt;Next.js vs React — 10 criteria compared&lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Criteria&lt;/th&gt;
&lt;th&gt;React (Vite / CRA-style)&lt;/th&gt;
&lt;th&gt;Next.js (App Router)&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;UI library&lt;/td&gt;
&lt;td&gt;Full React framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;You add React Router&lt;/td&gt;
&lt;td&gt;File-based, built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO / first paint&lt;/td&gt;
&lt;td&gt;CSR unless you add SSR&lt;/td&gt;
&lt;td&gt;SSR, SSG, ISR native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data fetching&lt;/td&gt;
&lt;td&gt;useEffect + fetch (client)&lt;/td&gt;
&lt;td&gt;async Server Components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Lower entry, higher assembly&lt;/td&gt;
&lt;td&gt;Steeper, fewer decisions later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size control&lt;/td&gt;
&lt;td&gt;You own the whole graph&lt;/td&gt;
&lt;td&gt;RSC reduces client JS by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Static host + separate API&lt;/td&gt;
&lt;td&gt;Vercel/Node edge-ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for SPAs&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Works, often overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for marketing / content sites&lt;/td&gt;
&lt;td&gt;Extra work&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Job market in India (2026)&lt;/td&gt;
&lt;td&gt;Still required everywhere&lt;/td&gt;
&lt;td&gt;Listed on most product roles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Takeaway: React teaches you the language. Next.js teaches you how product teams ship that language at scale.&lt;/p&gt;
&lt;p&gt;Neither row is a knockout punch. The job market row matters in India — recruiters still filter on "React" in CVs, but product job descriptions increasingly say "Next.js" or "React with SSR experience." Learning both in sequence covers either filter without lying on your resume.&lt;/p&gt;
&lt;h2 id="when-react"&gt;When plain React still wins&lt;/h2&gt;
&lt;p&gt;I still reach for React without Next.js when the app is a true SPA — authenticated dashboards, internal tools, or embeddable widgets inside another product. No SEO requirement, no server HTML, no file-based routing benefit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// When this is your whole app, Next.js adds ceremony you won't use:&lt;br&gt;
// - Single route shell&lt;br&gt;
// - Auth gate on the client&lt;br&gt;
// - WebSocket or polling for live data

&lt;p&gt;export function AnalyticsDashboard() {&lt;br&gt;
  // 100% client-side — Server Components buy you nothing here&lt;br&gt;
  return &amp;lt;LiveChart streamUrl="/ws/metrics" /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What nobody tells you is that forcing App Router patterns onto a dashboard can slow you down — you fight the framework instead of shipping features.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — over-engineered dashboard page in Next.js&lt;br&gt;
export default async function DashboardPage() {&lt;br&gt;
  const session = await getServerSession(); // re-fetch every navigation&lt;br&gt;
  const metrics = await fetchMetrics(session.userId);&lt;br&gt;
  return &amp;lt;DashboardClient initial={metrics} /&amp;gt;; // still hydrates everything&lt;br&gt;
}

&lt;p&gt;// AFTER — honest SPA inside Next.js (or plain React if you prefer)&lt;br&gt;
"use client";&lt;br&gt;
export default function DashboardPage() {&lt;br&gt;
  const metrics = useLiveMetrics(); // WebSocket / SWR — belongs on client&lt;br&gt;
  return &amp;lt;DashboardClient metrics={metrics} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've shipped internal analytics tools as Create React App SPAs that ran for years without a rewrite. The users were logged-in employees on fast office networks — SEO was irrelevant. Pick the stack that matches the delivery model, not the hype cycle.&lt;/p&gt;
&lt;h2 id="when-nextjs"&gt;When Next.js is the obvious pick in 2026&lt;/h2&gt;
&lt;p&gt;Public websites, blogs, e-commerce, landing pages, anything that needs Google to see real HTML on first request. Also any full-stack product where API routes and server actions live next to UI — one repo, one deploy.&lt;/p&gt;
&lt;p&gt;If you're building like &lt;a href="https://safdarali.in/projects" rel="noopener noreferrer"&gt;the client sites in my portfolio&lt;/a&gt;— marketing-first, performance-sensitive, mobile-heavy — Next.js is the default I recommend in 2026.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// app/products/[slug]/page.tsx — metadata + SSR in one file&lt;br&gt;
import type { Metadata } from "next";

&lt;p&gt;type Props = { params: Promise&amp;lt;{ slug: string }&amp;gt; };&lt;/p&gt;

&lt;p&gt;export async function generateMetadata({ params }: Props): Promise&amp;lt;Metadata&amp;gt; {&lt;br&gt;
  const { slug } = await params;&lt;br&gt;
  const product = await getProduct(slug);&lt;br&gt;
  return {&lt;br&gt;
    title: product.name + " — Shop",&lt;br&gt;
    description: product.summary, // Google + WhatsApp previews use this&lt;br&gt;
    openGraph: { images: [product.imageUrl] },&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export default async function ProductPage({ params }: Props) {&lt;br&gt;
  const { slug } = await params;&lt;br&gt;
  const product = await getProduct(slug);&lt;br&gt;
  return &amp;lt;ProductDetail product={product} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try doing that cleanly in a client-only React app. You can — with react-helmet, a meta tag library, and probably a pre-render step — but you're rebuilding what Next.js ships on day one. For public pages, that assembly tax adds up across every route.&lt;/p&gt;
&lt;h2 id="starting-today"&gt;What I'd choose if starting today&lt;/h2&gt;
&lt;p&gt;Week 1–4: React fundamentals — components, props, state, effects, lists, forms. Build small UI without a framework. I cover this path on my &lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt; — 70+ free tutorials aimed at developers in India learning their first production stack.&lt;/p&gt;
&lt;p&gt;Week 5–8: TypeScript basics, then Next.js App Router — one page, one layout, one Server Component fetch. Read my &lt;a href="https://safdarali.in/blog/rsc-vs-client-components" rel="noopener noreferrer"&gt;RSC vs client components guide&lt;/a&gt; before you sprinkle "use client" everywhere.&lt;/p&gt;
&lt;p&gt;Month 3: Ship one real project — portfolio, blog, or small product — deploy to Vercel. Employers don't hire tutorial completers. They hire people who shipped.&lt;/p&gt;
&lt;p&gt;Free resources that worked for me: official React docs (beta.react.dev), Next.js learn course, and building in public on GitHub. Paid is optional until you need structured accountability — a ₹2,000 Udemy course is fine; a ₹2 lakh bootcamp is not required to land a first frontend role in India if your GitHub shows real work.&lt;/p&gt;
&lt;p&gt;If you only have time for one thing this month: learn React. If you have two: add Next.js immediately after — not instead.&lt;/p&gt;
&lt;h2 id="react-19-2026"&gt;Does React 19 change the nextjs vs react 2026 decision?&lt;/h2&gt;
&lt;p&gt;React 19 stabilised features that used to be Next.js-only experiments — improved hydration, the &lt;code&gt;use()&lt;/code&gt; hook, and better form actions. That does not make Next.js optional. It makes React itself more capable while Next.js still owns routing, caching, and deployment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// React 19 — use() for promises inside components (still client or RSC)&lt;br&gt;
import { use } from "react";

&lt;p&gt;function ProductList({ productsPromise }: { productsPromise: Promise&amp;lt;Product[]&amp;gt; }) {&lt;br&gt;
  const products = use(productsPromise); // suspends until resolved&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;ul&amp;gt;&lt;br&gt;
      {products.map((p) =&amp;gt; (&lt;br&gt;
        &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;&lt;br&gt;
      ))}&lt;br&gt;
    &amp;lt;/ul&amp;gt;&lt;br&gt;
  );&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can learn this API in a plain React sandbox. In production, I still reach for Next.js to wire the promise on the server and stream HTML. The library got sharper; the framework still saves calendar time.&lt;/p&gt;
&lt;p&gt;Takeaway: React 19 is a reason to learn modern React, not a reason to skip Next.js on public web apps.&lt;/p&gt;
&lt;h2 id="production-setup"&gt;My production setup&lt;/h2&gt;
&lt;p&gt;In production I default to Next.js App Router with TypeScript strict mode, Tailwind CSS, and Server Components for anything public-facing. Client components are leaves — charts, modals, forms — not entire pages.&lt;/p&gt;
&lt;p&gt;On a recent marketing rebuild, that discipline plus &lt;code&gt;next/image&lt;/code&gt; and route caching moved Lighthouse performance from 54 to 91. The bundle analyser showed 38% less client JavaScript after we moved data fetching to the server — same pattern I document in the performance case study linked above.&lt;/p&gt;
&lt;p&gt;Plain React stays in my toolbox for isolated widgets and legacy SPAs. Next.js is where I ship anything a user or crawler hits on the open web.&lt;/p&gt;
&lt;p&gt;When I interview junior candidates in Bengaluru, I ask them to explain one component they wrote — props in, state updates, why a list needs keys. I don't start with middleware or ISR. If they learned only Next.js templates, they often cannot answer. If they learned React first, the Next.js-specific questions become teachable in a week.&lt;/p&gt;
&lt;h2 id="mistake"&gt;The mistake I see most often&lt;/h2&gt;
&lt;p&gt;Beginners skip React and copy-paste Next.js templates. They can deploy, but they cannot debug hydration errors, cannot explain why a component is client-only, and panic when the build fails on server/client boundaries.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// BEFORE — copied from a template, broken mental model&lt;br&gt;
"use client";&lt;br&gt;
export default function Page() {&lt;br&gt;
  useEffect(() =&amp;gt; {&lt;br&gt;
    fetch("/api/data").then(/* ... */); // could be server-side&lt;br&gt;
  }, []);&lt;br&gt;
}

&lt;p&gt;// AFTER — fetch on server, interactivity only where needed&lt;br&gt;
export default async function Page() {&lt;br&gt;
  const data = await getData(); // runs once on server&lt;br&gt;
  return &amp;lt;ProductGrid data={data} /&amp;gt;;&lt;br&gt;
}&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Learn React so the second snippet feels natural — not magic.&lt;/p&gt;
&lt;p&gt;Bootcamps that advertise "Full Stack Next.js in 4 weeks" without JSX fundamentals produce developers who can clone a Vercel deploy — until the first production bug. I'd rather you ship one ugly React todo app you fully understand than a polished template you cannot modify.&lt;/p&gt;
&lt;h2 id="closing"&gt;The single takeaway&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Next.js vs React in 2026 is the wrong either/or.&lt;/strong&gt; React is the skill. Next.js is how most product teams apply that skill on the web. Learn React first, ship one Next.js project second, and measure everything — LCP, bundle size, time-to-first-commit.&lt;/p&gt;
&lt;p&gt;I still get DMs asking which certificate to buy. Buy none until you've built something. Certificates prove attendance; GitHub repos prove competence. Start with React this week, add Next.js next month, and publish the result — even if it's rough.&lt;/p&gt;
&lt;p&gt;Related reading: &lt;a href="/blog/nextjs-performance-60-percent"&gt;How I cut load time by 60% with Next.js App Router&lt;/a&gt;. More case studies: &lt;a href="https://safdarali.in/projects" rel="noopener noreferrer"&gt;safdarali.in/projects&lt;/a&gt;. Questions: &lt;a href="https://safdarali.in/contact" rel="noopener noreferrer"&gt;safdarali.in/contact&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If this helped you&lt;/p&gt;
&lt;p&gt;I publish free tutorials and write-ups like this in my spare time — no paywall on the guides. If it saved you an afternoon of trial and error, you can support the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt;→ &lt;/span&gt;&lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;Buy me a coffee at buymeacoffee.com/safdarali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt;👉 &lt;/span&gt;&lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;Subscribe to my YouTube channel&lt;/a&gt;&lt;span&gt; — it's free; 70+ React &amp;amp; Next.js tutorials&lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt; 

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>I Built a Free Video Thumbnail Generator That Never Uploads Your Files</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 07 Jun 2026 00:01:42 +0000</pubDate>
      <link>https://dev.to/safdarali25/i-built-a-free-video-thumbnail-generator-that-never-uploads-your-files-56pp</link>
      <guid>https://dev.to/safdarali25/i-built-a-free-video-thumbnail-generator-that-never-uploads-your-files-56pp</guid>
      <description>&lt;p&gt;Every creator hits the same wall: you finished the video, exported the MP4, uploaded to YouTube — and &lt;strong&gt;forgot the custom thumbnail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your best frame is already inside that file. You just need to grab it.&lt;/p&gt;

&lt;p&gt;Most online tools make you &lt;strong&gt;upload the entire video to their server&lt;/strong&gt;. That is slow for large files, risky for unreleased content, and often ends with a watermark unless you pay.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://framesnap.safdarali.in" rel="noopener noreferrer"&gt;FrameSnap&lt;/a&gt;&lt;/strong&gt; — a free browser tool that extracts thumbnails locally. No account. No watermark. Your video never leaves your device.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with "free" thumbnail tools
&lt;/h2&gt;

&lt;p&gt;Search for &lt;em&gt;free video thumbnail generator online&lt;/em&gt; and you get dozens of results. Most fall into two camps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server-upload converters&lt;/strong&gt; — your file goes to their cloud, gets processed, comes back with a logo on the free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design apps&lt;/strong&gt; — great for templates and text overlays, overkill when you already have a strong frame in your footage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creators actually need something simpler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrub to the right moment&lt;/li&gt;
&lt;li&gt;Export at &lt;strong&gt;1280×720&lt;/strong&gt; for YouTube&lt;/li&gt;
&lt;li&gt;Download &lt;strong&gt;PNG or JPG&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Optionally grab &lt;strong&gt;6–25 frames&lt;/strong&gt; to compare before deciding&lt;/li&gt;
&lt;li&gt;Do it &lt;strong&gt;without sending client footage to a random SaaS&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters if you record courses, agency work, or anything under NDA.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why client-side wins here
&lt;/h2&gt;

&lt;p&gt;Modern browsers can decode MP4, WebM, and MOV through the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;Canvas API&lt;/a&gt; can draw any frame to a bitmap. Download via blob URL. Done.&lt;/p&gt;

&lt;p&gt;The flow in FrameSnap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User selects file → video loads in memory → set currentTime → drawImage to canvas → export PNG/JPG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API route handles the video. No S3 bucket. No queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-offs I accepted:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Works great&lt;/th&gt;
&lt;th&gt;Does not work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local MP4, WebM, MOV, MKV&lt;/td&gt;
&lt;td&gt;YouTube watch-page URLs (platform blocks direct access)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct &lt;code&gt;.mp4&lt;/code&gt; URLs&lt;/td&gt;
&lt;td&gt;DRM-protected streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch export via auto-snap intervals&lt;/td&gt;
&lt;td&gt;Replacing Photoshop for layered design&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the 80% case — &lt;em&gt;"I have an exported MP4 and need a still"&lt;/em&gt; — client-side is faster and more private than upload-and-wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features I cared about as a user
&lt;/h2&gt;

&lt;p&gt;These shaped the product more than any framework choice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-snap presets&lt;/strong&gt; — Quick 6 frames, or every 2%/4%/5% of video length. Picking one thumbnail from a 15-minute video by manual scrubbing is painful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download all as ZIP&lt;/strong&gt; — Compare candidates side-by-side before adding text in Canva.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Original aspect ratio&lt;/strong&gt; — Portrait video stays portrait. No black bars from forcing everything into 16:9.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JPG quality slider&lt;/strong&gt; — YouTube's custom thumbnail limit is 2 MB. PNG screen recordings blow past that; JPG at 90% usually does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct MP4 URL tab&lt;/strong&gt; — If your file is hosted at &lt;code&gt;https://yoursite.com/demo.mp4&lt;/code&gt;, paste and go. Not a Google Drive preview link — an actual file URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack (for the curious)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js 16&lt;/a&gt;&lt;/strong&gt; — App Router, static blog with scheduled publishing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — the fun part is typed canvas math, not the boilerplate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas + HTML5 Video&lt;/strong&gt; — frame capture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://stuartk.com/jszip/" rel="noopener noreferrer"&gt;JSZip&lt;/a&gt;&lt;/strong&gt; — batch downloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; — deploy at &lt;a href="https://framesnap.safdarali.in" rel="noopener noreferrer"&gt;framesnap.safdarali.in&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;100% client-side processing. The server only serves HTML/JS.&lt;/p&gt;




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

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://framesnap.safdarali.in" rel="noopener noreferrer"&gt;framesnap.safdarali.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drop an MP4 (or paste a direct video URL)&lt;/li&gt;
&lt;li&gt;Scrub or use auto-snap&lt;/li&gt;
&lt;li&gt;Export HD (1280×720) PNG/JPG&lt;/li&gt;
&lt;li&gt;Upload to YouTube Studio&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also wrote a longer comparison guide on the site: &lt;a href="https://framesnap.safdarali.in/blog/video-thumbnail-generator-online" rel="noopener noreferrer"&gt;Free Video Thumbnail Generator Online&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;More export presets for Shorts/Reels covers&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts for power users&lt;/li&gt;
&lt;li&gt;Better error messages for codec edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and it saves you ten minutes before your next upload, that is enough for me.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://framesnap.safdarali.in" rel="noopener noreferrer"&gt;FrameSnap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog:&lt;/strong&gt; &lt;a href="https://framesnap.safdarali.in/blog" rel="noopener noreferrer"&gt;framesnap.safdarali.in/blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://safdarali.in" rel="noopener noreferrer"&gt;safdarali.in&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;@safdarali_&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X / Twitter:&lt;/strong&gt; &lt;a href="https://x.com/safdarali___" rel="noopener noreferrer"&gt;@safdarali___&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/safdarali25/" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Safdar-Ali-India" rel="noopener noreferrer"&gt;Safdar-Ali-India&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Support the project
&lt;/h2&gt;

&lt;p&gt;FrameSnap is free with no export limits. If it helps your workflow, you can &lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;buy me a coffee ☕&lt;/a&gt; — it keeps side projects like this alive.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://safdarali.in" rel="noopener noreferrer"&gt;Safdar Ali&lt;/a&gt;. Feedback welcome in the comments — especially if you hit a codec or browser edge case I have not seen yet.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Build a Frontend Developer Portfolio That Stands Out</title>
      <dc:creator>Safdar Ali</dc:creator>
      <pubDate>Sun, 31 May 2026 23:00:00 +0000</pubDate>
      <link>https://dev.to/safdarali25/how-to-build-a-frontend-developer-portfolio-that-stands-out-4d3k</link>
      <guid>https://dev.to/safdarali25/how-to-build-a-frontend-developer-portfolio-that-stands-out-4d3k</guid>
      <description>&lt;p&gt;I rebuilt &lt;strong&gt;&lt;a href="https://safdarali.in/" rel="noopener noreferrer"&gt;safdarali.in&lt;/a&gt;&lt;/strong&gt; on Next.js as a living document — not a static PDF, but a frontend developer portfolio that shows how I think about performance, content, and craft.&lt;/p&gt;

&lt;p&gt;Fast first paint.&lt;/p&gt;

&lt;p&gt;Clear navigation.&lt;/p&gt;

&lt;p&gt;Three meaningful projects instead of twelve cloned tutorials.&lt;/p&gt;

&lt;p&gt;Developers ask me every week:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should I put on my portfolio in 2026?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide is the exact framework I recommend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Frontend Developer Portfolio Still Matters in 2026
&lt;/h2&gt;

&lt;p&gt;LinkedIn profiles are crowded.&lt;/p&gt;

&lt;p&gt;GitHub repositories rarely explain your thinking.&lt;/p&gt;

&lt;p&gt;Generic link-in-bio pages look identical.&lt;/p&gt;

&lt;p&gt;A portfolio remains the one place where you control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your story&lt;/li&gt;
&lt;li&gt;Your design&lt;/li&gt;
&lt;li&gt;Your technical decisions&lt;/li&gt;
&lt;li&gt;Your personal brand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Companies don't just hire code.&lt;/p&gt;

&lt;p&gt;They hire problem-solvers.&lt;/p&gt;

&lt;p&gt;Your portfolio is where you demonstrate that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Visitors Notice First
&lt;/h2&gt;

&lt;p&gt;Most visitors decide within seconds whether to continue exploring.&lt;/p&gt;

&lt;p&gt;They're looking for signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What Good Looks Like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Role clarity&lt;/td&gt;
&lt;td&gt;Frontend Engineer • React • Next.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live proof&lt;/td&gt;
&lt;td&gt;Working project links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Fast loading pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Honesty&lt;/td&gt;
&lt;td&gt;Clear ownership of work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contact&lt;/td&gt;
&lt;td&gt;Easy-to-find email or LinkedIn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Blog posts or case studies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The best portfolios answer two questions immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What do you build?&lt;/li&gt;
&lt;li&gt;How do you think?&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Common Portfolio Mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Template Trap
&lt;/h3&gt;

&lt;p&gt;Using a template is fine.&lt;/p&gt;

&lt;p&gt;Publishing it without customization isn't.&lt;/p&gt;

&lt;p&gt;Hiring managers see the same templates repeatedly.&lt;/p&gt;

&lt;p&gt;Add your own voice, structure, and design decisions.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Too Many Tutorial Projects
&lt;/h3&gt;

&lt;p&gt;Five Netflix clones won't impress anyone.&lt;/p&gt;

&lt;p&gt;One production-style project with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;API integration&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Responsive design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;is far more valuable.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. GitHub as the Entire Portfolio
&lt;/h3&gt;

&lt;p&gt;Most visitors won't read your code first.&lt;/p&gt;

&lt;p&gt;Explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;Your solution&lt;/li&gt;
&lt;li&gt;The outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make projects understandable before visitors open GitHub.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Ignoring Mobile Users
&lt;/h3&gt;

&lt;p&gt;Many recruiters first visit portfolios from phones.&lt;/p&gt;

&lt;p&gt;A portfolio that looks amazing on desktop but breaks on mobile loses opportunities.&lt;/p&gt;

&lt;p&gt;Always test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile navigation&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Layout shifts&lt;/li&gt;
&lt;li&gt;Touch interactions&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Generic Introductions
&lt;/h3&gt;

&lt;p&gt;Avoid:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Passionate developer who loves coding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead write something specific:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Frontend Engineer specializing in React, Next.js, and performance-focused web applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Specificity wins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Portfolio Sections Every Developer Needs
&lt;/h2&gt;

&lt;p&gt;You don't need 20 pages.&lt;/p&gt;

&lt;p&gt;These seven sections are enough:&lt;/p&gt;

&lt;h3&gt;
  
  
  Home
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Role&lt;/li&gt;
&lt;li&gt;Location&lt;/li&gt;
&lt;li&gt;Call-to-action&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Projects
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;3–5 strong projects&lt;/li&gt;
&lt;li&gt;Live demos&lt;/li&gt;
&lt;li&gt;Tech stack&lt;/li&gt;
&lt;li&gt;Short case studies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  About
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Experience&lt;/li&gt;
&lt;li&gt;Background&lt;/li&gt;
&lt;li&gt;Personality&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Skills
&lt;/h3&gt;

&lt;p&gt;Group skills honestly.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Skills&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Familiar With&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Blog
&lt;/h3&gt;

&lt;p&gt;Even a few technical articles help.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO traffic&lt;/li&gt;
&lt;li&gt;Personal branding&lt;/li&gt;
&lt;li&gt;Communication proof&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Contact
&lt;/h3&gt;

&lt;p&gt;Make it simple.&lt;/p&gt;

&lt;p&gt;Don't hide contact information behind multiple clicks.&lt;/p&gt;




&lt;h3&gt;
  
  
  Privacy Policy
&lt;/h3&gt;

&lt;p&gt;Especially important if your contact form stores user data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Frontend Portfolio Project Ideas
&lt;/h2&gt;

&lt;p&gt;The strongest projects solve real problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics Dashboard
&lt;/h3&gt;

&lt;p&gt;Shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charts&lt;/li&gt;
&lt;li&gt;Data visualization&lt;/li&gt;
&lt;li&gt;Complex UI&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Multi-Step Form
&lt;/h3&gt;

&lt;p&gt;Demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;State management&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Documentation Platform
&lt;/h3&gt;

&lt;p&gt;Shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search functionality&lt;/li&gt;
&lt;li&gt;Content architecture&lt;/li&gt;
&lt;li&gt;Navigation design&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  E-Commerce Application
&lt;/h3&gt;

&lt;p&gt;Demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product listings&lt;/li&gt;
&lt;li&gt;Cart management&lt;/li&gt;
&lt;li&gt;API integrations&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Component Library
&lt;/h3&gt;

&lt;p&gt;Excellent for frontend-focused roles.&lt;/p&gt;

&lt;p&gt;Shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusability&lt;/li&gt;
&lt;li&gt;Design systems&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to Write Better Project Case Studies
&lt;/h2&gt;

&lt;p&gt;Don't stop at screenshots.&lt;/p&gt;

&lt;p&gt;Explain:&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;What needed to be solved?&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Role
&lt;/h3&gt;

&lt;p&gt;What exactly did you build?&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;p&gt;Which technologies did you use?&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Decision
&lt;/h3&gt;

&lt;p&gt;What tradeoff did you make?&lt;/p&gt;

&lt;h3&gt;
  
  
  Outcome
&lt;/h3&gt;

&lt;p&gt;What improved?&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster load times&lt;/li&gt;
&lt;li&gt;Better UX&lt;/li&gt;
&lt;li&gt;Higher Lighthouse scores&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Next.js Portfolio Best Practices
&lt;/h2&gt;

&lt;p&gt;If you're building a modern portfolio, Next.js remains an excellent choice.&lt;/p&gt;

&lt;p&gt;Recommended patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Server Components
&lt;/h3&gt;

&lt;p&gt;Keep client-side JavaScript minimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimize Images
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;next/image&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Metadata
&lt;/h3&gt;

&lt;p&gt;Every page should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Description&lt;/li&gt;
&lt;li&gt;Canonical URL&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Add Structured Data
&lt;/h3&gt;

&lt;p&gt;Useful schemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Person&lt;/li&gt;
&lt;li&gt;Website&lt;/li&gt;
&lt;li&gt;BlogPosting&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SEO Tips for Developer Portfolios
&lt;/h2&gt;

&lt;p&gt;Many developers ignore SEO entirely.&lt;/p&gt;

&lt;p&gt;That creates opportunity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target Specific Keywords
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend developer portfolio&lt;/li&gt;
&lt;li&gt;react developer portfolio&lt;/li&gt;
&lt;li&gt;next.js developer portfolio&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Create Supporting Articles
&lt;/h3&gt;

&lt;p&gt;Write content around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Career growth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Content creates long-term traffic.&lt;/p&gt;




&lt;h3&gt;
  
  
  Internal Linking
&lt;/h3&gt;

&lt;p&gt;Link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blog → Projects&lt;/li&gt;
&lt;li&gt;Projects → Contact&lt;/li&gt;
&lt;li&gt;About → Projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This improves discoverability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Optimization Checklist
&lt;/h2&gt;

&lt;p&gt;Users notice speed.&lt;/p&gt;

&lt;p&gt;Recommended targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LCP&lt;/td&gt;
&lt;td&gt;Under 2.5s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS&lt;/td&gt;
&lt;td&gt;Under 0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INP&lt;/td&gt;
&lt;td&gt;Under 200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total JS&lt;/td&gt;
&lt;td&gt;Keep minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quick wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-host fonts&lt;/li&gt;
&lt;li&gt;Optimize images&lt;/li&gt;
&lt;li&gt;Lazy-load heavy components&lt;/li&gt;
&lt;li&gt;Avoid unnecessary animations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Personal Branding That Compounds
&lt;/h2&gt;

&lt;p&gt;Your portfolio should not exist in isolation.&lt;/p&gt;

&lt;p&gt;A strong ecosystem looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portfolio website&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;DEV Community&lt;/li&gt;
&lt;li&gt;YouTube (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each platform drives traffic to the others.&lt;/p&gt;

&lt;p&gt;Over time, this creates a compounding effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Portfolio Checklist
&lt;/h2&gt;

&lt;p&gt;Before publishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Clear role and value proposition&lt;/li&gt;
&lt;li&gt;[ ] Mobile-friendly design&lt;/li&gt;
&lt;li&gt;[ ] Fast page speed&lt;/li&gt;
&lt;li&gt;[ ] Live project links&lt;/li&gt;
&lt;li&gt;[ ] Working contact form&lt;/li&gt;
&lt;li&gt;[ ] SEO metadata&lt;/li&gt;
&lt;li&gt;[ ] Open Graph images&lt;/li&gt;
&lt;li&gt;[ ] At least one technical article&lt;/li&gt;
&lt;li&gt;[ ] Updated GitHub profile&lt;/li&gt;
&lt;li&gt;[ ] Custom domain configured&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A frontend developer portfolio isn't decoration.&lt;/p&gt;

&lt;p&gt;It's proof.&lt;/p&gt;

&lt;p&gt;Proof of your skills.&lt;/p&gt;

&lt;p&gt;Proof of your decisions.&lt;/p&gt;

&lt;p&gt;Proof of how you solve problems.&lt;/p&gt;

&lt;p&gt;In 2026, templates and generic portfolios are everywhere.&lt;/p&gt;

&lt;p&gt;What still stands out is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast performance&lt;/li&gt;
&lt;li&gt;Real projects&lt;/li&gt;
&lt;li&gt;Honest case studies&lt;/li&gt;
&lt;li&gt;Strong communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build something you'd be proud to share.&lt;/p&gt;

&lt;p&gt;And keep improving it as you grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  ☕ Enjoyed This Article?
&lt;/h2&gt;

&lt;p&gt;If this guide helped you and you'd like to support more content like this:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Buy me a coffee:&lt;/strong&gt; &lt;a href="https://buymeacoffee.com/safdarali" rel="noopener noreferrer"&gt;https://buymeacoffee.com/safdarali&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Subscribe to my YouTube channel:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@safdarali_?sub_confirmation=1" rel="noopener noreferrer"&gt;https://www.youtube.com/@safdarali_?sub_confirmation=1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I regularly share content about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;AI-assisted development&lt;/li&gt;
&lt;li&gt;Developer career growth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your support helps me continue publishing free tutorials and in-depth articles.&lt;/p&gt;

&lt;p&gt;Thank you for reading! 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>portfolio</category>
      <category>career</category>
    </item>
  </channel>
</rss>
