Build and Deployment

This section documents React 19.x for the web, using function components and Hooks throughout (class components appear only where React still requires them, such as error boundaries). React Native is out of scope and gets only a pointer. The React 19 additions follow the current official React documentation. This content was generated with the assistance of AI and should be verified against react.dev before being relied on in production, since React APIs continue to evolve between releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

A React app ships as static assets, a running server, or both. This page covers bundling with Vite and the three deployment shapes, with a worked static-SPA deploy.

Bundling

A bundler walks the module graph from your entry point, applies transforms (JSX → JS, TypeScript stripping), removes unused exports (tree-shaking), minifies, splits code into chunks, and writes content-hashed filenames (app.9f3a1c.js) so browsers can cache aggressively and still get updates.

Vite is the default toolchain: an esbuild-based dev server with hot module replacement, and Rollup for the production build.

npm run build        # -> dist/  (index.html + hashed assets)
npm run preview      # serve dist/ locally to sanity-check
  • Dynamic import() becomes a separate chunk loaded on demand — the mechanism behind code splitting.

  • import.meta.env exposes build-time config; only variables prefixed VITE_ are sent to the client.

  • Source maps (build.sourcemap: true) map minified stack traces back to your source; upload them to your error tracker rather than serving them publicly.

Three deployment models

  • (a) SPA / static build — upload dist/ to any static host or CDN (Netlify, Vercel, GitHub Pages, S3
    CloudFront). Configure a SPA fallback: rewrite unknown paths to /index.html so client-side routes work on refresh. No server. Best for dashboards, internal tools, auth-gated apps where SEO does not matter.

  • (b) SSR — a long-running Node or edge server calls renderToPipeableStream per request (or use a platform like Vercel/Netlify that runs your framework’s server for you). Best when pages are dynamic, per-user, and need fast first paint or SEO. See Server Rendering.

  • (c) SSG / prerender — render HTML per route at build time and serve the files statically, optionally revalidating (ISR). Best for content that changes rarely.

Worked example B — a Vite React SPA on AWS S3 + CloudFront

A client-rendered SPA that calls a REST API with fetch/axios, built to static files and served from S3 behind CloudFront. No server-side rendering, so no server to run.

# 1. build
npm run build
#   dist/index.html
#   dist/assets/index-4e9b12.js
#   dist/assets/index-9a1f77.css

# 2. deploy: hashed assets get a long cache; index.html must not be cached
aws s3 sync dist/ s3://my-app-bucket --delete \
  --cache-control "public,max-age=31536000,immutable" \
  --exclude index.html
aws s3 cp dist/index.html s3://my-app-bucket/index.html \
  --cache-control "no-cache"

# 3. invalidate the CDN so index.html is refetched
aws cloudfront create-invalidation --distribution-id E123ABC --paths "/index.html"

CloudFront config: set the default root object to index.html, and add a custom error response mapping 403/404 to /index.html with a 200 status so deep links like /reports/42 load the SPA and let the client router take over. The API lives on its own origin, so it must send CORS headers for the SPA’s domain.

// the SPA just calls the API over the network
const res = await fetch(`${import.meta.env.VITE_API_URL}/products/42`);
const product = await res.json();
flowchart TB subgraph SPA["SPA on S3 + CloudFront"] b1["Browser"] -->|"HTML + JS (static)"| cf["CloudFront / S3"] b1 -->|"XHR / fetch"| api1["REST API origin"] end subgraph SSRv["SSR for comparison"] b2["Browser"] -->|"HTML per request"| srv["SSR server"] srv -->|"data"| api2["API / DB"] end

CI/CD and environment variables

  • CI/CD — on push, install, run tests and type-check, npm run build, then publish: upload dist/ (SPA/ SSG) or deploy the server image (SSR). Keep the build reproducible (lockfile, pinned Node).

  • Build-time vs. run-time env — anything in import.meta.env.VITE_* is baked into the bundle at build and visible to users; never put secrets there. Runtime secrets belong on the server (SSR) or in the API, not the client bundle. Rebuild to change a build-time value.