Skip to content

Commit d20c878

Browse files
authored
fix: issue 7240 causing fouc (#7250)
1 parent 387479d commit d20c878

3 files changed

Lines changed: 123 additions & 2 deletions

File tree

.changeset/twenty-tools-lose.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/solid-router': patch
3+
---
4+
5+
Enhanced internal tag comparison logic to optimize stylesheet persistence during route navigation.

packages/solid-router/src/headContentUtils.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
escapeHtml,
44
getAssetCrossOrigin,
55
isInlinableStylesheet,
6-
replaceEqualDeep,
76
resolveManifestAssetLink,
87
} from '@tanstack/router-core'
98
import { useRouter } from './useRouter'
@@ -234,10 +233,36 @@ export const useTags = (assetCrossOrigin?: AssetCrossOriginConfig) => {
234233
if (prev === undefined) {
235234
return next
236235
}
237-
return replaceEqualDeep(prev, next)
236+
return replaceEqualTags(prev, next)
238237
})
239238
}
240239

240+
function replaceEqualTags(
241+
prev: Array<RouterManagedTag>,
242+
next: Array<RouterManagedTag>,
243+
) {
244+
const prevByKey = new Map<string, RouterManagedTag>()
245+
for (const tag of prev) {
246+
prevByKey.set(JSON.stringify(tag), tag)
247+
}
248+
249+
let isEqual = prev.length === next.length
250+
const result = next.map((tag, index) => {
251+
const existing = prevByKey.get(JSON.stringify(tag))
252+
if (existing) {
253+
if (existing !== prev[index]) {
254+
isEqual = false
255+
}
256+
return existing
257+
}
258+
259+
isEqual = false
260+
return tag
261+
})
262+
263+
return isEqual ? prev : result
264+
}
265+
241266
export function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
242267
const seen = new Set<string>()
243268
return arr.filter((item) => {

packages/solid-router/tests/Scripts.test.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,97 @@ describe('ssr scripts', () => {
223223
).toHaveLength(1)
224224
})
225225

226+
test('keeps manifest stylesheet links mounted when preload counts change', async () => {
227+
const history = createTestBrowserHistory()
228+
229+
const rootRoute = createRootRoute({
230+
component: () => {
231+
return (
232+
<>
233+
<HeadContent />
234+
<Outlet />
235+
</>
236+
)
237+
},
238+
})
239+
240+
const aRoute = createRoute({
241+
path: '/a',
242+
getParentRoute: () => rootRoute,
243+
component: () => <Link to="/b">Go to B</Link>,
244+
})
245+
246+
const bRoute = createRoute({
247+
path: '/b',
248+
getParentRoute: () => rootRoute,
249+
component: () => <Link to="/a">Go to A</Link>,
250+
})
251+
252+
const router = createRouter({
253+
history,
254+
routeTree: rootRoute.addChildren([aRoute, bRoute]),
255+
})
256+
257+
router.ssr = {
258+
manifest: {
259+
routes: {
260+
[rootRoute.id]: {
261+
preloads: ['/root.js'],
262+
assets: [
263+
{
264+
tag: 'link',
265+
attrs: {
266+
rel: 'stylesheet',
267+
href: '/main.css',
268+
},
269+
},
270+
],
271+
},
272+
[aRoute.id]: {
273+
preloads: ['/a.js'],
274+
assets: [],
275+
},
276+
[bRoute.id]: {
277+
preloads: ['/b.js', '/b-child.js'],
278+
assets: [],
279+
},
280+
},
281+
},
282+
}
283+
284+
await router.navigate({ to: '/a' })
285+
await router.load()
286+
287+
render(() => <RouterProvider router={router} />)
288+
289+
const getStylesheetLink = () =>
290+
Array.from(document.head.querySelectorAll('link[rel="stylesheet"]')).find(
291+
(link) => link.getAttribute('href') === '/main.css',
292+
)
293+
294+
await waitFor(() => {
295+
expect(getStylesheetLink()).toBeInstanceOf(HTMLLinkElement)
296+
})
297+
298+
const initialLink = getStylesheetLink()
299+
expect(initialLink).toBeInstanceOf(HTMLLinkElement)
300+
301+
fireEvent.click(screen.getByRole('link', { name: 'Go to B' }))
302+
303+
await waitFor(() => {
304+
expect(router.state.location.pathname).toBe('/b')
305+
})
306+
307+
await screen.findByRole('link', { name: 'Go to A' })
308+
309+
expect(getStylesheetLink()).toBe(initialLink)
310+
expect(
311+
Array.from(
312+
document.head.querySelectorAll('link[rel="stylesheet"]'),
313+
).filter((link) => link.getAttribute('href') === '/main.css'),
314+
).toHaveLength(1)
315+
})
316+
226317
test('applies assetCrossOrigin to manifest assets and preloads', async () => {
227318
const history = createTestBrowserHistory()
228319

0 commit comments

Comments
 (0)