# Tooling and Routing
# Packages
This lecture targets React 18 with the modern createRoot API. Keep the runtime and its types on the same major version:
| Package | Role | Where it belongs |
|---|---|---|
react | Component/hook runtime | dependencies |
react-dom | DOM renderer | dependencies |
@types/react, @types/react-dom | Type declarations only | devDependencies |
react-router-dom 6 | Routing | dependencies (ships its own types) |
react-router-dom v6 includes built-in TypeScript declarations—do not add @types/react-router-dom, which targeted older major versions. Continue with TypeScript 5, ts-loader 9 and Webpack 5 from the lecture 4 setup. Older projects may use "jsx": "react" and ReactDOM.render; the snippets here show the current equivalents. Nothing is installed into the course repository itself—configure this in your own project.
# TypeScript for JSX
Starting from the lecture 4 tsconfig.json, add one option and keep the rest:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2020", "DOM"],
"jsx": "react-jsx",
"strict": true,
"noEmitOnError": true,
"sourceMap": true,
"outDir": "dist"
},
"include": ["src/**/*"]
}
"jsx": "react-jsx" emits calls to the automatic runtime—no import * as React is needed in files that only use JSX (still import it to use React.useState etc.). The older "jsx": "react" setting compiles JSX to React.createElement, which requires React to be in scope in every file.
# Webpack changes
Extend the lecture 4 webpack.config.cjs rather than writing a second config. Only three fields change—the entry, the loader test, and the resolved extensions:
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: { extensions: ['.tsx', '.ts', '.js'] },
Keep everything else already in the configuration: plugins, output, devtool, and any rules you added earlier (e.g. the CSS style-loader/css-loader rule from the lecture 4 development workflow stays alongside the tsx rule).
# Mounting the app
React 18 replaces the legacy ReactDOM.render API with createRoot from react-dom/client:
import { createRoot } from 'react-dom/client';
import App from './components/App';
const container = document.getElementById('root');
if (!container) {
throw new Error('Missing #root element in index.html');
}
createRoot(container).render(<App />);
The null guard is required: getElementById returns HTMLElement | null, and an unchecked createRoot(document.getElementById("root")) fails type-checking under strict.
Replace the lecture 4 mount point with a plain wrapper—<div id="root"></div> instead of <main id="root"></main>—because routed pages render their own <main> elements and nested <main> landmarks are invalid HTML. The root container is just a mount point; each page component owns its semantic structure.
Keep the script distinction from Webpack development: build emits assets and exits, watch rebuilds on change, and webpack serve is the dev server. Naming a script "build": "webpack --watch" mislabels a watch command—name it watch instead.
# Routing with react-router-dom v6
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function Home() {
return (
<main>
<h2>Home</h2>
<Link to="/products">Products</Link>
</main>
);
}
function ProductPage() {
return (
<main>
<h2>Products</h2>
<Link to="/">Home</Link>
</main>
);
}
function NotFound() {
return (
<main>
<h2>Not found</h2>
<Link to="/">Back home</Link>
</main>
);
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
v6 essentials: <Routes> replaces <Switch>, each <Route> takes an element (not component), "*" is the catch-all, and <Link to="…"> navigates without a page reload.
# Serving routes in development and production
Client-side routing means deep links like /products only work if the server returns index.html for document requests. For webpack-dev-server:
devServer: {
historyApiFallback: true,
proxy: [
{
context: ['/api'],
target: 'http://127.0.0.1:3000'
}
]
}
historyApiFallback rewrites document requests to index.html; it does not rewrite /api calls (the proxy handles those—note the webpack-dev-server 5 array syntax from lecture 4, not the older object form) and must not swallow real asset requests: a missing bundle.js should 404, not return HTML. In production your web server needs the same fallback rule for document routes, applied after real files and /api routes. This fallback is about document routing only—authentication and CORS are separate concerns.
# A note on Create React App
Create React App (CRA) was the standard 2022 bootstrap but is now deprecated and is not recommended for new projects. The manual TypeScript + Webpack setup above continues what you built in lecture 4 and makes every step visible.
Try it: start from your lecture 4 build, apply the three Webpack edits and the jsx option, mount <App /> with createRoot, then add two routes and confirm that a direct request to /products works with historyApiFallback but fails with a 404 without it.