The `link` HTTP header in SSR breaks the import maps
Describe the bug
I'm using node adaptor and I found when doing SSR, the server adds a link HTPP header to indicate the browser to preload things.
However, I'm using import maps as well. According to the standard, the import map must be declared and processed before any
Reproduction
https://github.com/hronro/sveltekit-http-link-importmap-issue
The problem only exists in production build.
Logs
No response
System Info
System:
OS: Linux 6.6 Arch Linux
CPU: (14) x64 AMD Ryzen 7 5800X 8-Core Processor
Memory: 24.67 GB / 27.41 GB
Container: Yes
Shell: 3.6.1 - /usr/bin/fish
Binaries:
Node: 21.2.0 - /usr/bin/node
Yarn: 1.22.19 - /usr/bin/yarn
npm: 10.2.4 - /usr/bin/npm
pnpm: 8.10.2 - /usr/bin/pnpm
bun: 1.0.0 - /usr/local/bin/bun
npmPackages:
@sveltejs/adapter-auto: ^2.0.0 => 2.1.1
@sveltejs/adapter-node: ^1.3.1 => 1.3.1
@sveltejs/kit: ^1.27.4 => 1.27.6
svelte: ^4.0.5 => 4.2.4
vite: ^4.4.2 => 4.5.0
Severity
blocking all usage of SvelteKit
Additional Information
No response
Could any core members please respond to this?
I would be happy to submit a PR that adds options for users to specify how the preload links should be sent - either via HTTP header or the HTML <head> element, or even disable preload links altogether.
And I think this may also be helpful for #11084, if users are not able to change their NGINX (or whatever any proxies) configurations.
You can add a handle hook in hooks.server.ts where you do response.headers.delete('Link')
@coyotte508 Thank you for sharing that. However, in my opinion, it is not ideal to delete the entire preload links. The best option would be to let users decide whether to send the preload links through HTTP headers or the HTML <head> element.
This fix worked for me and is necessary on any page which is not pre-rendered. Trying to use import-maps I had the error:
An import map is added after module script load was triggered.
I might just try and append the preload links below the map.
Hello, encountering the same issue here. The header.delete('Link') hack works but as @hronro said it doesn't feel like a great solution
Bumping this. Deleting the Link header leads to these modules not being preloaded.
You can alternatively append these modules to the html chunk to make sure they are loaded (as suggested by @aMediocreDad)
const handleLinkHeaders = (value: string): ResolveOptions => ({
transformPageChunk: ({ html }) => {
const linkTags = value
.split(',')
.map((part) => part.trim())
.map((link) => {
const [url] = link.split(';');
const href = url.trim().slice(1, -1);
return `<link rel="modulepreload" as="script" crossorigin href="${href}">`;
})
.join('\n');
return html.replace(
'</head>',
`<!-- Link header start -->${linkTags}<!-- Link header end -->\n</head>`
);
},
});
export const handle: Handle = async ({ event, resolve }) => {
let response = await resolve(event);
const linkHeader = response.headers.get('Link');
if (linkHeader) {
response = await resolve(event, handleLinkHeaders(linkHeader));
response.headers.delete('Link');
}
return response;
};