I come from a React background, but I'm switching to Svelte and Sapper for my next application in order to fight the massive bundle size that comes with React these days. However, I'm having trouble initializing Svelte's store with data retrieved from localStorage.
As per the Sapper docs (https://sapper.svelte.dev/docs#Getting_started), I created my project by running npx degit "sveltejs/sapper-template#rollup" my-app
from the command line. I then installed the dependencies and removed the demo code in the src
folder.
I then created two files: src/routes/index.svelte
and src/store/index.js
.
Code for both:
src/store/index.js
import {writable} from "svelte/store";
export let userLang;
if(typeof window !== "undefined") {
userLang = writable(localStorage.getItem("lang") || "en");
} else {
userLang = writable(null);
}
src/routes/index.svelte
<script>
import {userLang} from "../store";
</script>
<p>Your Preferred Language: {$userLang}</p>
When I run the application and hit the index
route, I see this:
Your Preferred Language: null
which then almost immediately updates and changes to
Your Preferred Language: en
when there is no lang
item in localStorage, and changes to
Your Preferred Language: fr
After explicitly setting localStorage.setItem("lang", "fr")
from the developer console and refreshing.
I know that the store is being initialized on the server first where window
is undefined
and then is being rehydrated on the client. So this behavior is expected.
So my question is: how can I skip the server initialization entirely? Is it possible to only set up the store on the client (where localStorage
is defined) so that the user's chosen language is immediately available?
I can't default to having everything in English or any other language after the user has chosen to change their preferred language. I also can't get the user language from the browser via navigator.language
on initial page load either since navigator
is undefined
on the server as well.
And having a flash of empty text appear before the store rehydrates would screw up the UX for my application, especially when the value of userLang
is going to be used all over the place with translations.
So any strategies or hacks for this are definitely appreciated.
**** Deeper Issue ****
I would actually prefer to not have server-side rendering at all for this application, but I do need all the other excellent features that Sapper provides, like routing, prefetching, and static site building.
So I tried running npx sapper export
as per the docs to generate a completely static site in an effort to remove the server from the equation, but the exact same issue still occurs, even though there is no server being used at all.
Does anyone have any advice on how to configure Sapper and turn off SSR but keep the other features?
Thank you!
**** Update ****
As per Rich Harris's answer, wrapping the markup with {#if process.browser}
does the trick just fine. So I've updated the src/routes/index.svelte
file like so:
<script>
import {userLang} from "../store";
</script>
{#if process.browser}
<p>Your Preferred Language: {$userLang}</p>
{/if}
And the userLang
variable is immediately set with the value from localStorage
or defaults to en
as I intended for this simple demo. There is no more flash of null
, so it's essentially behaving like it's client-side only at this point.
I will work on fleshing out my project and see if there are any more issues I encounter. Til then, I think this solves my issue.
A simple solution is to save these variables to the underlying browser's localStorage . Let's modify the store to retrieve the default value from localStorage . import { writable } from "svelte/store"; const storedTheme = localStorage. getItem("theme"); export const theme = writable(storedTheme);
You can manually create a subscription to your store and persist the changes to localStorage and also use the potential value in localStorage as default value. @AnilSivadas Doing it on the server complicates it a bit. You could skip it on the server and just do it in the browser with a typeof window !==
Going over it again, Svelte stores are basically just reactive Javascript objects which can be imported into multiple components, all unrelated to each other. The examples used here are very simple, and it is likely that you will be using Svelte stores for more complicated data, but the concepts will remain the same.
Svelte stores offer similar features for state management. A store is an object with a subscribe() method that allows interested parties to be notified whenever the store value changes, and an optional set() method that allows you to set new values for the store. This minimal API is known as the store contract.
At present, SSR is non-optional. There's an issue open for an SPA mode — https://github.com/sveltejs/sapper/issues/383 — that would behave as you describe, we just need to get round to it.
We also plan to have built-in support for i18n in a future release: https://github.com/sveltejs/sapper/issues/576
In the meantime, you can fake it by wrapping all your markup in {#if process.browser}
— anything inside won't be server rendered, but will be present as soon as JavaScript kicks in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With