diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 2f7cc58e381d0..87dade1f4966d 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -296,4 +296,56 @@ export function hydrate() { if (isError) { reactRoot.render(reactEl) } + + // TODO-APP: Remove this logic when Float has GC built-in in development. + if (process.env.NODE_ENV !== 'production') { + const callback = (mutationList: MutationRecord[]) => { + for (const mutation of mutationList) { + if (mutation.type === 'childList') { + for (const node of mutation.addedNodes) { + if ( + 'tagName' in node && + (node as HTMLLinkElement).tagName === 'LINK' + ) { + const link = node as HTMLLinkElement + if (link.dataset.precedence === 'next.js') { + const href = link.getAttribute('href') + if (href) { + const [resource, version] = href.split('?v=') + if (version) { + const allLinks = document.querySelectorAll( + `link[href^="${resource}"]` + ) as NodeListOf + for (const otherLink of allLinks) { + if (otherLink.dataset.precedence === 'next.js') { + const otherHref = otherLink.getAttribute('href') + if (otherHref) { + const [, otherVersion] = otherHref.split('?v=') + if (!otherVersion || +otherVersion < +version) { + otherLink.remove() + const preloadLink = document.querySelector( + `link[rel="preload"][as="style"][href="${otherHref}"]` + ) + if (preloadLink) { + preloadLink.remove() + } + } + } + } + } + } + } + } + } + } + } + } + } + + // Create an observer instance linked to the callback function + const observer = new MutationObserver(callback) + observer.observe(document.head, { + childList: true, + }) + } } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 0d33831908153..be22fefc4780b 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -379,12 +379,15 @@ export async function renderToHTMLOrFlight( ? cssHrefs.map((href, index) => ( (

Loading...<\/h2>/ + /

Loading...<\/h2>/ ) }) @@ -233,8 +233,11 @@ createNextDescribe( it('should bundle css resources into chunks', async () => { const html = await next.render('/dashboard') expect( - [...html.matchAll(/ href.endsWith('/a/page.css'))` + `[...document.styleSheets].some(({ href }) => href.includes('/a/page.css'))` ) ).toBe(true) // Should not load the chunk from /b expect( await browser.eval( - `[...document.styleSheets].some(({ href }) => href.endsWith('/b/page.css'))` + `[...document.styleSheets].some(({ href }) => href.includes('/b/page.css'))` ) ).toBe(false) }) diff --git a/test/e2e/app-dir/app-css/middleware.js b/test/e2e/app-dir/app-css/middleware.js index 7538957f20e10..a43a08b964626 100644 --- a/test/e2e/app-dir/app-css/middleware.js +++ b/test/e2e/app-dir/app-css/middleware.js @@ -3,11 +3,11 @@ import { NextResponse } from 'next/server' export async function middleware(request) { // This middleware is used to test Suspensey CSS if ( - request.url.endsWith('_next/static/css/app/suspensey-css/slow/page.css') + request.url.includes('_next/static/css/app/suspensey-css/slow/page.css') ) { await new Promise((resolve) => setTimeout(resolve, 150)) } else if ( - request.url.endsWith('_next/static/css/app/suspensey-css/timeout/page.css') + request.url.includes('_next/static/css/app/suspensey-css/timeout/page.css') ) { await new Promise((resolve) => setTimeout(resolve, 1000)) }