Hello,
I work on multiple Cloudflare Pages projects based on Sveltekit and @sveltejs/adapter-cloudflare
and have a problem with server side scripts (implicit workers) not being executed. Instead, earlier cached responses are being served.
This behavior is potentially dangerous, the caching means that Cloudflare replays responses provided to other users.
The goal is to enable a simple cookie-based authentication scheme where
- A cookie is set on the server after a successful form POST (Sveltekit action) - this works, the cookie is set and forwarded.
- The cookie is parsed either by a middleware (in hooks.server.ts) or the +layout.server.ts file, and if it is valid,
-
hooks.server.ts
: set a event.locals.someFlagHere
- **
+layout.server.ts**
: return a LayoutData
response (e.g. {user: “ok”})
- The flag is parsed by the page, and if the user is authenticated, we serve the protected content.
Only step 1 works when the site is deployed to Cloudflare Pages. Step 2 and 3 are partially omitted. With a layout-based flow, the user is signed in once, and then an old cache is served, even though the cookie is present. With a middleware-based flow, the original cached response is always served.
I understand that any server side-functions are bundled to .svelte-kit/Cloudflare/_workers.js. With the hooks-method, I can find the function corresponding to the middleware I am introducing. With the Layout-method, I can see that the cookie is set and a new authenticated version of the page is loaded, before an older cached version is returned.
To reproduce this, please have a look at
https://github.com/mariusei/testCloudflarePages
or a live version of the page here:
https://testcloudflarepages-7vd.pages.dev/
I’m in the exact same boat as you on the latest SvelteKit and have tried both hooks.server.ts and +layout.server.ts. No matter what I do cached user data is still available under protected routes by simply navigating back to it even with the cookie deleted. This is different behavior than what happens when I run it with pnpm run dev
Things I’ve tried:
- setHeaders({‘cache-control’:‘no-store’}) in both hooks.server.ts and layout.server.ts
- under static/_headers file attempted to set Cache-Control:no-store
- tried deleting the cookie, invalidateAll(), redirect to login
- On Cloudflare under Caching → Configuration → Browser Cache TTL → set that to Respect Existing Headers.
Nothing works.
I was running into the same issue, also tried adding “cache-control: no-store, max-age=0” and “vary: cookie” headers to pretty much every single response to a GET request (using hooks.server.ts), and that seems to have fixed it.
You are my hero. You have no idea how much time you just saved me.
That fixed it. I just set the header before my return. works for both hooks.server.ts and +layout.server.ts
setHeaders({ ‘cache-control’:‘no-store’, ‘vary’:‘cookie’ })
Now my quest is to figure out what vary does. Thanks again!
I think I understand now. Using ‘vary’:‘cookie’ ensures that the cookie header is attached to the cached elements. Meaning that if the cookie doesn’t exist then the cache is invalid. That’s pretty crafty; I’ll remember this one.
Thanks for the workaround! But doesn’t turning off the caching degrade the overall performance? Does ‘vary_cookie’ suffice to fix the issue without disabling caching?
I wonder if this is a feature of the Cloudflare caching mechanisms, rather than a Pages-specific issue.
If any Sveltekit page on any random server is served with Cloudflare caching enabled, then there’s a risk of user data leaks if Cloudflare caches the server-side rendered content while disregarding authentication data in the cookies? Or any site with some SSR-dymamic mix, like Gatsby?
It could very well be that this is a common problem for any caching methods.
My original hunch was and still is that there’s a bug when parsing the generated _workers.js file. My understanding is that any function in there should be executed when called. But again, the calls could never even be made if Cloudflare indiscriminately intercepts the fetch-responses it already has stored.