Search code examples
javascriptserver-side-renderingsveltesveltekit

SvelteKit Rendering a Random Prop is different between server and client


I would like to make a component in SvelteKit which has a randomized parameter. The problem is that the value this parameter takes is different for when the page is rendered server-side versus when that page becomes hydrated.

For example, consider this component:

<script>
    export let t = Math.random() * 90
    export let l = Math.random() * 90
</script>

<div class="box" style="--t: {t}vh; --l: {l}vw;"></div>

<style>
    .box {
        position: fixed;
        top: var(--t); left: var(--l);
        width: 10vw; height: 10vh;
        background-color: black;
        transition: all 1s;
    }
</style>

When the page is rendered on the server, t and l take on some random value, and the result is returned to the browser as HTML. However, once the page becomes hydrated, t and l take on different values. As a result, the box moves.

I don't want the box to move; rather, I want the random value returned by the server to be used by the client as well so there isn't a flash of changing style. Everything's fine if the page is navigated via the in-page router; it's when the page is server-rendered that the box moves.

The result is the same if I export a load function. Is there a way with SvelteKit for the server and client to agree on a random value?


Solution

  • You have a few options here:

    1. Set the random numbers in onMount so they're only set on the client. This will cause a FOUC, however, since the box won't be server rendered.
    <script>
      import { onMount } from 'svelte';
    
      let t, l;
    
      onMount(() => {
        t = Math.random() * 90;
        l = Math.random() * 90;
      })
    </script>
    
    {#if t && l }
    <div class="box" style="--t: {t}vh; --l: {l}vw;"></div>
    {/if}
    
    <style>
        .box {
            position: fixed;
            top: var(--t); left: var(--l);
            width: 10vw; height: 10vh;
            background-color: black;
            transition: all 1s;
        }
    </style>
    
    1. Generate the random numbers inside a server endpoint that you fetch in the load function. Since SvelteKit caches the result of fetches inside load, this should use the same random numbers on both client and server.
    // random.json.js
    export async function get() {
      return {
        body: {
          t: Math.random() * 90,
          l: Math.random() * 90,
        },
      };
    }
    
    <!-- index.svelte -->
    <script context="module">
      export async function load({ fetch }) {
        // this will be cached, so it will be the same on client & server
        const result = await fetch('/random.json');
        const { t, l } = await result.json();
        return {
          props: {
            t, l
          }
        }
      }
    </script>
    
    <script>
        export let t;
        export let l;
    </script>
    
    <div class="box" style="--t: {t}vh; --l: {l}vw;"></div>
    
    <style>
        .box {
            position: fixed;
            top: var(--t); left: var(--l);
            width: 10vw; height: 10vh;
            background-color: black;
            transition: all 1s;
        }
    </style>
    

    Personally, I like the second option better, since there's no FOUC.