I am attempting to manage cache on my sveltekit website. The Env:
I manage sessions with a session id cookie that fetches user session data from redis on every external request. On internal requests (ie svelte's special fetch()
), session information is forwarded to locals
inside the hooks.server.js
's handleFetch()
.
Note that no data the client requests comes from anywhere else but the site itself, it's a monolith.
All pages on the site have user specific information in them, for example on the nav bar there's a user card if they are logged in, and most pages have admin moderation tools. Pure data is fetched from api endpoints, which I set cache-control headers for.
So how do I use user specific data in my home page and still be able to let Cloudflare cache most of it?
And if that's not possible, then how do I tell upstream (CF and the client) that the page can be cached when no session ID cookie is set?
And lastly, a third option might be to create various api endpoints for user specific data and request it after the page loads in onMount()
, which I guess would work but adds an extra request. Maybe someone can elaborate on how this would work better.
For anyone wondering how to do this given a setup similar to mine, which is:
Considering these constraints, high traffic pages need to be user-agnostic to be cached by cloudflare, which forces us to fetch()
user data from an endpoint after the page has loaded.
So, first create a session store for front-end only, this will contain all information the client might need for rendering user cards etc. Do not include sensitive session data like password and session id itself because it defeats the purpose of having httpOnly cookies!
session.js
import { writable } from 'svelte/store';
import { invalidate } from '$app/navigation';
import { browser } from '$app/environment';
export const refetch = writable(true);
export const session = writable({}); // This default user object can be different
if (browser) {
refetch.subscribe(b => {
if (!b) return;
session.set({});
invalidate('data:session'); // Can be invalidateAll() also
});
}
export default session;
Now in your root +layout.svelte
import { session, refetch } from '$lib/stores/session';
import { afterNavigate } from '$app/navigation';
afterNavigate(async () => { // Add your error handling...
if (!$refetch) return;
$refetch = false;
const req = await fetch('/api/session'); // Put your own session endpoint and headers
const res = await req.json();
console.log('Session', res);
$session = res;
});
I won't provide any specifics for the session/+server.js
endpoint, you can implement it however you like.
Whenever $refetch
is set to true
it will clear the current session store and fetch it from your api after the page finishes navigating. When the page first loads, $refetch
is true and it will get the user without a trigger. Any load functions you want to rerun, like pages that require login, add depends('data:session')
inside them or, instead of using invalidate('data:session')
a simpler more heavy handed approach would be to invalidateAll()
.
In your login and register pages, be sure to set $refetch = true
when submitting the form in your use:enhance
function body.
This also works with oauth2 redirects since the client will load the page once (only) after the backend has returned a final location/route.
For added control, you may add a secondary cookie such as cf-nocache
for admin users and set a cache rule in your CF panel that bypasses cache when the cookie exists. This will prevent admin versions of pages from being public cached without having to set complicated headers in your routes.
Final thoughts - Keep in mind that the session store will always be empty on the server side when SSR runs. This will return a user-less page and the client is the one hydrating this user store. If you need to use session data on the server, add your user session to the locals
object.