From 57e69e9dec3a5dcb62e84afe24e3b10c6633e1a7 Mon Sep 17 00:00:00 2001 From: ladybluenotes Date: Thu, 10 Sep 2026 19:18:47 -0700 Subject: [PATCH 1/2] perf(history): avoid generating unused location keys --- .changeset/lazy-locations-listen.md | 5 +++++ packages/history/src/index.ts | 8 +++++--- packages/history/tests/parseHref.test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 .changeset/lazy-locations-listen.md diff --git a/.changeset/lazy-locations-listen.md b/.changeset/lazy-locations-listen.md new file mode 100644 index 0000000000..d048bbebe4 --- /dev/null +++ b/.changeset/lazy-locations-listen.md @@ -0,0 +1,5 @@ +--- +'@tanstack/history': patch +--- + +Avoid generating unused history keys for locations with existing state. diff --git a/packages/history/src/index.ts b/packages/history/src/index.ts index b8fd3f016c..29ee775191 100644 --- a/packages/history/src/index.ts +++ b/packages/history/src/index.ts @@ -749,8 +749,10 @@ export function parseHref( const sanitizedHref = normalizeHref(href) const hashIndex = sanitizedHref.indexOf('#') const searchIndex = sanitizedHref.indexOf('?') - - const addedKey = createRandomKey() + if (!state) { + const key = createRandomKey() + state = { [stateIndexKey]: 0, key, __TSR_key: key } + } return { href: sanitizedHref, @@ -772,7 +774,7 @@ export function parseHref( hashIndex === -1 ? undefined : hashIndex, ) : '', - state: state || { [stateIndexKey]: 0, key: addedKey, __TSR_key: addedKey }, + state, } } diff --git a/packages/history/tests/parseHref.test.ts b/packages/history/tests/parseHref.test.ts index e1a95fafa4..10df49d330 100644 --- a/packages/history/tests/parseHref.test.ts +++ b/packages/history/tests/parseHref.test.ts @@ -21,6 +21,30 @@ describe('parseHref', () => { expect(parsed.hash).toEqual('#qux') }) + test.each([ + { __TSR_index: 2 }, + { __TSR_index: 2, key: 'legacy-key', __TSR_key: 'current-key' }, + ])('preserves supplied state %j', (state) => { + Object.freeze(state) + expect(parseHref('/foo', state).state).toBe(state) + }) + + test('creates independent initial states with matching history keys', () => { + const first = parseHref('/foo', undefined) + const second = parseHref('/bar', undefined) + + for (const location of [first, second]) { + expect(location.state).toEqual({ + __TSR_index: 0, + key: expect.any(String), + __TSR_key: location.state.key, + }) + } + + first.state.__TSR_index = 1 + expect(second.state.__TSR_index).toBe(0) + }) + describe('open redirect prevention', () => { test('strips CR characters to prevent open redirect', () => { // If \r (CR) is in the href, it should be stripped From a2f58a3d9ac5b058a4381f817b5e64238565aa59 Mon Sep 17 00:00:00 2001 From: Sarah Gerrard Date: Sat, 12 Sep 2026 13:15:57 -0700 Subject: [PATCH 2/2] refactor(history): drop redundant empty-state fallback in assignKeyAndIndex Spreading undefined is a no-op, so the guard was dead weight. --- packages/history/src/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/history/src/index.ts b/packages/history/src/index.ts index 29ee775191..1c9bd5ed18 100644 --- a/packages/history/src/index.ts +++ b/packages/history/src/index.ts @@ -281,9 +281,6 @@ export function createHistory(opts: { } function assignKeyAndIndex(index: number, state: HistoryState | undefined) { - if (!state) { - state = {} - } const key = createRandomKey() return { ...state,