'SvelteKit Pass Data From Server to Browser

I am trying to pass data from the server to the client to load my app faster and prevent multiple calls to the database.

Via Fetch

SvelteKit is made to do this via the fetch function. This is great if you have an endpoint that allows for custom fetch. But what if you don't?

Firebase is a perfect example of not having a custom fetch function.

Cookies

I would think I could use cookies, but when I set the cookie, it just prints 'undefined' and never gets set.

<script lang="ts" context="module">
    import Cookies from 'js-cookie';
    import { browser } from '$app/env';
    import { getResources } from '../modules/resource';

    export async function load() {

        if (browser) {

            // working code would use JSON.parse
            const c = Cookies.get('r');
            return {
                props: {
                    resources: c
                }
            };
        } else {

            // server
            const r = await getResources();

            // working code would use JSON.stringify
            Cookies.set('resources', r);

            // no cookies were set?
            console.log(Cookies.get());
            return {
                props: {
                    resources: r
                }
            };
        }
    }
</script>

So my code loads correctly, then dissapears when the browser load function is loaded...

Surely there is a functioning way to do this?

J



Solution 1:[1]

So it seems the official answer by Rich Harris is to use and a rest api endpoint AND fetch.

routes/something.ts

import { getFirebaseDoc } from "../modules/posts";

export async function get() {
    return {
        body: await getFirebaseDoc()
    };
}

routes/content.svelte

export async function load({ fetch }) {
  const res = await fetch('/resources');
  if (res.ok) {
    return {
      props: { resources: await res.json() }
    };
  }
  return {
    status: res.status,
    error: new Error()
  };
}

This seems extraneous and problematic as I speak of here, but it also seems like the only way.

J

Solution 2:[2]

You need to use a handler that injects the cookie into the server response (because load functions do not expose the request or headers to the browser, they are just used for loading props I believe). Example here: https://github.com/sveltejs/kit/blob/59358960ff2c32d714c47957a2350f459b9ccba8/packages/kit/test/apps/basics/src/hooks.js#L42

https://kit.svelte.dev/docs/hooks#handle

export async function handle({ event, resolve }) {
    event.locals.user = await getUserInformation(event.request.headers.get('cookie'));

    const response = await resolve(event);
    response.headers.set('x-custom-header', 'potato');
    response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');


    return response;
}

FYI: This functionality was only added 11 days ago in @sveltejs/[email protected]: https://github.com/sveltejs/kit/pull/3631

Solution 3:[3]

No need to use fetch!

You can get the data however you like!

<script context="module">
    import db from '$/firebaseConfig'

    export async function load() {
        const eventref = db.ref('cats/whiskers');
        const snapshot = await eventref.once('value');
        const res = snapshot.val();

        return { props: { myData: res.data } } // return data under `props` key will be passed to component
    }
</script>

<script>
    export let myData //data gets injected into your component
</script>


<pre>{JSON.stringify(myData, null, 4)}</pre>

Here's a quick demo on how to fetch data using axios, same principle applies for firebase: https://stackblitz.com/edit/sveltejs-kit-template-default-bpr1uq?file=src/routes/index.svelte


If you want to only load data on the server you should use an "endpoint" (https://kit.svelte.dev/docs/routing#endpoints)

Solution 4:[4]

My solution might solve it especially for those who work with (e.g: laravel_session), actually in your case if you want to retain the cookie data when loading on each endpoint.

What you should gonna do is to create an interface to pass the event on every api() call

interface ApiParams {
    method: string;
    event: RequestEvent<Record<string, string>>;
    resource?: string;
    data?: Record<string, unknown>;
}

Now we need to modify the default sveltekit api(), provide the whole event.

// localhost:3000/users
export const get: RequestHandler = async (event) => {
    const response = await api({method: 'get', resource: 'users', event});
    // ...
});

Inside your api() function, set your event.locals but make sure to update your app.d.ts

// app.d.ts
declare namespace App {
    interface Locals {
        r: string;
    }
    //...
}

// api.ts
export async function api(params: ApiParams) {
    // ...
    params.event.locals.r = response.headers.get('r')
});

Lastly, update your hooks.ts

/** @type {import('@sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
    const cookies = cookie.parse(event.request.headers.get('cookie') || '');
    const response = await resolve(event);

    if (!cookies.whatevercookie && event.locals.r) {
        response.headers.set(
            'set-cookie',
            cookie.serialize('whatevercookie', event.locals.r, {
                path: '/',
                httpOnly: true
            })
        );
    }
    
    return response;
});

Refer to my project:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jonathan
Solution 2
Solution 3
Solution 4 NosiaD