<Routes> looks at every <Route> beneath it and renders exactly one: the best match for the current location, not the first match, and never several at once. <Route> does nothing outside a <Routes> — it is a configuration object wearing JSX syntax, not a component that renders.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Catalog />} />
<Route path="/products/:sku" element={<Product />} />
<Route path="/docs/*" element={<Docs />} />
<Route path="*" element={<NotFound />} />
</Routes>Path patterns have four pieces. A literal segment matches itself; :sku is a dynamic segment, readable with useParams, and a ? suffix such as :page? makes it optional; a trailing /* is a splat that swallows the rest of the URL; and * alone matches everything, which is how the last row becomes a 404. Regular-expression constraints — the old :id(\d+) syntax — are gone. Besides path and element, a <Route> takes Component={Product} in place of element, index for the child that fills the parent's own URL (Nested Routes and Outlet), and caseSensitive; omitting path makes a pathless layout route.
"Best match" is scored, not ordered: static segments beat dynamic ones, dynamic ones beat splats, and a longer pattern beats a shorter one. So /products/new reaches a path="/products/new" route even when /products/:sku is written above it; row order is a readability choice. Gone since v6: exact and strict (matching is exact now, which is why <Switch> was renamed <Routes>), the per-route location override, the render and children render props, and an array of paths in one path. The rendered component also receives no props — no match, no location, no history. Call the hooks.