kit icon indicating copy to clipboard operation
kit copied to clipboard

The `link` HTTP header in SSR breaks the import maps

Open hronro opened this issue 2 years ago • 5 comments

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

hronro avatar Nov 17 '23 08:11 hronro

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.

hronro avatar Nov 20 '23 08:11 hronro

You can add a handle hook in hooks.server.ts where you do response.headers.delete('Link')

coyotte508 avatar Nov 20 '23 09:11 coyotte508

@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.

hronro avatar Nov 21 '23 05:11 hronro

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.

NicholasNewlandsDR avatar Mar 17 '24 06:03 NicholasNewlandsDR

Hello, encountering the same issue here. The header.delete('Link') hack works but as @hronro said it doesn't feel like a great solution

marwie avatar Jun 20 '24 15:06 marwie

Bumping this. Deleting the Link header leads to these modules not being preloaded.

aMediocreDad avatar Nov 15 '24 09:11 aMediocreDad

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;
};

veggiss avatar Nov 15 '24 12:11 veggiss