50 Lines of Code That Demystify React Router: A Frontend Routing Epiphany
For Western developers who treat React Router as a black box, this 50-line implementation strips away the framework complexity and reveals the raw browser APIs underneath. Understanding that hash vs history is just hashchange vs popstate, and that pushState doesn't trigger popstate, directly impacts debugging and architectural decisions — especially when handling SSR, lazy loading, or the dreaded history-mode 404 on refresh.
After years of using React Router without truly understanding it, a developer spent two hours writing a 50-line mini router from scratch. The result is a clean class that handles route registration, URL change listening, path matching (including dynamic parameters like /user/:id), and programmatic navigation — all in under 50 lines.
The implementation makes the core principle explicit: frontend routing is an event-driven mapping table. Hash mode listens to the hashchange event; history mode listens to popstate. The critical gotcha is that pushState and replaceState do not trigger popstate, so manual route handling is required after programmatic navigation.
The article also tackles six common interview questions, including how to handle history mode's 404-on-refresh (it's a server-side problem solved by an Nginx try_files fallback), how to add route guards with a beforeEach hook, and how to extract structured parameters from dynamic routes using regex. The key insight is that React Router's thousands of lines are just engineering on top of this 50-line core — nested routes, lazy loading, scroll restoration, and SSR are all additions, not fundamental changes.
The fact that pushState doesn't trigger popstate is the single most common source of confusion in history-mode routing — and the 50-line implementation makes it impossible to miss.
The article's claim that 'React Router's thousands of lines are just engineering on top of this 50-line core' is a powerful reframing that demystifies a framework many developers treat as magical.
The choice between hash and history mode is presented as a simple tradeoff (no server config vs. clean URLs), but the real cost of history mode is the operational burden of ensuring every deployment has the correct server fallback.
The 50-line implementation's use of a Map instead of a plain object for routes is a subtle but real interview signal — it shows awareness of insertion-order guarantees.
The article's argument that AI cannot replace hand-written learning for debugging and design judgment is a timely counterpoint to the 'AI writes everything' trend in developer culture.
The regex-based dynamic parameter matching (converting :id to ([^/]+)) is elegant but has a hidden limitation: it cannot handle nested or complex parameter patterns without becoming unwieldy.
The beforeEach guard implementation is minimal but reveals a design tension: should guards block navigation or redirect? The article's example does both, which can lead to infinite loops if not carefully managed.
The article's framing of 'use AI to write code, hand-write to learn principles' is a pragmatic middle ground that acknowledges AI's utility while defending the value of deep understanding.