Skip to content

Expand documentation on SSR hydration with SvelteKit #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Jakeii
Copy link

@Jakeii Jakeii commented Jan 20, 2022

I saw this wasn't documented but found that it works great, so have expanded the documentation, very similar to the next.js/react-query approach

@Jakeii
Copy link
Author

Jakeii commented Jan 21, 2022

Actually, i discovered the approach I used in the example doesn't quite work as expected. I did figure it out though, will update very soon.

@amen-souissi amen-souissi added the documentation Improvements or additions to documentation label Jan 22, 2022
Copy link
Collaborator

@amen-souissi amen-souissi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...

@Jakeii
Copy link
Author

Jakeii commented Jan 25, 2022

@amen-souissi So I've discovered 2 things, one is that SvelteKit does the (de)hydration automatically when using the fetch passed to the load function, which is worth a mention.

The other is that unless I'm missing something, using svelte-query's hydration is a bit inelegant for a few reasons:

  • As far as I can see, for the same QueryClient instance to be available in all load functions it needs to be created in __layout.svelte's load and passed around via stuff (or another means), as the getContext/setContext used in useQueryClient/useQuery doesn't seem to work during SSR.
  • The Hydrate Component can't be used as the re-hydration would be too late, as load runs in the browser too so the prefetched requests would also run in the browser, but we can call the hydrate function 'manually' in __layout.svelte's load.
  • The only the only way to get the dehydratedState from various components' load during SSR to _layout.svelte's load during browser rendering is the session store (not really a big issue) (stuff doesn't work for some reason)

This is the sort of thing we're ending up with:

// __layout.svelte
<script context="module">
  import { hydrate, QueryClient } from '@sveltestack/svelte-query';

  export function load({ stuff, session }) {
    const { dehydratedState } = session; // dehydratedState is set in the session during SSR

    const queryClient = new QueryClient();

    hydrate(queryClient, dehydratedState); // 

    stuff.queryClient = queryClient; // save a reference to the hydrated queryClient instance so it can be used in other components' `load`

    return { props: {queryClient}};
  }

<script>
  import { QueryClientProvider } from '@sveltestack/svelte-query';

  export const queryClient;
</script>

<QueryClientProvider client={queryClient}>
	<slot />
</QueryClientProvider>
// pages/posts.svelte
<script context="module">
  import { dehydrate, QueryClient, useQuery } from 'react-query';

export async function load({ session }) {
  const queryClient = stuff.queryClient; // access the queryClient, during SSR it won't have been hydrated but when loaded in the browser it will

  await queryClient.prefetchQuery('posts', getPosts)

  session.dehydratedState = dehydrate(queryClient);  // save the dehydratedState in the session during SSR so it can be accessed in the browser
  }
}
</script>
<script>
  const queryResult = useQuery('posts', getPosts) // by the time we get here hydration will have happend
</script>

@10p-freddo
Copy link

@Jakeii super helpful, thanks!

Are you able to build successfully when you import hydrate and dehydrate in the way you indicated? If I try to import either of them from @sveltestack/svelte-query, the dev server runs but I can't complete a build:

"hydrate" cannot be exported from ../../node_modules/@sveltestack/svelte-query/svelte/queryCore/core/hydration.js as it is a reexport that references itself.

This appears to work instead, with the caveat that you lose the type declarations:

import { hydrate } from '@sveltestack/svelte-query/svelte/queryCore/hydration';

@Jakeii
Copy link
Author

Jakeii commented Feb 4, 2022

@Jakeii super helpful, thanks!

Are you able to build successfully when you import hydrate and dehydrate in the way you indicated? If I try to import either of them from @sveltestack/svelte-query, the dev server runs but I can't complete a build:

"hydrate" cannot be exported from ../../node_modules/@sveltestack/svelte-query/svelte/queryCore/core/hydration.js as it is a reexport that references itself.

This appears to work instead, with the caveat that you lose the type declarations:

import { hydrate } from '@sveltestack/svelte-query/svelte/queryCore/hydration';

strange, building works fine for me, what version of sveltekit are you using?

@10p-freddo
Copy link

1.0.0-next.250

I haven't investigated because the workaround is simple enough, but it is strange, indeed.

@Jakeii
Copy link
Author

Jakeii commented Feb 10, 2022

@ghughes started happening to me randomly, hopefully the fix can be merged soon #76

@johnny-mh
Copy link

johnny-mh commented Apr 4, 2022

Here is my solution. it work well with pagination.

// __layout.svelte

<script lang="ts">
    import { session } from '$app/stores';
    import { Hydrate, QueryClient, QueryClientProvider } from '@sveltestack/svelte-query';

    const queryClient = new QueryClient({
        // If you don't pass staleTime. then fetching 1 page on first render.
        // So pass 5 minutes for 1st render
        defaultOptions: { queries: { refetchOnWindowFocus: false, staleTime: 60000 } }
    });
</script>

<QueryClientProvider client={queryClient}>
    // Get state from server from session
    <Hydrate state={$session.dehydratedState}>
        <slot />
    </Hydrate>
</QueryClientProvider>
// /consults/[page].svelte

<script lang="ts" context="module">
    import { browser } from '$app/env';
    import { useQuery, dehydrate, QueryClient, type QueryFunction } from '@sveltestack/svelte-query';

    let origin = '';

    export async function load({ url, params, session }) {
        // Create the QueryClient on server only for prefetching
        if (!browser) {
            origin = url.origin;

            const queryClient = new QueryClient();
            await queryClient.prefetchQuery(`consults/${params.page}`, getConsults);
            
            // Return stuff here isn't help to send state to `__layout.svelte`
            // So using session for deliver it to client side `__layout.svelte`
            session.dehydratedState = dehydrate(queryClient);
        }

        return {};
    }

    const getConsults: QueryFunction<GetConsultResponse, string> = async ({ queryKey }) => {
        const pageStr = /consults\/(\d+)/.exec(queryKey[0])![1];
        const page = Number(pageStr || '1');
        const res = await fetch(`${origin}/api/consults/${page}`);

        return res.json();
    };
</script>

<script lang="ts">
    import { page } from '$app/stores';
    import { goto } from '$app/navigation';

    $: pageNum = Number($page.params.page);
    $: result = useQuery(`consults/${pageNum}`, getConsults);

    function onSetPage(e: CustomEvent) {
        goto(`/consults/${e.detail}`);
    }
</script>

{#if $result.isLoading}
loading!
{:else}
done!
{/if}

image

It does not fetching fist time on render 1 page. when click 2page. it fetching 2nd page.

at last. click 1 page it fetching 1 page. but it does not fetch first render :)

Copy link

@NatoBoram NatoBoram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A year late isn't too late!

Ok but this is literally the only resource on SSR for Svelte Query.

... I hope it works?

Together with SvelteKit's [`load`](https://kit.svelte.dev/docs#loading), you can pass the data you fetch to `useQuery`'s' `initialData` option:

```markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```markdown
```svelte

### Using `initialData`

Together with SvelteKit's [`load`](https://kit.svelte.dev/docs#loading), you can pass the data you fetch to `useQuery`'s' `initialData` option:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Together with SvelteKit's [`load`](https://kit.svelte.dev/docs#loading), you can pass the data you fetch to `useQuery`'s' `initialData` option:
Together with SvelteKit's [`load`](https://kit.svelte.dev/docs#loading), you can pass the data you fetch to `useQuery`'s `initialData` option:

- Wrap your app component with `<Hydrate>` and pass it the `dehydratedState` prop from `pageProps`

```markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```markdown
```svelte


```markdown
// __layout.svelte

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// __layout.svelte
// +layout.svelte

- Create a new `QueryClient` instance
- Prefetch the data using the clients `prefetchQuery` method and wait for it to complete
- Use `dehydrate` to dehydrate the query cache and pass it to the page via the `dehydratedState` prop. This is the same prop that the cache will be picked up from in your `__layout.svelte`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Use `dehydrate` to dehydrate the query cache and pass it to the page via the `dehydratedState` prop. This is the same prop that the cache will be picked up from in your `__layout.svelte`
- Use `dehydrate` to dehydrate the query cache and pass it to the page via the `dehydratedState` prop. This is the same prop that the cache will be picked up from in your `+layout.svelte`

// pages/posts.svelte
<script context="module">
import { dehydrate, QueryClient, useQuery } from 'react-query';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔


```markdown
// pages/posts.svelte

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// pages/posts.svelte
// pages/posts/+page.svelte

import { Hydrate } from '@sveltestack/svelte-query/hydration'
```markdown
<script>
import { Hydrate } from '@sveltestack/svelte-query'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { Hydrate } from '@sveltestack/svelte-query'
import { Hydrate } from '@sveltestack/svelte-query'

@@ -356,4 +356,4 @@
]
}
]
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

}
</script>
<script>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a mix of <script> and <script lang="ts">; it would be nice if it could all be <script lang="ts"> so it can be useful to both JavaScript users with /** @type {} */ and to TypeScript users with import type {}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants