kit icon indicating copy to clipboard operation
kit copied to clipboard

Why is `__vdpl` cookie used for Skew Protection in Vercel instead of `x-deployment-id` header?

Open MathiasWP opened this issue 1 year ago • 13 comments

Describe the problem

We're trying to understand why our app multiple times a day report the Failed to fetch dynamically imported module issue to Sentry.

It seems that this is caused by the user opening a new version of the app in a new tab, which is documented on SvelteKit docs:

Cookie-based skew protection comes with one caveat: if a user has multiple versions of your app open in multiple tabs, requests from older versions will be routed to the newer one, meaning they will fall back to SvelteKit's built-in skew protection.

I may be understanding this wrong, but wouldn't the x-deployment-id header make it so different application versions in different tabs would not be affected by the new cookie that is set? Instead of setting the cookie across all app versions, wouldn't each application be able to use the separate deployment ID's in the header from the server its connected to?

Describe the proposed solution

Using x-deployment-id header instead of __vdpl cookie.

Alternatives considered

SvelteKits internal Skew Protection will reload the page which "solves" the error, but why not let the application keep working with Vercel's Skew protection if it's enabled?

Importance

would make my life easier

Additional Information

No response

MathiasWP avatar Aug 19 '24 10:08 MathiasWP

cc: @Rich-Harris @dummdidumm

teemingc avatar Aug 19 '24 10:08 teemingc

Presumably because it was easier to set the cookie once and have SK's and the browser's automatic cookie handling take over. I don't know what setting this header on every request looks like. We'd need new hooks in SK to make that happen it feels like.

Conduitry avatar Aug 19 '24 12:08 Conduitry

Presumably because it was easier to set the cookie once and have SK's and the browser's automatic cookie handling take over. I don't know what setting this header on every request looks like. We'd need new hooks in SK to make that happen it feels like.

Can this be done in the adapter? I don't know how the adapter and routes fully work, but would it be possible to add a route that has src: '/.*' and setting 'x-deployment-id': process.env.VERCEL_DEPLOYMENT_ID in the header?

MathiasWP avatar Aug 19 '24 12:08 MathiasWP

Presumably because it was easier to set the cookie once and have SK's and the browser's automatic cookie handling take over. I don't know what setting this header on every request looks like. We'd need new hooks in SK to make that happen it feels like.

Can this be done in the adapter? I don't know how the adapter and routes fully work, but would it be possible to add a route that has src: '/.*' and setting 'x-deployment-id': process.env.VERCEL_DEPLOYMENT_ID in the header?

Sounds like what the generated vercel static config is suppose to do. ~I’m not sure if~ this only applies to static asset requests ~or also requests that hit serverless functions~ https://github.com/sveltejs/kit/blob/761b8afd33f45e6b9cf0be1fb3933213d4542b49/packages/adapter-vercel/index.js#L499

teemingc avatar Aug 19 '24 14:08 teemingc

But we'd also need to inject that header into all requests made from the browser as well, right? That's more the part I'm concerned about. I don't think we have a mechanism to do that, and it's requests from the browser that are the primary concern in preventing version skew.

Conduitry avatar Aug 19 '24 14:08 Conduitry

But we'd also need to inject that header into all requests made from the browser as well, right? That's more the part I'm concerned about. I don't think we have a mechanism to do that, and it's requests from the browser that are the primary concern in preventing version skew.

If that's the case, could a Service Worker achieve this behaviour? Could the service-worker inject the x-deployment-id header via the fetch event in the ServiceWorkerGlobalScope ?

MathiasWP avatar Aug 22 '24 08:08 MathiasWP

Would it be possible to avoid injecting the header from the browser by instead adding the x-deployment-id in a middleware on the server? It doesn't matter where the header was set as long as it is present on the request before being handled, right? @Conduitry @eltigerchino

MathiasWP avatar Sep 01 '24 14:09 MathiasWP

No, we can't inject the header later. We need to know what version the browser has. That's the whole point of skew protection.

Conduitry avatar Sep 01 '24 14:09 Conduitry

But we'd also need to inject that header into all requests made from the browser as well, right? That's more the part I'm concerned about. I don't think we have a mechanism to do that, and it's requests from the browser that are the primary concern in preventing version skew.

If that's the case, could a Service Worker achieve this behaviour? Could the service-worker inject the x-deployment-id header via the fetch event in the ServiceWorkerGlobalScope ?

I think it's worth experimenting with this. But then we'd also need a way for the adapter to add the service worker registration script.

teemingc avatar Sep 01 '24 16:09 teemingc

No, we can't inject the header later. We need to know what version the browser has. That's the whole point of skew protection.

True... i kinda forgot how servers work

MathiasWP avatar Sep 01 '24 22:09 MathiasWP

But we'd also need to inject that header into all requests made from the browser as well, right? That's more the part I'm concerned about. I don't think we have a mechanism to do that, and it's requests from the browser that are the primary concern in preventing version skew.

If that's the case, could a Service Worker achieve this behaviour? Could the service-worker inject the x-deployment-id header via the fetch event in the ServiceWorkerGlobalScope ?

I think it's worth experimenting with this. But then we'd also need a way for the adapter to add the service worker registration script.

I tried to experiment with no luck, but i have a almost no experience with Service Workers.

There should be ways to make the script appear in the final application if the adapter is present in the svelte.config.js file, right? I have no strong opinions on how, but it sounds like a solvable task.

MathiasWP avatar Sep 01 '24 22:09 MathiasWP

We have been encountering this issue for quite a long time as well. In some cases the app breaks entirely and no reload is triggered. Is there no solution to this still?

yekta avatar Oct 16 '24 13:10 yekta

@MathiasWP someone has made a reproduction of skew protection being broken due to the cookie changing on opening a new tab with a new deployment, leaving the old tab with the new cookie too, causing it to use the wrong skew protection id https://github.com/sveltejs/kit/issues/9089#issuecomment-2414230989

teemingc avatar Oct 17 '24 02:10 teemingc

I want to ping this issue, because it can cause some really bad user experiences if they're unlucky. We've seen that if a user opens our app at the wrong moment after a deployment then they're just greeted with a loading screen that never loads because of the Failed to fetch dynamically imported module error being thrown, causing the app to stop running.

Is there an easy way to workaround this?

MathiasWP avatar Jan 17 '25 09:01 MathiasWP

Same issue #14437 Any solution yet?

ak4zh avatar Sep 16 '25 10:09 ak4zh

After trying to implement a solution using a service worker, I can understand why the cookie based solution is the preferred solution. There's a lot of overhead involved with a service worker tracking the different deployment IDs for each client (new tab or window).

A rough implementation (which is doable in user-land today if you follow the ideas from the two patches in the provided repo) involves:

  1. Dispatching a message to the service worker when the page has completed loading.
  2. Updating the cached map of the current browsing context with the Vercel deployment ID.
  3. Intercepting subsequent fetches with the service worker and adding the correct deployment ID in the request header.

https://github.com/teemingc/vercel-skew-protection/blob/main/patches/%40sveltejs__adapter-vercel.patch

This takes up additional browser storage, requires a service worker, uses up more energy and memory to run the code, and doesn't currently work in Safari (I've no idea why). In comparison, the current solution is a small cookie that gets sent with every request but doesn't work with multiple tabs. We also haven't answered the question of how this would interact with existing user service workers.

It feels like the service worker solution is probably not the best solution. Unless there's some other way we can ensure all network requests include the deployment ID in its header, I'm in favour of keeping the existing cookie based solution but finding a better way to handle these failed fetches.

teemingc avatar Oct 25 '25 00:10 teemingc