Modern React 21: Framework choice and project setup
This is part 21 of the Modern React development series.
A React app needs more than components once it becomes a product. Routing, data loading, code splitting, rendering strategy, forms, mutations, deployment, and error handling all need an owner. A framework bundles answers to many of those choices.
Concept
A React framework is an application layer around React that supplies project structure and production features. A client-only setup uses React with a build tool and adds application pieces separately.
Terms
- Framework: A project structure and runtime model that handles common app concerns around React.
- CSR: Client-side rendering, where the browser runs JavaScript to produce the UI.
- SSR: Server-side rendering, where the server produces HTML for a route request.
- SSG: Static site generation, where HTML is produced at build time.
- SPA: Single-page app, a browser app that handles navigation client side after initial load.
Mental model
Think of framework choice as deciding who owns the roads. React gives you vehicles. A framework supplies routes, loading lanes, signs, and deployment rules.
How it is used
Choose a framework when the app needs routes, server rendering, data loading conventions, Server Components, form Actions, static generation, or production deployment defaults. Choose a smaller Vite-style setup for embedded widgets, internal tools, demos, and client-only apps with simple needs.
How to use it
- List the app’s route, data, authentication, rendering, and deployment needs.
- Prefer a framework when several of those needs are product requirements.
- Use a client-only build tool when the app truly does not need framework-level features.
- Pick tooling that supports TypeScript, linting, tests, and production builds from the start.
- Document the rendering strategy so future features do not fight the setup.
Example: Framework decision shape
import type { ProjectNeed } from "./projectNeeds";
export function chooseReactStart(need: ProjectNeed) { if (need.routes && (need.serverRendering || need.serverMutations)) { return "Start with a React framework"; }
if (need.staticPages || need.routes) { return "Compare a framework with a Vite app plus router"; }
return "A client-only Vite app is a reasonable starting point";}export function chooseReactStart(need: ProjectNeed) {
if (need.routes && (need.serverRendering || need.serverMutations)) {
return "Start with a React framework";
}
if (need.staticPages || need.routes) {
return "Compare a framework with a Vite app plus router";
}
return "A client-only Vite app is a reasonable starting point";
}
The decision is about ownership of application concerns, not taste in folder names.
Example: Client entry point
import { createRoot } from "react-dom/client";import { App } from "./App";
const rootElement = document.getElementById("root");
if (!rootElement) { throw new Error("Missing root element");}
createRoot(rootElement).render(<App />);const rootElement = document.getElementById("root");
if (!rootElement) {
throw new Error("Missing root element");
}
createRoot(rootElement).render(<App />);
A scratch client app owns its entry point directly. A framework often generates or hides this layer.
Details to watch
- Framework value: React docs recommend starting new apps and sites with a framework for production features.
- Scratch cost: A scratch app still needs choices for routing, data fetching, code splitting, styling, and deployment.
- Rendering strategy: CSR, SSR, and SSG affect performance, hosting, data access, and hydration.
- Migration pressure: Starting small is fine, but adding framework-like features later means the app now owns that framework work.
Series navigation
- Previous: Part 20: Performance and React Compiler
- Next: Part 22: Next.js App Router
- Series index: Modern React development