React / Coordinated application behavior / Lesson 11
Routing and navigation boundaries
Keep React rendering distinct from URL routing, preserve native links, and coordinate route identity and loading deliberately.
On this page
Lesson goals
- A route is an application boundary
- Demonstrate location transport without inventing a router
- Coordinate lifetime, pending UI, and errors
A route is an application boundary
React does not include a complete router. A framework or routing library can map locations to components, coordinate nested layouts, load data, and handle server requests. Choose one supported integration rather than assembling a production router from scattered window.location reads. The URL should represent shareable navigation state where appropriate, while temporary input may remain local. Distinguish route parameters, query parameters, and component state, including parsing and authorization. A route match selects a screen; it does not prove the current user may read or mutate the underlying resource. Keep those checks at the trusted server boundary.
Preserve navigation semantics
Use anchors for destinations and buttons for actions. A router-provided link should preserve meaningful href behavior, keyboard use, and ordinary browser features such as opening another tab where supported. Do not make every navigation item a div with an onClick handler. Announce the current location appropriately, often with aria-current on the relevant link, and manage page title and focus when a client navigation changes the principal content. Those concerns are not solved merely because the component tree changed. Test browser Back and Forward, direct entry, refresh, and a missing route under the selected deployment arrangement.
Demonstrate location transport without inventing a router
The first example is an explicitly tiny hash-location demonstration, not a production routing recommendation. It subscribes to hashchange with cleanup, maps two known fragments to content, and keeps native anchors. Hash navigation differs from server path routing, so the example does not require a server fallback or pretend to implement nested routes, loaders, pending navigation, or not-found handling for a full product. Its limited value is showing that a browser location is external state requiring a subscription, not a value React automatically rereads on every change. A chosen routing integration should own that concern in a real application.
import { useEffect, useState } from "react";
export default function App() { const [hash, setHash] = useState(() => window.location.hash); useEffect(() => { const update = () => setHash(window.location.hash); window.addEventListener("hashchange", update); return () => window.removeEventListener("hashchange", update); }, []); return <main><nav aria-label="Local demonstration"><a href="#intro">Introduction</a> <a href="#practice">Practice</a></nav><h1>{hash === "#practice" ? "Practice" : "Introduction"}</h1><p>Limited hash demonstration, not a complete router.</p></main>; }
Following Practice changes the heading to Practice.
Native fragment navigation drives a cleaned-up browser subscription. Only two local states are illustrated.
Keep route selection separate from rendering
The second example receives a selected record id through props and renders the corresponding view from a known local map. In a framework, a route adapter could supply that validated id after its own loading and access policy. The component itself is independent of a particular router package, making its rendering contract easy to test. Unknown ids produce an explicit missing state. Do not parse arbitrary path text into a trusted database key without validation, and do not expose a private record in a client bundle merely because the route link is hidden from the navigation.
const records = { a: "Alpha workshop", b: "Beta workshop" };
function RecordScreen({ id }) { const title = records[id]; return title ? <article><h2>{title}</h2><p>Known local record.</p></article> : <p>Record not found</p>; }
export default function App() { return <main><h1>Route adapter boundary</h1><RecordScreen id="b" /></main>; }
Route adapter boundary
Beta workshop
Known local record.
The screen consumes a known id independently of routing transport. An unknown id has a separate missing result.
Coordinate lifetime, pending UI, and errors
A route transition can preserve a layout while replacing a child screen, or remount a subtree and reset its state. Choose keys and placement according to that lifecycle rather than using random keys to force updates. Data loaders need stale-navigation protection and recoverable errors. An authorization denial, missing record, network failure, and malformed route are different outcomes. Keep analytics and other navigation effects tied to the integration’s documented commit boundary to avoid counting abandoned transitions. The production router or framework’s own official documentation supplies deployment and server behavior that React’s component API does not define.
Common errors and fixes
A route works after a click but fails on direct entry
- Why it happens
- Deployment and server routing were never coordinated.
- How to fix it
- Follow the chosen framework or router’s deployment contract.
- Prevent it next time
- Test refresh and direct navigation.
Hiding a link is treated as authorization
- Why it happens
- Navigation visibility was confused with access control.
- How to fix it
- Enforce access at the trusted resource boundary.
- Prevent it next time
- Test unauthorized direct requests separately.
Navigation changes content but leaves users disoriented
- Why it happens
- Title, focus, and current-link state were ignored.
- How to fix it
- Implement the integration’s accessible navigation policy.
- Prevent it next time
- Test the entire keyboard journey.
Check your understanding
1. What should a direct-entry test cover for a nested route?
Show worked solution
Request the actual route URL under the intended server arrangement, verify its data and authorization handling, then refresh and use Back and Forward. A component-only test cannot establish that the deployment serves the route or that the server protects its records.
2. Why keep a record screen independent of the router package where practical?
Show worked solution
It can accept validated inputs and render explicit loading, missing, denied, or success states under a small contract. The route adapter owns location parsing and integration behavior. This simplifies isolated tests and prevents framework details from spreading through every presentation component.
Key takeaways
- React renders; routing integration owns location policy.
- Keep links native and navigation accessible.
- Route matching is not authorization.