diff --git a/.changeset/quiet-script-preloads.md b/.changeset/quiet-script-preloads.md new file mode 100644 index 0000000000..bf5474ed1d --- /dev/null +++ b/.changeset/quiet-script-preloads.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-core': patch +--- + +Render manifest script preloads with low fetch priority so they do not compete with render-blocking resources. diff --git a/packages/react-router/tests/Scripts.test.tsx b/packages/react-router/tests/Scripts.test.tsx index 893cef01bc..976410cba5 100644 --- a/packages/react-router/tests/Scripts.test.tsx +++ b/packages/react-router/tests/Scripts.test.tsx @@ -547,6 +547,35 @@ describe('ssr HeadContent', () => { ) }) + test('renders manifest module preloads with low fetch priority', async () => { + const preloadHref = '/low-priority-module.js' + const rootRoute = createRootRoute({ + component: () => , + }) + const indexRoute = createRoute({ + path: '/', + getParentRoute: () => rootRoute, + }) + const router = createRouter({ + history: createMemoryHistory({ initialEntries: ['/'] }), + routeTree: rootRoute.addChildren([indexRoute]), + isServer: true, + }) + + router.ssr = { + manifest: createTestManifest(rootRoute.id, { preloadHref }), + } + + await router.load() + + const html = ReactDOMServer.renderToString( + , + ) + expect(html).toContain( + ``, + ) + }) + test('keeps manifest stylesheet links mounted when history state changes', async () => { const history = createTestBrowserHistory() const stylesheetHref = '/history-state.css' @@ -745,7 +774,7 @@ describe('ssr HeadContent', () => { ).toBe('.runtime{color:red}') }) - test('renders preload as script links for iife manifest preloads', async () => { + test('renders iife manifest preloads as low priority script links', async () => { const history = createTestBrowserHistory() const preloadHref = '/iife-preload.js' @@ -795,6 +824,13 @@ describe('ssr HeadContent', () => { `link[rel="modulepreload"][href="${preloadHref}"]`, ), ).toBeFalsy() + expect( + document.head + .querySelector( + `link[rel="preload"][as="script"][href="${preloadHref}"]`, + ) + ?.getAttribute('fetchpriority'), + ).toBe('low') }) test('assetCrossOrigin overrides manifest crossOrigin values', async () => { diff --git a/packages/router-core/src/manifest.ts b/packages/router-core/src/manifest.ts index aca4b96eac..b43f485236 100644 --- a/packages/router-core/src/manifest.ts +++ b/packages/router-core/src/manifest.ts @@ -43,6 +43,7 @@ export function getScriptPreloadAttrs( rel: 'modulepreload' | 'preload' as?: 'script' href: string + fetchPriority: 'low' crossOrigin?: AssetCrossOrigin } { const preloadLink = resolveManifestAssetLink(link) @@ -54,6 +55,7 @@ export function getScriptPreloadAttrs( ? { rel: 'preload', as: 'script' } : { rel: 'modulepreload' }), href: preloadLink.href, + fetchPriority: 'low', ...(crossOrigin ? { crossOrigin } : {}), } } diff --git a/packages/vue-router/tests/Scripts.test.tsx b/packages/vue-router/tests/Scripts.test.tsx index 8d6724076b..ad6da944f1 100644 --- a/packages/vue-router/tests/Scripts.test.tsx +++ b/packages/vue-router/tests/Scripts.test.tsx @@ -311,6 +311,7 @@ describe('ssr HeadContent', () => { const ssrPreload = document.createElement('link') ssrPreload.setAttribute('rel', 'modulepreload') ssrPreload.setAttribute('href', '/main.js') + ssrPreload.setAttribute('fetchpriority', 'low') document.head.append(ssrStylesheet, ssrPreload) @@ -397,6 +398,7 @@ describe('ssr HeadContent', () => { const ssrPreload = document.createElement('link') ssrPreload.setAttribute('rel', 'modulepreload') ssrPreload.setAttribute('href', '/index.js') + ssrPreload.setAttribute('fetchpriority', 'low') document.head.append(ssrStylesheet, ssrPreload)