Search code examples
apollosveltesveltekit

How to initialize ApolloClient in SvelteKit to work on both SSR and client side


I tried but didn't work. Got an error: Error when evaluating SSR module /node_modules/cross-fetch/dist/browser-ponyfill.js:

<script lang="ts">
import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";

const client = new ApolloClient({
    ssrMode: true,
    link: new HttpLink({ uri: '/graphql', fetch }),
    uri: 'http://localhost:4000/graphql',
    cache: new InMemoryCache()
  });
</script>

Solution

  • With SvelteKit the subject of CSR vs. SSR and where data fetching should happen is a bit deeper than with other somewhat "similar" solutions. The bellow guide should help you connect some of the dots, but a couple of things need to be stated first.

    To define a server side route create a file with the .js extension anywhere in the src/routes directory tree. This .js file can have all the import statements required without the JS bundles that they reference being sent to the web browser.

    The @apollo/client is quite huge as it contains the react dependency. Instead, you might wanna consider importing just the @apollo/client/core even if you're setting up the Apollo Client to be used only on the server side, as the demo bellow shows. The @apollo/client is not an ESM package. Notice how it's imported bellow in order for the project to build with the node adapter successfully.

    Try going though the following steps.

    1. Create a new SvelteKit app and choose the 'SvelteKit demo app' in the first step of the SvelteKit setup wizard. Answer the "Use TypeScript?" question with N as well as all of the questions afterwards.
    npm init svelte@next demo-app
    cd demo-app
    
    1. Modify the package.json accordingly. Optionally check for all packages updates with npx npm-check-updates -u
    {
        "name": "demo-app",
        "version": "0.0.1",
        "scripts": {
            "dev": "svelte-kit dev",
            "build": "svelte-kit build --verbose",
            "preview": "svelte-kit preview"
        },
        "devDependencies": {
            "@apollo/client": "^3.3.15",
            "@sveltejs/adapter-node": "next",
            "@sveltejs/kit": "next",
            "graphql": "^15.5.0",
            "node-fetch": "^2.6.1",
            "svelte": "^3.37.0"
        },
        "type": "module",
        "dependencies": {
            "@fontsource/fira-mono": "^4.2.2",
            "@lukeed/uuid": "^2.0.0",
            "cookie": "^0.4.1"
        }
    }
    
    1. Modify the svelte.config.js accordingly.
    import node from '@sveltejs/adapter-node';
    
    export default {
        kit: {
            // By default, `npm run build` will create a standard Node app.
            // You can create optimized builds for different platforms by
            // specifying a different adapter
            adapter: node(),
    
            // hydrate the <div id="svelte"> element in src/app.html
            target: '#svelte'
        }
    };
    
    1. Create the src/lib/Client.js file with the following contents. This is the Apollo Client setup file.
    import fetch from 'node-fetch';
    import { ApolloClient, HttpLink } from '@apollo/client/core/core.cjs.js';
    import { InMemoryCache } from '@apollo/client/cache/cache.cjs.js';
    
    class Client {
        constructor() {
            if (Client._instance) {
                return Client._instance
            }
            Client._instance = this;
    
            this.client = this.setupClient();
        }
    
        setupClient() {
            const link = new HttpLink({
                uri: 'http://localhost:4000/graphql',
                fetch
            });
    
            const client = new ApolloClient({
                link,
                cache: new InMemoryCache()
            });
            return client;
        }
    }
    
    export const client = (new Client()).client;
    
    
    1. Create the src/routes/qry/test.js with the following contents. This is the server side route. In case the graphql schema doesn't have the double function specify different query, input(s) and output.
    import { client } from '$lib/Client.js';
    import { gql } from '@apollo/client/core/core.cjs.js';
    
    export const post = async request => {
        const { num } = request.body;
    
        try {
            const query = gql`
                query Doubled($x: Int) {
                    double(number: $x)
                }
            `;
            const result = await client.query({
                query,
                variables: { x: num }
            });
    
            return {
                status: 200,
                body: {
                    nodes: result.data.double
                }
            }
        } catch (err) {
            return {
                status: 500,
                error: 'Error retrieving data'
            }
        }
    }
    
    
    1. Add the following to the load function of routes/todos/index.svelte file within <script context="module">...</script> tag.
        try {
            const res = await fetch('/qry/test', {
                method: 'POST',
                credentials: 'same-origin',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    num: 19
                })
            });
            const data = await res.json();
            console.log(data);
        } catch (err) {
            console.error(err);
        }
    
    1. Finally execute npm install and npm run dev commands. Load the site in your web browser and see the server side route being queried from the client whenever you hover over the TODOS link on the navbar. In the console's network tab notice how much quicker is the response from the test route on every second and subsequent request thanks to the Apollo client instance being a singleton.