History and Location State
Purpose and Scope
TanStack Router treats navigation history as a replaceable abstraction rather than as a hard dependency on one browser API. Most applications can ignore that abstraction because a browser-oriented history instance is created during router initialization, but the abstraction becomes important when an app runs in a hash-only hosting environment, a test runner, an embedded shell, or a server rendering path. This page explains the available history implementations, how an application supplies one to router creation, and how the parsed location and state objects describe what the router currently understands about the URL.
Sources: docs/router/guide/history-types.md, packages/history/src/index.ts
The key idea is that the router consumes a history object with a stable navigation contract. That object knows the current location, exposes methods for changing entries, and lets subscribers observe navigation actions. The router can therefore make the same route-matching and data-loading decisions whether the visible URL is controlled by the browser history API, by the hash fragment, or by an in-memory list. This separation also keeps the public Router API focused on route trees, links, loaders, and matching while the lower-level package owns navigation mechanics and browser integration details.
Sources: packages/history/src/index.ts
Relevant Source Files
- docs/router/guide/history-types.md — Reader-facing guide for browser, hash, and memory history, including examples that pass a custom history instance into router creation.
- docs/router/api/router/ParsedLocationType.md — API reference for the parsed location object, including URL pieces, typed search data, history state, masking data, and reload masking behavior.
- docs/router/api/router/historyStateInterface.md — API reference for augmenting application-wide history state through TypeScript module declaration merging.
- packages/history/src/index.ts — Source contract for the history package, including the RouterHistory interface, navigation methods, subscription shape, parsed path and history state types, blocker hooks, and action names.
Core History Implementations
The guide identifies three supported history creation paths. Browser history is the default, so an app that does not configure anything explicitly still receives a history implementation designed for ordinary web navigation. Hash history is intended for deployments where the server cannot rewrite arbitrary requests back to the application entry document. Memory history keeps entries outside the browser address bar and is useful for non-browser environments or situations where components should not interact with the real URL. Those choices cover the main operational environments without changing how routes are declared.
Sources: docs/router/guide/history-types.md
Choosing between these implementations is a deployment and runtime decision. Browser history gives clean paths but expects the hosting layer to serve the app for deep links. Hash history moves the route identity after the hash, which avoids many server rewrite requirements because the server receives only the document path. Memory history is the most isolated option because it starts from supplied entries and does not rely on window navigation. That makes it a natural fit for tests, story-like component environments, embedded flows, and server-side work where a full browser location is not available.
Sources: docs/router/guide/history-types.md
The public examples show the same integration shape for React and Solid: create the desired history instance, then pass it as the history option when creating the router. The important part is not the framework wrapper but the constructor boundary. Once the router has a history instance, links, imperative navigation, route matching, and subscription-driven state updates can operate through the same history interface. The guide also points server-side rendering readers to the SSR guide for automatic server history usage, reinforcing that server behavior is part of the same abstraction family.
Sources: docs/router/guide/history-types.md
import { createMemoryHistory, createRouter } from '@tanstack/react-router'
const memoryHistory = createMemoryHistory({
initialEntries: ['/'],
})
const router = createRouter({ routeTree, history: memoryHistory })System-to-Code Mapping
At the source level, the central contract is the history object consumed by the router. It carries the current location and length, stores subscribers, and exposes methods to push, replace, go by index, move back, move forward, create hrefs, block navigation, flush pending work, destroy resources, and notify subscribers. The subscriber payload includes both a location and an action, so the rest of the router can react to whether navigation was a push, replace, back, forward, or indexed movement rather than merely observing that some URL string changed.
Sources: packages/history/src/index.ts
The implementation source also shows why history state is not only user data. Parsed history state combines an extensible application state interface with internal keys used by the router, including an index value and TanStack Router key fields. That index is what allows a history implementation to reason about directional movement and whether going back is possible. The public interface keeps application state augmentable while preserving the internal bookkeeping needed for robust navigation, blocking, and subscriber notification across different history backends.
Sources: packages/history/src/index.ts, docs/router/api/router/historyStateInterface.md
Parsed Location Shape
A parsed location is the router-facing representation of where the app is now. The API reference describes it as containing a full href, pathname, typed search object, serialized search string, parsed history state, hash, and optional masking fields. This is richer than a raw browser location because the router has already separated the URL into pieces that matter to route matching, search validation, state handling, and advanced URL presentation. When code asks for current location data, it is typically working with this parsed structure rather than with an unprocessed string.
Sources: docs/router/api/router/ParsedLocationType.md
The search fields are deliberately split into structured and string forms. The typed search value represents validated search state as understood by the router, while the serialized search string preserves the URL representation. The state field points to parsed history state, so route code and navigation code can carry entry-specific metadata alongside the URL. The optional masked location and reload behavior fields support route masking patterns where the displayed URL may differ from the underlying matched location. That makes location state a bridge between ordinary navigation and more advanced user experiences such as modal routes or privacy-preserving URLs.
Sources: docs/router/api/router/ParsedLocationType.md
interface ParsedLocation {
href: string
pathname: string
search: TFullSearchSchema
searchStr: string
state: ParsedHistoryState
hash: string
maskedLocation?: ParsedLocation
unmaskOnReload?: boolean
}History State Extension
The history state API is intentionally open to application augmentation. The reference describes an exported interface from the history package that can be extended across an application, and the example uses TypeScript declaration merging through the router package module. This pattern is useful when every navigation state object should be allowed, or required, to carry additional metadata. Because the type is shared, route code, navigation helpers, and consuming components can agree on the shape of state without relying on loose object conventions spread throughout the application.
Sources: docs/router/api/router/historyStateInterface.md
That extensibility should be used for entry-specific data, not as a replacement for route params, search params, or loader data. Params belong in the path when they identify the route, search belongs in the URL when it should be shareable or reloadable, and loader data belongs in the router data flow when it is fetched or cached. History state is best for metadata tied to a single entry in the navigation stack. The source reinforces this distinction by combining user-augmentable fields with internal index and key fields that are about navigation identity rather than route content.
Sources: docs/router/api/router/historyStateInterface.md, packages/history/src/index.ts
declare module '@tanstack/react-router' {
interface HistoryState {
additionalRequiredProperty: number
additionalProperty?: string
}
}Navigation Actions and Blocking
The history contract names the navigation actions that subscribers and blockers receive. Push and replace create or update entries, back and forward move directionally, and go moves by an explicit index delta. Blocking receives the current location, next location, and action, which gives a blocker enough context to decide whether to allow the transition. Navigation methods also accept an option that can ignore blockers, making it possible for trusted flows to bypass confirmation behavior when that is appropriate. These details matter when debugging why navigation did or did not complete.
Sources: packages/history/src/index.ts
For browser history, source comments indicate that notification behavior can differ when index changes are already reported by the platform popstate event. The abstraction therefore has to avoid double notification while still keeping its cached current location fresh. This is one reason application code should usually subscribe through router-facing APIs rather than directly coupling itself to browser events. The history layer normalizes these differences so higher-level route matching and rendering see coherent location updates, regardless of the backend that produced them.
Sources: packages/history/src/index.ts
Compact Reference
| Concept | Source-level name | What it means |
|---|---|---|
| Default web history | createBrowserHistory | Browser-oriented routing used when no custom history is supplied. |
| Hash routing | createHashHistory | Tracks routing state with the hash for hosts that cannot rewrite deep links. |
| Isolated routing | createMemoryHistory | Keeps entries in memory for tests, non-browser environments, or URL-independent components. |
| Router history object | RouterHistory | Interface with location, length, subscribers, navigation methods, blockers, flush, destroy, and notify. |
| Raw path pieces | ParsedPath | href, pathname, search string, and hash before router-level parsing adds state and typed search. |
| Location with state | HistoryLocation | Parsed path plus parsed history state. |
| Router parsed location | ParsedLocation | Router-facing location with typed search, search string, state, hash, and optional masking information. |
| Application history state | HistoryState | Extensible interface that applications can augment with declaration merging. |
| Internal state | ParsedHistoryState | HistoryState plus TanStack Router key and index bookkeeping. |
Practical Flow
A practical setup starts by deciding whether the deployment can support clean URLs. If it can, the default browser history is usually enough and no explicit configuration is required. If deep links cannot be rewritten to the application document, create a hash history and pass it to router creation. If the app is running outside normal browser navigation, create a memory history with initial entries. After that, treat navigation state as part of the router system: use typed params and search for URL data, use history state for entry metadata, and inspect parsed locations when debugging current URL interpretation.
Sources: docs/router/guide/history-types.md, docs/router/api/router/ParsedLocationType.md, docs/router/api/router/historyStateInterface.md, packages/history/src/index.ts
Next, read the navigation and links material for day-to-day movement APIs, the search params material for typed URL state, and the route masking material if your application needs a displayed URL that differs from the matched location. When behavior is surprising, map the symptom to the layer first: hosting rewrites point to browser versus hash history, test isolation points to memory history, typed URL data points to parsed location search, and entry-specific metadata points to history state. That separation makes debugging significantly faster.