Why I Switched from Next.js to Vite + React for Client Projects

TL;DR

After 20+ client projects on Next.js, I switched most of my work to Vite + React. The honest reason: most clients don't need SSR, and Next.js makes you pay for SSR even when you're not using it, in build time, in deployment complexity, in mental overhead, and in money. Vite gives me sub-second dev server starts, smaller bundles, deploy-anywhere flexibility, and a stack a junior can hand off in a week. I still reach for Next.js when SEO, edge rendering, or co-located backend code is genuinely the requirement. The rule I now use is: pick the simplest tool that solves the actual problem in front of you, not the most powerful tool in case the problem grows.

The Tipping Point

I've been a Next.js advocate for years. Server-side rendering, file-based routing, API routes, image optimization, the App Router: it's a powerful framework, and Vercel has done a remarkable job of pushing the web forward through it. For long-form content sites, marketing pages, and large multi-tenant SaaS, it's an excellent default.

But after shipping more than twenty client projects across portfolios, dashboards, admin panels, internal tools, and small SaaS products, I noticed an uncomfortable pattern: almost none of my clients actually needed SSR. They needed a fast app, a clean handoff, cheap hosting, and a stack their next developer could understand without a course.

What they were getting was an SSR-shaped framework that added complexity I had to keep explaining away. The first time I shipped a Vite + React + Supabase project end to end and watched the dev server boot in 200ms and the production build deploy to a static host for free, I stopped pretending. For SPAs, Next.js is overkill, and the overkill has a cost.

The Cost of "Just in Case"

Next.js is built around the assumption that some pages will be server-rendered. Even if you push everything to the client, you're still inside that assumption. You pay for it in:

  • A heavier framework runtime in the bundle
  • A Node.js process at deploy time (or an edge function), instead of a static folder
  • A mental model where every component has to remember whether it's "use client" or not
  • Build times that grow super-linearly with the size of the app
  • A development server that, on a Macbook Air, is noticeably slower
  • None of this is fatal. A lot of it is acceptable for the right project. But for an SPA that's going to live behind auth and ship to a handful of users, every one of those costs is being paid for a benefit that's never collected.

    What Vite Gets Right

    Build speed. Vite's dev server starts in under 300 milliseconds on my machine. Cold start. Next.js takes between 3 and 8 seconds on the same hardware. When you're iterating fast, making a small change, checking the browser, making another small change, that gap compounds into real wall-clock hours over a week of work.

    Simplicity. There is no getServerSideProps, no getStaticProps, no generateStaticParams, no decision about which rendering strategy a page should use. Every component is a regular React component. Every route is a regular React Router route. The mental load is dramatically lower, and that frees energy for actual product work.

    Flexibility. A Vite build produces a folder of static files. I can deploy it to Lovable, Netlify, Vercel, Cloudflare Pages, Render, an S3 bucket with CloudFront, or even a cheap shared host. There is no Node.js server to keep alive, no cold start penalty, no "edge runtime" gotchas. For client projects, this matters because the client owns the hosting after handoff and I want their bill to be ten dollars a month, not two hundred.

    Bundle size. A typical Vite + React app I ship is between 100 and 180 KB of JavaScript, gzipped, including the framework. A similar Next.js App Router app starts at around 200 KB before you've written a line of business code. On a mobile connection in Damascus or Khartoum, that gap is the difference between an app that feels instant and one that feels sluggish for the first three seconds.

    Honest dev experience. Hot Module Reload in Vite is essentially perfect, state preserves, edits show up in under 100 milliseconds, and I almost never need to restart the dev server. Next.js has improved a lot here, but it's still noticeably slower and occasionally requires a full restart when something gets confused.

    When I Still Use Next.js

    I don't think Next.js is wrong. I think it's a great tool that's being used for the wrong job most of the time. The cases where I still reach for it:

  • Content-heavy sites that need SEO. Blogs, documentation sites, marketing landing pages, anything where Google needs to crawl the rendered HTML. If your first paint has to be HTML with content, SSR or SSG is the right tool, and Next.js does that well.
  • Apps where first paint matters more than interactivity. Some apps are read-heavy and the JavaScript bundle isn't critical until the user clicks something. Streaming SSR can genuinely help here.
  • Projects that benefit from co-located backend code. When the same engineer writes the API and the UI, Next.js API routes (or Route Handlers in the App Router) genuinely reduce friction.
  • Multi-tenant SaaS with per-tenant subdomains. Edge middleware and dynamic routing are a real win when you have to rewrite or proxy requests based on tenant.
  • If any of those apply, Next.js is the better answer. If none of them apply, Vite is almost certainly the better answer.

    The Stack I've Settled On

  • Vite: build tool and dev server
  • React 18: UI library
  • TypeScript: type safety, in strict mode by default
  • Tailwind CSS: styling, with shadcn/ui for primitives
  • Supabase: backend (Postgres, auth, storage, edge functions)
  • TanStack Query: data fetching, caching, and mutations
  • React Router: client-side routing
  • Zod: schema validation at the boundary
  • Vitest + Playwright: unit and end-to-end tests
  • This stack covers about 90% of my client work. It is fast to develop in, fast to build, fast to deploy, and (most importantly) easy to hand off to whoever picks up the project next. I've handed off three Vite projects to junior developers and all three were productive within a week.

    A Concrete Comparison

    Here's what a typical "fetch and render" looks like in both stacks. In Next.js App Router:

    // app/projects/page.tsx
    import { db } from '@/lib/db';
    
    export default async function ProjectsPage() {
      const projects = await db.project.findMany();
      return <ProjectsList projects={projects} />;
    }

    This is elegant on the surface, but you've now committed to running a Node process, you've coupled your component tree to a server runtime, and you've added a layer where "this is a server component" vs "this is a client component" becomes a thing you have to think about constantly.

    In Vite + React + TanStack Query:

    // src/pages/Projects.tsx
    import { useQuery } from '@tanstack/react-query';
    import { supabase } from '@/integrations/supabase/client';
    
    export default function ProjectsPage() {
      const { data: projects } = useQuery({
        queryKey: ['projects'],
        queryFn: async () => {
          const { data } = await supabase.from('projects').select('*');
          return data;
        },
      });
      return <ProjectsList projects={projects ?? []} />;
    }

    Slightly more verbose, but everything happens in the browser, every component is the same kind of component, deploys are static files, and the caching/refetching behavior is explicit instead of magical.

    What I Lose

    I'm not going to pretend this is free. Switching to Vite costs you:

  • No server components. You can't render data on the server and skip shipping JavaScript for it. For most SPAs, this doesn't matter, because users are interacting with the app anyway and the JavaScript is already there.
  • Worse out-of-the-box SEO. If SEO matters, you need to add prerendering (I use a build-time script) or move to a different stack. There's no escaping that crawled HTML still beats client-rendered HTML for most search engines.
  • Manual image optimization. No next/image. I usually pre-process images at build time with a script, or use Cloudinary for client uploads.
  • Manual route splitting. You set up React.lazy and Suspense yourself instead of getting it for free. It's about thirty extra lines of code, total.
  • For 90% of the projects I work on, these trade-offs are net positive. For the other 10%, I just use Next.js.

    How to Decide for Your Project

    Ask one question: does my first paint need to be HTML with content?

    If yes: Next.js (or Remix, or Astro). The SSR cost is paying for a real benefit.

    If no: Vite + React. You get a faster dev loop, smaller bundles, cheaper hosting, and a simpler mental model.

    That's the whole decision. Everything else (file routing, image optimization, middleware) is a feature you can add to either stack if you actually need it.

    The Lesson

    The temptation in our industry is to pick the most powerful tool because someday you might need its full power. The reality is most projects never reach "someday." They reach a launch date, they reach a handoff, they reach a sunset.

    The best tool is the simplest one that solves the problem in front of you. For most SPAs, that's Vite + React, and I'm not going back.

    لماذا انتقلت من Next.js إلى Vite + React لمشاريع العملاء

    الخلاصة

    بعد أكثر من 20 مشروع عميل على Next.js، نقلت معظم عملي إلى Vite + React. السبب الصادق: معظم العملاء لا يحتاجون SSR، وNext.js يجبرك على دفع كلفته حتى حين لا تستخدمه، في زمن البناء، وفي تعقيد النشر، وفي العبء الذهني، وفي المال. Vite يعطيني خادم تطوير يبدأ في أقل من ثانية، وحُزماً أصغر، ومرونة في النشر على أي مزوّد، وستاكاً يستطيع المطوّر الجديد تسلّمه خلال أسبوع. أبقى ألجأ إلى Next.js حين يكون SEO أو العرض على الحافّة أو الكود الخلفي المُلصَق بالواجهة متطلَّباً حقيقياً. القاعدة التي صرت أعمل بها: اختر أبسط أداة تحلّ المشكلة التي أمامك، لا أقوى أداة تحسّباً لمشكلة قد تأتي.

    نقطة التحوّل

    كنت من أنصار Next.js لسنوات. العرض من جانب الخادم، التوجيه المبني على الملفات، مسارات API، تحسين الصور، App Router: إطار قوي بحقّ، وVercel فعلت عملاً ممتازاً في دفع الويب إلى الأمام عبره. لمواقع المحتوى الطويلة وصفحات التسويق وSaaS الكبيرة متعددة المستأجرين هو الاختيار الافتراضي المنطقي.

    لكن بعد إطلاق أكثر من عشرين مشروع عميل بين بورتفوليو، لوحات تحكّم، أدوات داخلية، وSaaS صغيرة، لاحظت نمطاً مزعجاً: تقريباً لا أحد من عملائي احتاج SSR فعلاً. كانوا يحتاجون تطبيقاً سريعاً، وتسليماً نظيفاً، واستضافة رخيصة، وستاكاً يفهمه المطوّر التالي بلا دورة تدريبية.

    الذي كانوا يحصلون عليه هو إطار مصمَّم حول العرض من جانب الخادم بكلفة تعقيد كنت أمضي وقتي في تبريرها. أوّل مرة شحنت فيها مشروع Vite + React + Supabase من البداية إلى النهاية، ورأيت خادم التطوير يبدأ في 200 مللي ثانية والبناء الإنتاجي ينشَر على استضافة ثابتة مجانية، توقّفت عن التظاهر. لتطبيقات الصفحة الواحدة، Next.js مبالغة، والمبالغة لها ثمن.

    ثمن "تحسّباً للمستقبل"

    Next.js مبني على افتراض أن بعض الصفحات ستُعرض من الخادم. حتى لو دفعت كل شيء إلى العميل، تبقى داخل ذلك الافتراض. تدفع الثمن في:

  • وقت تشغيل أثقل للإطار في الحُزمة
  • عملية Node.js عند النشر، بدل مجلّد ملفات ثابتة
  • نموذج ذهني يجبرك على تذكّر "use client" لكل مكوّن
  • أزمنة بناء تنمو نموّاً أكبر من خطّي مع حجم التطبيق
  • خادم تطوير ملموس البطء على جهاز محمول
  • لا شيء من هذا قاتل. كثير منه مقبول في المشروع المناسب. لكن لتطبيق صفحة واحدة سيعيش خلف مصادقة ويُقدَّم لعدد محدود من المستخدمين، كل واحدة من هذه التكاليف تُدفع مقابل فائدة لن تُجنى.

    ما يتفوّق فيه Vite

    سرعة البناء. خادم Vite يبدأ في أقل من 300 مللي ثانية على جهازي. بداية باردة. Next.js يستغرق بين 3 و8 ثوانٍ على نفس العتاد. حين تكرّر بسرعة, تغيير صغير، فحص في المتصفّح، تغيير صغير آخر, هذا الفارق يتراكم إلى ساعات حقيقية في أسبوع عمل.

    البساطة. لا getServerSideProps ولا getStaticProps ولا generateStaticParams، ولا قرار حول استراتيجية العرض لكل صفحة. كل مكوّن هو مكوّن React عادي. كل مسار هو مسار React Router عادي. العبء الذهني أخف بكثير، وهذا يحرّر طاقة للعمل الفعلي على المنتج.

    المرونة. بناء Vite يُنتج مجلّد ملفات ثابتة. أستطيع نشره على Lovable أو Netlify أو Vercel أو Cloudflare Pages أو Render أو دلو S3 خلف CloudFront أو حتى استضافة مشتركة رخيصة. لا خادم Node.js يحتاج بقاءً حيّاً، لا غرامة بداية باردة، لا فخاخ "edge runtime". لمشاريع العملاء هذا يهمّ، لأن العميل يملك الاستضافة بعد التسليم وأريد فاتورته عشرة دولارات شهرياً لا مئتين.

    حجم الحُزمة. تطبيق Vite + React نموذجي عندي بين 100 و180 كيلوبايت من JavaScript مضغوطاً، شاملاً الإطار. تطبيق Next.js App Router مشابه يبدأ حول 200 كيلوبايت قبل أن تكتب سطر منطق عمل. على اتصال موبايل في دمشق أو الخرطوم، هذا الفارق هو الفرق بين تطبيق يبدو فورياً وتطبيق يبدو ثقيلاً في الثواني الأولى.

    تجربة تطوير صادقة. Hot Module Reload في Vite فعلياً مثالي, الحالة تُحفظ، التعديلات تظهر في أقل من 100 مللي ثانية، ونادراً ما أحتاج إعادة تشغيل الخادم. Next.js تحسّن كثيراً هنا، لكنه ما زال أبطأ ملحوظاً ويحتاج أحياناً إعادة تشغيل كاملة حين يرتبك شيء ما.

    متى أبقى مع Next.js

    لا أظنّ أن Next.js خطأ. أظنّه أداة ممتازة تُستخدم في الوظيفة الخطأ في معظم الأحيان. الحالات التي ما زلت ألجأ فيها إليه:

  • مواقع المحتوى الكثيف الذي يحتاج SEO. مدوّنات، توثيق، صفحات تسويق، أي شيء يحتاج Google أن يزحف على HTML المرسوم. لو لزم أن تكون أوّل لقطة HTML بمحتوى، فالعرض من الخادم أو SSG هو الأداة الصحيحة، وNext.js يجيد ذلك.
  • التطبيقات التي تكون فيها أوّل لقطة أهم من التفاعليّة. بعض التطبيقات قراءة كثيرة وحُزمة JavaScript ليست حرجة حتى يضغط المستخدم شيئاً. SSR التدريجي يساعد فعلاً.
  • مشاريع تستفيد من كود خلفي بجوار الواجهة. حين يكتب نفس المهندس الـ API والواجهة، مسارات Next.js API تقلّل الاحتكاك فعلاً.
  • SaaS متعددة المستأجرين بنطاقات فرعية. الـ Edge middleware والتوجيه الديناميكي مكاسب حقيقية حين تعيد كتابة الطلبات بحسب المستأجر.
  • لو انطبقت أيّ منها، Next.js أفضل. لو لم تنطبق أيّ منها، Vite يكاد يكون الأفضل دائماً.

    الستاك الذي استقررت عليه

  • Vite: أداة البناء وخادم التطوير
  • React 18: مكتبة الواجهة
  • TypeScript: أمان الأنواع، في الوضع الصارم افتراضياً
  • Tailwind CSS: التصميم، مع shadcn/ui للمكوّنات الأساسية
  • Supabase: الباك إند (Postgres، مصادقة، تخزين، Edge Functions)
  • TanStack Query: جلب البيانات والكاشينغ والطفرات
  • React Router: التوجيه على جهة العميل
  • Zod: التحقّق من المخطّط عند الحدود
  • Vitest + Playwright: الاختبارات الوحدوية واختبارات الطرف إلى طرف
  • هذا الستاك يغطّي حوالي 90% من عمل عملائي. سريع في التطوير، سريع في البناء، سريع في النشر، والأهم: سهل التسليم لمن يلتقط المشروع بعدي. سلّمت ثلاثة مشاريع Vite لمطوّرين مبتدئين وكان الثلاثة منتجين خلال أسبوع.

    مقارنة عمليّة

    هذا ما تبدو عليه عمليّة "اجلب وارسم" في كلا الستاكين. في Next.js App Router:

    // app/projects/page.tsx
    import { db } from '@/lib/db';
    
    export default async function ProjectsPage() {
      const projects = await db.project.findMany();
      return <ProjectsList projects={projects} />;
    }

    أنيقة في الظاهر، لكنك التزمت بتشغيل عملية Node، وقرنت شجرة المكوّنات بزمن تشغيل خادم، وأضفت طبقة تجبرك على التفكير دائماً في "هذا مكوّن خادم" أو "مكوّن عميل".

    في Vite + React + TanStack Query:

    // src/pages/Projects.tsx
    import { useQuery } from '@tanstack/react-query';
    import { supabase } from '@/integrations/supabase/client';
    
    export default function ProjectsPage() {
      const { data: projects } = useQuery({
        queryKey: ['projects'],
        queryFn: async () => {
          const { data } = await supabase.from('projects').select('*');
          return data;
        },
      });
      return <ProjectsList projects={projects ?? []} />;
    }

    أكثر إطنابة قليلاً، لكن كل شيء يحدث في المتصفّح، كل مكوّن من نفس النوع، النشر ملفات ثابتة، وسلوك الكاشينغ وإعادة الجلب صريح بدل أن يكون سحرياً.

    ما الذي أخسره

    لن أتظاهر بأن هذا مجاني. الانتقال إلى Vite يكلّفك:

  • لا مكوّنات خادم. لا تستطيع رسم البيانات على الخادم وتخطّي شحن JavaScript لها. لمعظم تطبيقات الصفحة الواحدة هذا لا يهمّ، لأن المستخدمين يتفاعلون مع التطبيق على أيّ حال وJavaScript موجود سلفاً.
  • SEO افتراضي أسوأ. لو SEO مهم، تحتاج إضافة Prerendering (أستخدم سكربت زمن البناء) أو الانتقال لستاك آخر. لا مفرّ من أن HTML المزحوف ما زال يهزم HTML المرسوم على العميل لمعظم محرّكات البحث.
  • تحسين صور يدوي. لا next/image. عادة أعالج الصور مسبقاً في زمن البناء بسكربت، أو أستخدم Cloudinary لرفع العملاء.
  • تقسيم مسارات يدوي. تُعدّ React.lazy وSuspense بنفسك بدل أن تأتي مجاناً. كلها حوالي ثلاثين سطراً إضافياً، لا أكثر.
  • لـ 90% من مشاريعي هذه المقايضات إيجابية الصافي. للـ 10% الباقية، أستخدم Next.js وأمضي.

    كيف تقرّر لمشروعك

    اسأل سؤالاً واحداً: هل تحتاج أوّل لقطة لي أن تكون HTML بمحتوى؟

    لو نعم: Next.js (أو Remix أو Astro). كلفة SSR تشتري فائدة حقيقية.

    لو لا: Vite + React. تحصل على حلقة تطوير أسرع وحُزم أصغر واستضافة أرخص ونموذج ذهني أبسط.

    هذا هو القرار كلّه. كل ما عداه (توجيه ملفات، تحسين صور، Middleware) مزايا تستطيع إضافتها لأيّ ستاك حين تحتاجها فعلاً.

    الدرس

    الإغراء في صناعتنا أن تختار الأقوى لأنك قد تحتاج كامل قوّته يوماً. الواقع أن معظم المشاريع لا تصل إلى ذلك اليوم. تصل إلى تاريخ إطلاق، تصل إلى تسليم، تصل إلى تقاعد.

    أفضل أداة هي الأبسط التي تحلّ المشكلة التي أمامك. لمعظم تطبيقات الصفحة الواحدة، هذه الأداة هي Vite + React، ولن أعود.

    أسئلة شائعة

    هل Vite مناسب لمواقع متعدّدة المستأجرين؟ جزئياً. لو كل مستأجر له نطاق فرعي يحتاج SSR، Next.js أنسب. لو المنطق متعدّد المستأجرين يحدث بعد المصادقة في تطبيق صفحة واحدة، Vite كافٍ.

    ماذا عن SEO؟ Vite بحدّ ذاته لا يقدّم SSR. لو تحتاج SEO، أضف Prerendering في زمن البناء (vite-plugin-prerender أو React Snap) أو انقل إلى Astro أو Next.js.

    كيف أتعامل مع متغيّرات البيئة في Vite؟ Vite يكشف فقط ما يبدأ بـ VITE_ للعميل، وهذا حدّ أمنيّ صحيّ. الأسرار الخلفيّة تبقى في الـ Edge Function أو في خدمة الباك إند.

    هل أستخدم React Router أم TanStack Router؟ React Router للمشاريع الجديدة لاستقرار النظام البيئيّ. TanStack Router لمشاريع تحتاج توجيهاً مكتوب الأنواع بدقّة من اليوم الأوّل.

    ما حدّ الحجم الذي يصبح فيه Vite غير كافٍ؟ لم أصل إليه. تطبيقات بمليون سطر تعمل عليه. الحدّ غالباً تنظيميّ لا تقنيّ, حين يكون فريقك ضخماً وتحتاج SSR لكلّ صفحة بسبب SEO.

    هل ما زلت أحتاج Webpack؟ لا، إلا لو ورثت مشروعاً قديماً. Vite يغطّي كلّ ما كان Webpack يفعله بسرعة أعلى وإعداد أقلّ.

    ملاحظة ختاميّة

    الانتقال بين الإطارات أمر عاطفيّ بقدر ما هو تقنيّ. أمضيت سنوات في Next.js وأقدّر ما تعلّمته منه. لكن أداة العمل اليوميّ يجب أن تطابق نمط المشاريع التي تعمل عليها لا التي تتمنّى أن تعمل عليها. لو معظم عملك تطبيقات صفحة واحدة، الستاك الأبسط يفوز. لو يومك مواقع SSR وSEO، Next.js ما زال الجواب. لا تختر بناءً على ما يبدو حديثاً، بل بناءً على ما تشحن به فعلاً.