alvinometric / unleash-sveltekit

Get started with Unleash and Sveltekit
0 stars 0 forks source link

SvelteKit SSR example #1

Closed mootoday closed 2 weeks ago

mootoday commented 2 weeks ago

Hi 👋,

In the SvelteKit tutorial, the conclusion paragraph mentions the following:

The different approaches to feature flagging on a static vs SSR context

From what I can tell, the code in the tutorial and in this repo here doesn't work on the server. I'm looking into it, but wanted to post and see if I missed anything.

mootoday commented 2 weeks ago

I figured it out, documenting it here in case anyone else stumbles across this.

src/app.d.ts

import type { Unleash } from "unleash-client";

declare global {
    namespace App {
        // interface Error {}
        interface Locals {
            unleashClient: Unleash | undefined;
        }
        // interface PageData {}
        // interface PageState {}
        // interface Platform {}
    }
}

export {};

src/hooks.server.ts

import { sequence } from "@sveltejs/kit/hooks";
import { destroy, startUnleash, type Unleash } from "unleash-client";
import { UNLEASH_API_KEY } from "$env/static/private";

let unleashClient: Unleash;
let isUnleashClientError = false;

const handleFeatureFlags: Handle = async ({ event, resolve }) => {
    if (!unleashClient && !isUnleashClientError) {
        try {
            unleashClient = await startUnleash({
                url: "<YOUR_API_URL>",
                appName: "<YOUR_APP_NAME>",
                customHeaders: {
                    Authorization: UNLEASH_API_KEY,
                },
                skipInstanceCountWarning: true, // I haven't figured out why this is needed
            });
        } catch (error: unknown) {
            isUnleashClientError = true;
            destroy();
            console.error("Failed to start the unleash client: ", error);
        }
    }

    event.locals.unleashClient = unleashClient ?? undefined;
    return resolve(event);
};

export const handle = sequence(..., handleFeatureFlags)

src/routes/+layout.server.ts

import type { LayoutServerLoad } from "./$types";

export const load: LayoutServerLoad = async ({ locals }) => {
    let ff = {};

    if (locals.unleashClient) {
        ff = locals.unleashClient
            .getFeatureToggleDefinitions()
            .map(({ name }) => name)
            .reduce(
                (result, flag) => ({
                    ...result,
                    [flag]: locals.unleashClient?.isEnabled(flag, { userId: <CURRENT_USER_ID> }),
                }),
                {}
            );
    }

    return {
        ff,
    };
};

src/routes/+layout.svelte

<script lang="ts">
    import { FlagProvider } from "@unleash/proxy-client-svelte";

    const ffConfig = {
        url: "YOUR_FRONTEND_API_URL",
        clientKey: dev
            ? "default:development.xyz"
            : "default:production.xyz",
        refreshInterval: 15,
        appName: "<YOUR_APP_NAME>",
        context: {
            userId: <CURRENT_USER_ID>,
        },
    };

</script>

<FlagProvider config={ffConfig}>
    <slot />
</FlagProvider>

src/lib/components/feature-flag.svelte

<script lang="ts">
    import { browser } from "$app/environment";
    import { page } from "$app/stores";
    import { useFlag } from "@unleash/proxy-client-svelte";
    import { writable, type Writable } from "svelte/store";

    export let name: string;

    let isEnabled: Writable<boolean>;
    if (browser) {
        isEnabled = useFlag(name);
    } else {
        isEnabled = writable($page.data.ff[name]);
    }
</script>

{#if $isEnabled}
    <slot />
{/if}

Now anywhere in your app, you can use the <FeatureFlag name=""> component like this:

src/routes/+page.svelte

<script lang="ts">
    import FeatureFlag from "$lib/components/feature-flag.svelte";
</script>

<FeatureFlag name="test-one">Feature flag is enabled</FeatureFlag>
alvinometric commented 2 weeks ago

Thank you 🙏