kit icon indicating copy to clipboard operation
kit copied to clipboard

Streaming Promises on a +page.server file misbehave in a built environment

Open Sheco opened this issue 2 years ago • 3 comments

Describe the bug

Streamed Promises work correctly in the development environment (npm run dev), but block the page load in a built environment (For example npm run build && npm run preview)

I'm using the latest version of sveltekit

Reproduction

  • Install a new sveltekit app npm create svelte@latest my-app
  • Install dependencies npm install
  • Create a src/routes/+page.svelte file with the example from the SvelteKit docs
<script>
  /** @type {import('./$types').PageData} */  
  export let data;
</script>

<p>
  one: {data.one}
</p>
<p>
  two: {data.two}
</p>
<p>
  three:
  {#await data.streamed.three}
    Loading...
  {:then value}
    {value}
  {:catch error}
    {error.message}
  {/await}
</p>
  • Create a src/routes/+page.server.js with the example in the SvelteKit docs
/** @type {import('./$types').PageServerLoad} */
export function load() {
  return {
    one: Promise.resolve(1),
    two: Promise.resolve(2),
    streamed: {
      three: new Promise((fulfil) => {
        setTimeout(() => {
          fulfil(3)
        }, 1000);
      })
    }
  };
}
  • Run the dev environment npm run dev and load the dev page at http://localhost:5174/ (adjust the port if needed) Enjoy the "loading..." message for a second and then enjoy the final "3"
  • Now build the app and preview it, npm run build && npm run preview and load the page at http://localhost:4173/ (adjust the port if needed) The page blocks for a second, shows "loading..." for an instant and then shows the final "3"

Logs

No response

System Info

System:
    OS: Linux 6.1 Fedora Linux 37 (Workstation Edition)
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
    Memory: 4.74 GB / 15.35 GB
    Container: Yes
    Shell: 5.2.15 - /bin/bash
  Binaries:
    Node: 18.15.0 - /usr/bin/node
    npm: 9.5.0 - /usr/bin/npm
  Browsers:
    Firefox: 111.0
  npmPackages:
    @sveltejs/adapter-auto: ^2.0.0 => 2.0.0 
    @sveltejs/kit: ^1.5.0 => 1.14.0 
    svelte: ^3.54.0 => 3.57.0 
    vite: ^4.2.0 => 4.2.1

Severity

annoyance

Additional Information

No response

Sheco avatar Mar 27 '23 17:03 Sheco

The problem is somewhere within the node stream or the browser itself. We're calling res.write(chunk) but that does not flush out the chunk immediately. Instead it seems to wait for more data and flush everything once res.end() is called. When doing curl localhost:4173 the first data chunk shows up immediately, which makes me think it's the browser. I'm not sure how to get it to flush out the data eagerly, other than appending junk data to the chunk which pushes it over the threshold.

dummdidumm avatar Mar 28 '23 14:03 dummdidumm

I have also encountered this problem and would like to help find a fix. Maybe this helps:

Command Chrome/Firefox curl 7.87.0
vite dev
vite preview
node build locally
node build in prod, behind nginx ✅ but chunks in a weird place inside the SvelteKit <script> tag – BEFORE the </html>

It feels like there might be several factors at play here? I don't understand why npm run preview does not work, but node build DOES, except that behind nginx, it doesn't. Can I try anything else to help get to the bottom of this?

danieldiekmeier avatar May 16 '23 22:05 danieldiekmeier

(I now realize that my nginx problems are not what this issue was originally about, but I still wanted to follow up)

I looked into this a little more and I came to the conclusion that vite preview not working is probably unrelated (and might even be considered "correct" if you're using adapters/platforms that don't support streaming.) vite preview seems to use a different setup from what you're getting out of adapter-node.

I was still unsure why streaming didn't work for me on production, behind nginx. This seems to be a feature of nginx: By default, nginx buffers the response from the proxied server, so the streaming does not reach the client. (Except if the buffer fills up.)

This gets amplified when nginx also gzips the responses, because then we have another buffer. (this also explains why __data.json seemed to work better for me: I didn't gzip the content type text/sveltekit-data.)

If I turn the proxy_buffering setting off, streaming responses immediately work:

# nginx.conf

location / {
	proxy_buffering off;
    # ...
}

But I think this may be a bad idea, so I'm not sure what I'm going to do.

danieldiekmeier avatar Jun 15 '23 10:06 danieldiekmeier

@danieldiekmeier you can disable NGINX proxy buffer by set X-Accel-Buffering header on specific route:

export async function load({ setHeaders }) {
  setHeaders({
    'X-Accel-Buffering': 'no'
  });
  ...
}

NGINX Docs: nginx.org/en/docs/http/ngx_http_proxy_module

rodshtein avatar Jul 26 '23 16:07 rodshtein

(I now realize that my nginx problems are not what this issue was originally about, but I still wanted to follow up)

I looked into this a little more and I came to the conclusion that vite preview not working is probably unrelated (and might even be considered "correct" if you're using adapters/platforms that don't support streaming.) vite preview seems to use a different setup from what you're getting out of adapter-node.

I was still unsure why streaming didn't work for me on production, behind nginx. This seems to be a feature of nginx: By default, nginx buffers the response from the proxied server, so the streaming does not reach the client. (Except if the buffer fills up.)

This gets amplified when nginx also gzips the responses, because then we have another buffer. (this also explains why __data.json seemed to work better for me: I didn't gzip the content type text/sveltekit-data.)

If I turn the proxy_buffering setting off, streaming responses immediately work:

# nginx.conf

location / {
	proxy_buffering off;
    # ...
}

But I think this may be a bad idea, so I'm not sure what I'm going to do.

In the end I was only able to get streaming working with "proxy_buffering off;" Also, it still doesn't work locally when running:

npm run build then npm run preview

ddxv avatar Feb 23 '24 07:02 ddxv

I'm also experiencing this issue. It runs fine on the dev server, but not once built, including when deployed to Vercel. I'm using:

  • @sveltejs/kit: v2.5.8
  • @sveltejs/adapter-vercel: v5.3.0

There are workarounds, but they're not at all ideal.

isaacharrisholt avatar May 14 '24 09:05 isaacharrisholt