Replies: 31 comments 76 replies
-
|
@babichjacob it's inconsiderate to close and lock an issue without any explanation we're trying to contribute+become part of the svelte community -- so getting auto-silenced feels like an invalidating and unwelcoming start |
Beta Was this translation helpful? Give feedback.
-
|
You could wrap store usage using Svelte's context API. That way they are scoped to a component and its children, therefore scoped per user: // store.js
import { setContext, getContext } from 'svelte';
export function createMyCustomStore() {
setContext('someUniqueName', writable('some content'));
}
export function getMyCustomStore() {
getContext('someUniqueName');
}
// src/routes/__layout.svelte
<script>
...
createMyCustomStore();
</script>
// src/routes/somewhereelse.svelte
<script>
const store = getMyCustomStore();
...
</script>Note that this means that you need to call these get/set methods on component initialization. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
Thanks for taking the time and chiming in @dummdidumm
We believe the docs are neither clear nor explicit, for these reasons:
At this point, we still feel that svelte stores should house our global state (just like a redux store), and to achieve the desired result on SSR we're using // src/routes/__layout.svelte
<script context="module">
export async function load({ fetch }) {
const res = await fetch("api.blink.xyz/conversations")
const conversations = await res.json()
return {
stuff: { conversations }
}
}
</script>
<script>
import conversationStore from "$lib/stores/conversations"
import { page } from "$app/stores"
conversationStore.update($page.stuff.data)
</script>
// src/routes/somewhereelse.svelte
<script>
import conversationStore from "$lib/stores/conversations"
</script>
<ul>
{#each $conversationStore[1].messages as message}
<li>{message.sender}: {message.content}</li>
{/each}
</ul> |
Beta Was this translation helpful? Give feedback.
-
|
I was just about to create a post for this same issue. Since Svelte stores are generally written as files where you have to initialize it like |
Beta Was this translation helpful? Give feedback.
-
|
Since there hasn't been any new activity on this thread, I have been experimenting on this a little bit, and I have found that using So, my solution is basically not have global stores. My Store file looks like: fooStore.js Notice that we return a function which instantiates the store. This is important to ensure we are not using global stores around on server. Now in __layout.svelte Now you can use your fooStore instance without any worry from the One caveat: Sveltekit removes |
Beta Was this translation helpful? Give feedback.
-
|
I kinda agree with this, there should be a JS sandbox being created per request. I understand the overhead, but i think it's critical for security, many devs are probably unaware of this caveat, despite a small note about it in the docs |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
@dummdidumm @Rich-Harris I have experience playing around with Vue's sandboxing system that they use to isolate module state from eachother using node's vm features. I would be happy to collaborate with someone, with the goal of creating a sveltekit adapter to achieve this. Using stores like normal javascript I find is generally the most convenient and not needing to worry about potential security vunerabilities between shared user state is a big plus. |
Beta Was this translation helpful? Give feedback.
-
|
thanks for pushing this forward @AlbertMarashi |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
Eventually we have also come accross this problem. This seems like one of hte biggest issues of Sveltekit. All solutions proposed seems not cool considering the ease-of-use of Sveltekit. What is the disadvantage of restricting the stores on the request context? Additionally, currently tried the below approach (upon populating store on SSR). We get an empty store on SSR. But this also does not help because since it gives an empty store on each server import, it is meaningless. export const hello = writable<string | null>(null)
export function getHelloStore() {
if (!browser) {
return writable<string | null>(null)
} else {
return hello
}
}In the end I am really really confused, as far as I understand either you set a store on @Rich-Harris, what is really interesting is that there have been 2 issues about this problem, 1 is closed and the other (this one) is converted into a discussion. For me and my team this is the number 1 issue currently in Sveltekit. Can somebody from Sveltekit please explain what is the ultimate correct way to use the stores considering the SSR and security? |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
We ran into the same issue with our app. Data was leaking between requests, and we also used server-side authentication, so this posed a significant security risk. I ended up writing a custom store that could be used in the same way as Svelte's default // sandboxedStore.js
//
import { browser } from '$app/environment'
import { getStores } from '$app/stores'
import { v4 as uuidv4 } from 'uuid'
import { get, writable as svelteWritable } from 'svelte/store'
const storesKey = `sandbox_${uuidv4()}`
/**
* Creates a façade for a writable store that is a sandboxed store that may be used as a
* global variable. It must be initialized during component initialization.
*
* This store is contextual since it is added to the context of the root component. This means that
* it is unique to each request on the server rather than shared across multiple requests handled
* by the same server at the same time, allowing it to be used as a global variable while also
* holding user-specific data.
*
* We must subscribe to this store during component initialization before we can use it. It is
* utilized in the same way as [SvletKit's app stores](https://kit.svelte.dev/docs/modules#$app-stores).
*
* _NB: In async methods, set the store before any await, otherwise, the context will be lost once
* the promise is fulfilled._
*
* @param {any} initialValue An initial value to set the store to
* @param {string} [key] An optional key name for the store
*/
export function writable(initialValue, key) {
key = key ? `${key}_${uuidv4()}` : uuidv4()
function setStore(value) {
try {
const { page: sessionStore } = getStores()
const session = get(sessionStore)
const store = session?.[storesKey]?.[key]
const currentValue = store ? store.value : initialValue
if (!store || value !== currentValue) {
if (!session[storesKey]) session[storesKey] = {}
session[storesKey][key] = {
value,
subscribers: store?.subscribers || [],
}
// alert subscribers
if (store) {
store.subscribers.forEach(fn => {
fn(value)
})
// return the updated value
return value
}
}
} catch (error) {
// if we reached this exception, it meant that the store had not yet been initialized
return value
}
}
const sandboxedWritable = {
set: setStore,
subscribe: fn => {
try {
const { page: sessionStore } = getStores()
const session = get(sessionStore)
const store = session?.[storesKey]?.[key]
const currentValue = store ? store.value : initialValue
// call the subscription function with the current value
fn(currentValue)
// register the subscriber
if (!session[storesKey]) session[storesKey] = {}
session[storesKey][key] = {
value: store?.value || initialValue,
subscribers: [...(store?.subscribers || []), fn],
}
} catch (error) {
// if we reached this exception, it meant that the store had not yet been initialized
// call the subscription function with the initial value
fn(initialValue)
}
// return the unsubscribe function
return function unsubscribe() {
try {
const { page: sessionStore } = getStores()
const session = get(sessionStore)
// unregister the subscriber
const { subscribers } = session[storesKey][key]
subscribers.splice(subscribers.indexOf(fn), 1)
} catch (error) {
// if we reached this exception, it meant that the store had not yet been initialized
// ignore
}
}
},
update: fn => {
try {
const { page: sessionStore } = getStores()
const session = get(sessionStore)
const store = session?.[storesKey]?.[key]
const currentValue = store ? store?.value : initialValue
setStore(fn(currentValue))
} catch (error) {
// if we reached this exception, it meant that the store had not yet been initialized
setStore(fn(initialValue))
}
},
}
return browser ? svelteWritable(initialValue) : sandboxedWritable
}EDIT: I updated the code, to remove const { page: sessionStore } = getStores()
- const { stuff: session } = get(sessionStore)
+ const session = get(sessionStore)You can use this writable store in the same way as the default one, with the exception that it will not leak data on the server between requests. You can also specify a value to initialize the store with, which will be set only after the context is available: import { writable } from 'sandboxedStore'
const foo = writable()
const bar = writable('initial value') |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
I absolutely agree that this behavior is non-obvious and as such should be mentioned and explained much more clearly in the docs. Declaring a store in a separate file and then importing it in components and so on is how you do global stores in vanilla Svelte, and so many people are probably assuming they can do the same thing in SvelteKit, while not actually realizing that it could be problematic. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
I would really prefer in build solution for the store in SSR with sensitive user data. Maybe leaving session store behind (removal of the session store) was too hasty. For the use cases discussed here the old session was really great and easy to use tool/. Even tools like Supabase SvelteKit Auth Helpers were build upon and embraced this feature. |
Beta Was this translation helpful? Give feedback.
-
|
I have been hitting this issue as I mentioned here #8614 (comment). Is @raduab's solution above still what most people here are using? Has any progress been made else where that I'm missing? Thanks! |
Beta Was this translation helpful? Give feedback.
-
|
Hi! Thanks for bringing up this info. Imo, this is quite unexpected behavior, and if a developer of an API needs people to read very carefully, then their users are not going to read (hi, user here). (All in my newbie opinion:) In Svelte (i.e. just client-side), stores don't have cross-session behavior (i.e. User1 will never see User2's info). Otherwise, I would personally just turn off SSR if I'm using stores, unless I were using a workaround like those in this discussion. |
Beta Was this translation helpful? Give feedback.
-
Unfortunately, turning off SSR with SvelteKit, and contrarily to what the documentation says, you're loosing all SEO, not some of it. From the experiments I've done, Google was unable/unwilling to fetch more than a few of the dozens of javascript files necessary to render a page, and I wasn't able to find how to have SK generate a unique js bundle either… |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
Here's how I have been dealing with this... In In Then, I have That way, throughout my app I can just start typing From any component, I can simply add: And I quickly have access to the store I want. For me, this feels like a nice pattern that matches the ease-of-use from how I used stores before SvelteKit (easy auto-import, minimal boiler plate), but makes more sense with SK's natural data flow. Hope that may help someone else, let me know if I'm missing some flaw in my own setup! |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
I made the simple utility to keep server states isolated per request. // src/lib/store.ts
import { get, writable as writable_ } from 'svelte/store';
import { browser } from '$app/environment';
import { page } from '$app/stores';
import type { Writable } from 'svelte/store';
const stores = new WeakMap();
export const writable = <T>(value?: T): Writable<T> => {
if (browser) {
return writable_(value);
}
const key = get(page);
let store = stores.get(key);
if (!store) {
store = writable_(value);
stores.set(key, store);
}
return store;
};You can now use them safely as a global variables. // src/lib/stores.ts
import { writable } from '$lib/store';
export const counter = writable(0);<!-- src/routes/+page.svelte -->
<script lang="ts">
import { counter } from '$lib/stores';
store.update((v) => v + 1);
console.log($store); // This will always print `1` on both the server and the browser.
</script>Since this is just a small snippet, there's no magic here. You can modify it for your needs. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
Is there a reason this thread is over a year old and the team still has not addressed it? My team and I are evaluating Sveltekit as an alternative to Nextjs but leaking data between sessions on SSR is immensely dangerous and given all the other conveniences of Sveltekit, surprising that such a pitfall exists. We've had success using @raduab's example above, but it still feels hacky that its not an official part of the framework, especially something so fundamental to many use cases. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
Just came across this and was as surprised as anyone else. Reading it back I realise the section was specific to those 4 built-in stores, but it sounded like a general principle. It should probably say "These stores on the server..." and also carry a warning that stores you create won't behave like this unless you do the adding to the context yourself. |
Beta Was this translation helpful? Give feedback.
-
|
Regardless of whether per-request stores will get implemented or not, there should at least be a warning for the time being. If you import the
The same warning should apply to any other writable store that's not in a context too. This is a huge pitfall. |
Beta Was this translation helpful? Give feedback.
-
|
I just published a drop-in replacement package for svelte stores that is safe to use with sveltekit ssr. |
Beta Was this translation helpful? Give feedback.
-
|
Looks like I unintentionally avoided this SvelteKit security vulnerability by running my webapp on Vercel 😁🤝 |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
I wrote an article where I built simple universal contexts like so: use-shared-store.ts import { getContext, hasContext, setContext } from "svelte";
import { readable, writable } from "svelte/store";
// context for any type of store
export const useSharedStore = <T, A>(
name: string,
fn: (value?: A) => T,
defaultValue?: A,
) => {
if (hasContext(name)) {
return getContext<T>(name);
}
const _value = fn(defaultValue);
setContext(name, _value);
return _value;
};
// writable store context
export const useWritable = <T>(name: string, value: T) =>
useSharedStore(name, writable, value);
// readable store context
export const useReadable = <T>(name: string, value: T) =>
useSharedStore(name, readable, value);https://dev.to/jdgamble555/the-correct-way-to-use-stores-in-sveltekit-3h6i Although, this should be built into SvelteKit to prevent bad SvelteKit coding! J |
Beta Was this translation helpful? Give feedback.
-
|
@jdgamble555 nice idea! You beat be to the article punch. I wrote something that clarifies what is happening, why it is bad, what the documentation lacks, and how to implement basic contexts (as shown in the docs). https://dev.to/brendanmatkin/safe-sveltekit-stores-for-ssr-5a0h |
Beta Was this translation helpful? Give feedback.
-
|
I have created a proposal to solve this issue and allow safe, ergonomic and usable file-based stores for svelte: |
Beta Was this translation helpful? Give feedback.
-
|
This breaks the Principle of Least Astonishment so much: https://en.wikipedia.org/wiki/Principle_of_least_astonishment I am truly deeply astonished. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
|
To be honest, I don't think this requires a technical solution. Sticking to good programming practices is sufficient. Devs should just avoid using globals. Really, this has been taught for decades, long before web dev was a thing. If you need per-request stores, put them in context and/or locals and pass them explicitly to the functions that require them. A pure function that does not reply on implicit global state is much less trouble. I know putting stores in global variables has been common practice for many Svelte devs pre SevelteKit. In SPA scenarios people got away with it, but now these practices backfire. That's not an inherit issue with Svelte, just bad practice. So, IMO, this problem is not a technical one. What's needed is better training (don't use globals unless it's for true singletons; write functions as pure as possible). The docs could be more explicit about this, I guess, but it's clearly mentioned. I don't want to sound condescending. I just don't believe this is a bug that needs fixing. |
Beta Was this translation helpful? Give feedback.
-
|
I'm trying to wrap my head around all of this so hopefully someone can confirm if my understanding is correct. I get if I make a store inside What I'm struggling to wrap my head around is if a store is created using a Eg Skeleton has a warning around this for some of their utilities, as behind the scenes they use stores which work via exporting the store as a const Is this only a danger if it's used within a load function (both |
Beta Was this translation helpful? Give feedback.
-
|
Could someone tell me if svelte 5 runes address this problem somehow? |
Beta Was this translation helpful? Give feedback.

{{title}}
-
Describe the bug
In adopting Svelte for our chat+communities app, we are trying to utilize the Store for our complex state needs. In this process, we came across semantic differences between stores on the client vs the server that are very concerning -- described in detail here: #2213 Using a Svelte store in the load function causes weird behavior
We are coming from
react+redux, which also has the concept of a store where you would keep complex global state. In our app, we use it to store the authenticated user'sprofileand privateconversations+messages. If we did this in a Svelte Store where it's treated as an in-memory singleton, there's the significant potential to leak personal/sensitive data to others using the app. Furthermore, SvelteKit documentation does a good job of making the case that Stores are where complex state should happen, but makes no mention of the implicit risks and impedance mismatch when SSR is used in conjunction with Stores.What is the recommended approach for handling complex, user-specific (private) global reactive state in SvelteKit? Are there any plans to address the implications of SSR Stores more explicitly in the documentation?
Reproduction
See #2213 for reproduction
Logs
No response
System Info
`@sveltejs/kit 1.0.0-next.282`Severity
serious, but I can work around it
Additional Information
No response
Beta Was this translation helpful? Give feedback.
All reactions