diff --git a/.changeset/brown-breads-begin.md b/.changeset/brown-breads-begin.md new file mode 100644 index 0000000000..0c54b09096 --- /dev/null +++ b/.changeset/brown-breads-begin.md @@ -0,0 +1,10 @@ +--- +'@tanstack/solid-router-ssr-query': patch +'@tanstack/solid-router-devtools': patch +'@tanstack/solid-start-client': patch +'@tanstack/solid-start-server': patch +'@tanstack/solid-router': patch +'@tanstack/solid-start': patch +--- + +Fix two related issues with HeadContent in solid-router diff --git a/.github/workflows/client-nav-benchmarks.yml b/.github/workflows/client-nav-benchmarks.yml index 817aea1809..d725368e17 100644 --- a/.github/workflows/client-nav-benchmarks.yml +++ b/.github/workflows/client-nav-benchmarks.yml @@ -48,7 +48,7 @@ jobs: - name: Run ${{ matrix.benchmark }}:${{ matrix.framework }} CodSpeed benchmark continue-on-error: true - uses: CodSpeedHQ/action@3194d9a39c4d46684cb44bf7207fc56626aad8fd # v4 + uses: CodSpeedHQ/action@3194d9a39c4d46684cb44bf7207fc56626aad8fd # v4.15.1 with: mode: simulation run: WITH_INSTRUMENTATION=1 pnpm nx run @benchmarks/${{ matrix.benchmark }}:test:perf:${{ matrix.framework }} diff --git a/packages/solid-router/src/Asset.tsx b/packages/solid-router/src/Asset.tsx index 6292e48484..34d531e367 100644 --- a/packages/solid-router/src/Asset.tsx +++ b/packages/solid-router/src/Asset.tsx @@ -1,5 +1,5 @@ import { isServer } from '@tanstack/router-core/isServer' -import { createEffect } from 'solid-js' +import { createEffect, onCleanup, onSettled } from 'solid-js' import { useRouter } from './useRouter' import type { RouterManagedTag } from '@tanstack/router-core' import type { JSX } from '@solidjs/web' @@ -25,89 +25,103 @@ export function Asset({ } } +// On the client, relocate a rendered head element into document.head so head +// tags end up in the right place even when is rendered in +// . The *same* node that Solid rendered/hydrated is moved — never +// recreated — so the server-rendered node stays claimed (its hydration id is +// preserved) and stylesheets/scripts are not refetched or re-executed. +// +// When is placed in (the SSR/hydration case), the node +// is already in document.head, so this is a no-op and the element is left +// exactly where Solid hydrated it. +function useRelocateToHead(getEl: () => Node | undefined) { + onSettled(() => { + const el = getEl() + if (el && el.parentNode !== document.head) { + document.head.appendChild(el) + } + }) + + onCleanup(() => { + const el = getEl() + if (el?.parentNode) { + el.parentNode.removeChild(el) + } + }) +} + function HeadElement(props: { tag: 'meta' | 'link' | 'style' attrs?: Record children?: unknown }): JSX.Element | null { - const router = useRouter() - - // Server: render the element in the tree so it's part of the SSR'd HTML. - // (Where is placed determines where it appears in the SSR - // output; the head-content design supports rendering in for full- - // document hydration.) - if (isServer ?? router.isServer) { - const { tag, attrs, children } = props - if (tag === 'style' && typeof children === 'string') { - return