Advanced Patterns — auth · blocking · masking

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on frontend/tanstack-start: API endpoints & middleware — middleware patterns; auth + blocking extend the same route lifecycle (gates that run before a navigation commits).

three gates around every navigation

A route is more than a component. The router runs a lifecycle around each navigation: beforeLoad can throw redirect() (the auth gate), a blocker can pause or cancel the transition (the unsaved-changes gate), and masking decides which URL lands in history vs. which route actually renders. The order matters: block check → auth check → commit URL → render.

patterngateAPIwhat it can do
Authenticated route before navigation commits beforeLoad + throw redirect() abort → send to /login, remember the target, redirect back after auth
Navigation blocking before navigation commits useBlocker({ shouldBlockFn, withResolver }) pause → show "unsaved changes?" UI → proceed() or reset()
Location masking at commit (history write) <Link mask={{ to }}> / createRouteMask() render route A, but persist a clean URL B to history + the address bar

1 · the auth + blocking simulator (edit me)

2 · Babel compiles JSX → element tree

<AuthDemo/> becomes React.createElement(AuthDemo). navigate() runs the two gates in order — blocker first (pause + remember target), then auth (redirect to /login + remember destination). The blocker's proceed/reset map to forceNavigate/ cancelBlock.

// (hit "compile & render" to see Babel's output)

3 · live React (the gate pipeline, proven)

Click Dashboard while logged out → you're sent to /login and the destination is remembered. Login sends you back. In the Editor, toggle unsaved changes and try to leave — navigation pauses. Force navigate resolves the block. The gold-check walks all six steps automatically.

route: · user: · gold: home→login→dashboard→editor→block→force→home

intent → pattern

intentpatternwhy
require login for a route beforeLoad + throw redirect({'{'}to:'/login', search:{'{'}redirect: location.pathname{'}'}{'}'}) redirect aborts the navigation; search param remembers the target
redirect back after login read search.redirect on the login route → navigate({'{'}to: redirect{'}'}) the "remember where I was going" round-trip
block leaving with unsaved changes useBlocker({'{'}shouldBlockFn: () ={'>'} isDirty, withResolver: true{'}'}) returns {'{'}proceed, reset, status{'}'} — drive a custom dialog
also block tab close / reload enableBeforeUnload: isDirty on the blocker registers the native beforeunload handler conditionally
show a clean URL, render a different route <Link to="/photos/$id/modal" mask={'{'}to:'/photos/$id'{'}'}> address bar + history show the mask; router renders the real route
shareable masked URLs app-wide createRouteMask({'{'}routeTree, from, to{'}'}) → router routeMasks declare once; every matching navigate is masked