From df045939207742d625980f02bb6553d43cde3682 Mon Sep 17 00:00:00 2001 From: svelte-triage-bot Date: Mon, 12 Oct 2026 11:52:57 +0000 Subject: [PATCH] fix: resolve server-routed CSS against its route module --- .changeset/calm-route-styles.md | 5 +++ .../src/runtime/server/page/server_routing.js | 3 +- .../playwright/cross-platform/client.test.js | 39 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .changeset/calm-route-styles.md diff --git a/.changeset/calm-route-styles.md b/.changeset/calm-route-styles.md new file mode 100644 index 000000000000..b6d07d1f5590 --- /dev/null +++ b/.changeset/calm-route-styles.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: resolve server-routed CSS against its route module diff --git a/packages/kit/src/runtime/server/page/server_routing.js b/packages/kit/src/runtime/server/page/server_routing.js index 2821662d75b4..931dcbc768e2 100644 --- a/packages/kit/src/runtime/server/page/server_routing.js +++ b/packages/kit/src/runtime/server/page/server_routing.js @@ -192,5 +192,6 @@ function create_css_import(route, url, client) { if (!css) return ''; - return `${create_client_import(client.start, paths)}.then(x => x.load_css([${css}]));\n`; + // CSS paths are relative to this module, not the document we are navigating from. + return `${create_client_import(client.start, paths)}.then(x => x.load_css([${css}].map(p => new URL(p, import.meta.url).href)));\n`; } diff --git a/packages/kit/test/apps/basics/test/playwright/cross-platform/client.test.js b/packages/kit/test/apps/basics/test/playwright/cross-platform/client.test.js index acd04fc2439f..672873b9f52b 100644 --- a/packages/kit/test/apps/basics/test/playwright/cross-platform/client.test.js +++ b/packages/kit/test/apps/basics/test/playwright/cross-platform/client.test.js @@ -718,6 +718,45 @@ test.describe('Scrolling', () => { }); test.describe('CSS', () => { + for (const preload of [false, true]) { + test(`loads styles from a deeper page${preload ? ' after preloading code' : ''}`, async ({ + page, + app + }) => { + await page.goto('/routing/rest/xyz/abc/qwe/deep'); + + if (preload) await app.preloadCode('/css/encöded'); + await app.goto('/css/encöded'); + + await expect(page.locator('h1')).toHaveCSS('color', 'rgb(128, 0, 128)'); + }); + } + + test('reuses server-rendered styles after preloading from a deeper page', async ({ + page, + app + }) => { + test.skip(!!process.env.DEV); + + await page.goto('/css/encöded'); + const assets = await page + .locator('link[rel="stylesheet"]') + .evaluateAll((links) => + links.map((link) => /** @type {HTMLLinkElement} */ (link).href.split('/').at(-1)) + ); + expect(assets.length).toBeGreaterThan(0); + + await app.goto('/routing/rest/xyz/abc/qwe/deep'); + await app.preloadData('/css/encöded'); + await app.goto('/css/encöded'); + + await expect(page.locator('h1')).toHaveCSS('color', 'rgb(128, 0, 128)'); + for (const asset of assets) { + // Compare filenames to also catch duplicate links with incorrectly resolved URLs. + await expect(page.locator(`link[rel="stylesheet"][href$="/${asset}"]`)).toHaveCount(1); + } + }); + test('applies generated component styles (hides announcer)', async ({ page, clicknav,