Plain React with Vite

A Vite 25,978 single-page app is React 7,897 with nothing added: an index.html, a main.tsx that calls createRoot, and a bundler. No framework to learn, no file-based routing, no server. The build emits static files that any web server, S3 bucket or CDN will serve.

Scaffolding and building a React SPA with ViteShell
npm create vite@latest shop -- --template react-ts
cd shop && npm install && npm run build
Output
vite v8.3.0 building client environment for production...
✓ 20 modules transformed.
dist/index.html                   0.45 kB │ gzip:  0.29 kB
dist/assets/index-BRDr3nmD.js   222.52 kB │ gzip: 69.27 kB
✓ built in 156ms

That 69 kB gzipped is React, React DOM and the app, and it is the floor for any React SPA: the browser downloads it, parses it, and only then does anything appear. Nothing in the HTML is useful to a crawler that does not run JavaScript, and nothing renders on a slow first visit until the bundle lands.

For a large class of applications that is exactly right. An internal dashboard, an admin console, a trading screen, the authenticated half of a product — none of these are indexed, none get social previews, and the first paint happens once per session before a user who is already committed. Server rendering buys them nothing and costs a server you now have to deploy, patch, scale and be paged about.

Choose plain React with Vite when SEO genuinely does not matter; when your data already lives behind an API — such as the Express 24,430 service of Express.js — and you are happy fetching it from the client with TanStack Query 79,069 or RTK Query (TanStack Query and Queries with RTK Query); and when you want deployment to mean copying a directory.

What you give up is real. You write your own routing setup (Routing with React Router), code-splitting boundaries, data-fetching conventions, <head> management and auth redirects. None of it is hard; all of it is work a framework would have done, and every team ends up with a slightly different version of it.