Chapter 20 of 20 · React for Beginners — Complete Free Tutorial
Error Boundaries and Deployment
The final chapter of the React for Beginners series covers the two things that separate a demo from a shipped product: surviving failures gracefully and putting the build on the internet. First you will learn what happens when a component throws, how error boundaries contain it, and why one legacy class-component API still matters. Then you will build and deploy the Vite app you started in chapter 2.
What happens when a render throws
An uncaught exception during rendering used to unmount the whole React tree — a blank white page. React still removes the UI above the failure, so without a boundary a single bad component takes down everything. Errors in event handlers do not unmount the tree (they bubble to window like normal JS); render-time, lifecycle and constructor errors do.
| Error type | Handled by an error boundary? |
|---|---|
| Thrown during render or lifecycle | Yes |
| Error inside another error boundary's rendering | Caught by the next boundary above it |
| Error in an event handler | No — use try/catch there |
Async code (setTimeout, promises) | No — handle with catch |
Errors deliberately thrown (throw) | Yes, if thrown in render |
What an error boundary is
An error boundary is a component that catches errors in its subtree and renders a fallback instead of disappearing. It must be a class component — React exposes the two lifecycle methods it needs (getDerivedStateFromError and componentDidCatch) only on classes. This is the one place the class syntax from old tutorials still earns its keep; everything else in your app stays hooks-based.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, message: '' };
}
static getDerivedStateFromError(error) {
return { hasError: true, message: error.message };
}
componentDidCatch(error, info) {
console.error('Boundary caught:', error, info.componentStack);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback" role="alert">
<h2>Something went wrong.</h2>
<p>{this.state.message}</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
Expected output: when a child throws during render, the fallback panel appears with the error message; the rest of the page around the boundary keeps working. Resetting state re-renders the subtree (and may throw again — that is acceptable for a user-facing retry).
Where to place boundaries
Placement is strategy. Wrapping only <App /> contains the crash but replaces the entire page; wrapping each major section keeps navigation and unrelated UI alive:
<AppShell>
<ErrorBoundary key={location.pathname} fallback={<PageCrashed />}>
<Routes>…</Routes>
</ErrorBoundary>
<Sidebar />
</AppShell>
Expected output: a crash inside one route shows a per-page fallback while the sidebar and header remain usable. A key tied to the route resets the boundary on navigation, and logging on the server (or to an error service) turns the componentDidCatch report into something you can act on.
Errors outside rendering
Boundaries catch render-time failures only. Plan the other paths deliberately: try/catch inside event handlers and effects, rejected-promise handling with catch around every fetch, and a React-level fallback for the rare unhandled rejection. The ErrorBoundary above handles rendering; your data layer handles its own errors — you covered both patterns in chapter 13.
Building for production
Vite turns your source into static files ready for any static host:
npm run build
npm run preview
Expected output:
vite v6.x.x building for production...
✓ 142 modules transformed.
dist/index.html 0.46 kB │ gzip: 0.30 kB
dist/assets/index-Bx7q2m.css 12.31 kB │ gzip: 3.21 kB
dist/assets/index-Ck1v9p.js 143.08 kB │ gzip: 46.12 kB
✓ built in 1.21s
The dist/ folder is plain HTML, CSS and JavaScript — no Node required to serve it. npm run preview runs the production bundle locally so you can test the real thing before uploading.
Deploying
Any static host works; the workflow is the same everywhere:
- Push to GitHub — keep your repository as the source of truth.
- Connect the repo in Netlify, Vercel or Cloudflare Pages; build command
npm run build, output directorydist. - Add the SPA fallback — every unknown path must serve
index.html, or deep links like/posts/42404 on refresh. Netlify: a_redirectsfile containing/* /index.html 200; Vercel:{"rewrites": [{"source": "/(.*)", "destination": "/index.html"}]}invercel.json. - Set the base path if not at the domain root — a project served from
/my-app/needsbase: '/my-app/'invite.config.js. - Verify — load the site, refresh a deep link, click through navigation, and check the console for 404s on hashed assets.
This fallback is the deployment-side half of routing: the server always returns your shell, and React Router decides which view to show.
Common mistakes
- Expecting boundaries to catch everything — event handlers, timers and promise rejections need ordinary
try/catch. - One boundary at the root only — a single crash blanks the whole app; wrap meaningful sections instead.
- Deploying without the SPA fallback — the home page works, then any refresh or deep link 404s.
- Wrong output directory or base path — blank page with 404s on assets; confirm
distandbasematch the URL you serve from. - Serving
node_modulesor source files — onlydist/goes live; the build has already bundled what the browser needs.
Practice checklist
- Wrap a deliberately throwing component in
ErrorBoundaryand verify the fallback renders. - Add a per-section boundary and confirm the rest of the page survives a crash.
- Log a render error with
componentDidCatchand read the component stack. - Run
npm run buildandnpm run preview; confirm hashed assets load. - Deploy to a static host with the SPA fallback, then refresh a deep link to prove it works.
Congratulations — that is the full React for Beginners path, from an empty folder to a live URL. Browse the other tracks at the tutorials index to keep going, starting with JavaScript Best Practices and Next Steps.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.