Deploying React Apps to Azure: Vite vs NextJS — The Fruit Store Showdown
Deploying React Apps to Azure: Vite vs NextJS — The Fruit Store Showdown
In my previous articles, I explored how NextJS is fundamentally React on the server side and the mental model behind Server Components vs Client Components. That was all theory. Now it’s time to get practical.
I built the same app twice. A simple Fruit Store — browse fruits, add them to a cart, see your total. Same features, same data, same visual result. But one version uses React + TypeScript + Vite and the other uses NextJS + TypeScript + Tailwind CSS. Then I deployed both to Azure Static Web Apps.
The source code is public:
- Vite version: github.com/develprr/fruit-store-vite
- NextJS version: github.com/develprr/fruit-store-nextjs
Let me walk you through what’s different, what surprised me, and how to get both of them running on Azure.
The Vite Version: Classic React SPA
Vite gives you pure client-side React. There’s no server component concept, no routing conventions, no opinions about architecture. You get a fast bundler and hot module replacement. The rest is up to you.
Here’s the core of my Vite Fruit Store — the App.tsx:
Everything is a client component. Every piece of state, every click handler, every render — it all happens in the browser. This is the React you know if you’ve been building SPAs for the past decade.
The FruitCard component receives an onAddToCart callback as a prop:
Simple. Props in, events out. The classic React pattern.
The NextJS Version: Server Meets Client
Now here’s where it gets interesting. In the NextJS version, the page component is a Server Component by default:
Notice what’s missing? No useState. No useEffect. No event handlers. This component renders on the server, produces HTML, and sends it to the browser. Zero JavaScript for this component reaches the client.
The FruitCard is also a Server Component:
The card renders on the server. But the Add to Cart button — that’s the only part that needs interactivity, so it’s extracted into its own Client Component:
This is the pattern I talked about in my previous article: push the "use client" directive as deep as you can. The card layout and data display don’t need the browser at all. Only the button with its onClick handler does.
State Management: Props vs Context
In the Vite version, state lives in App.tsx and flows down through props. App owns the cart state, passes onAddToCart to FruitCard, passes items to Cart. Standard prop drilling.
In the NextJS version, you can’t do that. The page component is a Server Component — it can’t hold useState. So the cart state moves into a React Context:
The CartProvider wraps the entire app in layout.tsx, and any Client Component can call useCart() to access the cart. This is actually cleaner than prop drilling — the AddToCartButton doesn’t need its parent to pass a callback through every intermediate component.
But there’s a trade-off: Context forces every consumer to be a Client Component. In a simple app this is fine. In a complex app with many independent interactive elements, you need to think carefully about which components consume the context and where the client boundary lands.
Deploying to Azure Static Web Apps
Here’s where things get practical. Both apps are deployed to Azure Static Web Apps (SWA), which is Microsoft’s hosting service for static frontends. It’s free tier, globally distributed, and has built-in CI/CD support.
Deploying the Vite App
The Vite deployment is straightforward. Build the app, upload the output:
That’s it. Vite produces a dist/ folder with index.html, a CSS file, and a JS bundle. Static Web Apps serves them with a CDN. Zero configuration needed.
Deploying the NextJS App
NextJS requires one extra step. By default, NextJS produces a Node.js server application — it expects to run server-side. But Azure Static Web Apps is for static files. So you need to tell NextJS to produce a static export.
Add this to next.config.ts:
Then build and deploy:
The output: "export" flag makes NextJS pre-render all pages to static HTML at build time and skip the server runtime. The result is a folder of HTML files — just like Vite. But there’s a catch.
The Export Trade-Off
When you set output: "export" in NextJS, you lose all the server-side features that make NextJS unique:
- No API routes
- No server-side rendering on each request
- No incremental static regeneration
- No middleware
- No dynamic server functions
You’re essentially turning NextJS into a static site generator. The Server Components still benefit you at build time — they render to HTML during the build. But they don’t render on each request.
If you need actual server-side rendering, you’d need to deploy NextJS to a service that runs Node.js — like Azure Container Apps or Azure App Service. Static Web Apps won’t cut it.
For this simple Fruit Store demo, static export is perfect. All the data is hardcoded. There’s nothing dynamic on the server. But for a real production app with a database, authentication, or API calls — you’d want the full NextJS server.
See Them Live
Both apps are deployed and running on Azure. You can try them right here — or open them in a new tab.
Vite Fruit Store
Open Vite Fruit Store in new tab ↗
NextJS Fruit Store
Open NextJS Fruit Store in new tab ↗
Same features, same data, same result — built with completely different architectures. Try adding items to the cart in both. The experience is identical. The code behind it is not.
What’s Actually Different?
Let me be honest about what’s different and what isn’t in these two versions of the same app.
Bundle Size
Vite produces a single JS bundle: 194 KB (61 KB gzipped). Everything — React, the app code, all components — goes to the browser.
NextJS with static export produces pre-rendered HTML pages with smaller JS chunks. The initial HTML renders immediately without waiting for JavaScript. The hydration bundle is smaller because Server Components don’t ship any JS at all.
For a tiny demo app, this difference is negligible. For a large app with dozens of pages and heavy components, the NextJS approach would start to show real wins.
Developer Experience
Vite is simpler. One component model. No boundaries to think about. Write React, run React. The build is fast (121ms for this project). Hot reload is instant.
NextJS gives you conventions: file-based routing, layout components, the app/ directory structure. It’s more opinionated but you get more out of the box. The build is slower (1.5 seconds — still fast, but 12x slower than Vite for the same app).
Architecture Clarity
The Vite version uses prop drilling for state management. For a small app, this is fine. For a large app, you’d eventually reach for Redux, Zustand, or Context anyway.
The NextJS version forces the Context pattern from the start because Server Components can’t hold state. This is actually a healthier architecture — it separates data display (server) from interactivity (client) at the component level. You think about what needs the browser and what doesn’t.
Steps Forward: What I’d Build Next
This demo is deliberately simple. Here’s what I’d add to make it a real application, and how the approach would differ between Vite and NextJS:
1. Product API with a Database
Replace the hardcoded fruit array with API calls to a real database. In the Vite version, you’d build a separate backend (Express, Fastify, or Azure Functions) and fetch from it with useEffect. In the NextJS version, you’d query the database directly from Server Components — no separate backend needed.
2. Authentication
Add user login with Azure Entra ID or a simple auth provider. In Vite, you’d integrate an auth library and manage tokens in the client. In NextJS, you could use middleware and server-side session validation. Azure Static Web Apps also has built-in auth with Azure AD, GitHub, and Twitter — works for both.
3. Shopping Cart Persistence
Right now the cart resets on page refresh. You’d want to persist it to localStorage (client-side) or a database (server-side). The Vite version would use localStorage directly. The NextJS version could use a combination — localStorage for anonymous users, server-side storage for authenticated users.
4. Server-Side Rendering for SEO
If this were a real product catalog, you’d want search engines to index the product pages. The Vite version would need a pre-rendering solution or SSR middleware. The NextJS version already does this out of the box — Server Components produce SEO-friendly HTML by default.
5. CI/CD Pipeline
Both apps could use GitHub Actions to automatically build and deploy to Azure Static Web Apps on every push. Azure SWA has a built-in GitHub integration that generates the workflow file for you.
My Verdict
For a simple SPA with no server-side requirements — Vite wins on simplicity. It’s faster to set up, faster to build, and there’s no mental overhead from server/client boundaries.
For anything that will grow into a real product — NextJS wins on architecture. The server/client split forces you to think about what needs to go to the browser and what doesn’t. The file-based routing saves boilerplate. And when you eventually need a database, API routes, or SSR, it’s already there.
Both deploy to Azure Static Web Apps with minimal effort. The process is nearly identical: build, get a deployment token, upload. The only difference is NextJS needs output: "export" for static hosting.
Pick the tool that matches where your project is heading, not where it is today.
Okay, enough for now. I’ll be back!