Replace the current implementation of load
#2939
Replies: 4 comments
-
Very interesting read thanks,
One year later do you have a battle-tested implementation to share? |
Beta Was this translation helpful? Give feedback.
-
@grischaerbe I just stumbled across this old post of yours. I know it's a bit delayed, but I thought I'd see if I could offer some suggestions
Rather than putting the cache in each individual
This option may not have existed when you originally posted, but it sounds like you're looking to mark the |
Beta Was this translation helpful? Give feedback.
-
Thanks for sharing. Could #10481 be related? |
Beta Was this translation helpful? Give feedback.
-
For future readers it should be noted that this is definitely not backwards compatible, because currently load is explicitly allowed to return things that can't be serialized (e.g. functions) and the cached result of load needs to be serialized to send it from the server to the client on initial page load with SSR. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi,
First of all: Svelte and SvelteKit is great, I'm coming from Vue/Nuxt and React/Next and Svelte feels like a revelation in many parts. There are some things I'm not 100% happy with, and this discussion is about one of them.
To make senseful use of the current implementation of loading data for prerendering pages you have to use the custom
fetch
implementation to fetch data that is provided to the method on both server- and clientside.The mechanics are somewhat "simple": this custom
fetch
implementation "caches" data on the server side and ships it along the rendered html, ready for the customfetch
implementation on the client side to pick it up and use it without actually making an API roundtrip. Clever!There are however some pitfalls to that approach.
Caching
Usually my stack looks like this: I'm setting up a GraphQL API on the server side and I'm using Typescript along with GQL code generation on the client side to achieve a maximum of end-to-end type safety. This for me has proven to be a pretty good solution to produce error-free code that is easily maintainable and easy to reason about even after some time went by.
For simple GraphQL operations I'm using
graphql-request
which has the option to supply a custom fetch implementation.Problem
In a current project (which also happens to be the first full SvelteKit project) I'd like to add a caching layer somewhere along the data flow. For some past projects I wrote a simple but type-safe caching library and it's working great in conjunction with Nuxt.JS's
asyncData
andfetch
.A NuxtJS example of
graphql-request
with a cache layer in between would look something like this:The first thing to note is that the complexity of injecting a custom
fetch
implementation is missing here, but the important difference is that caching works as expected. When NuxtJS is in SSR mode and and you hit that page without navigating to it (= it's actually prerendering on the server), the server is able to use the cache layer provided bycache.cacheable
to fetch the data and ships it along with the prerendered page, ready for NuxtJS to hydrate the page without callingasyncData
and thereby making an API request. The client now holds it's own cache and subsequently navigating to a page implementing that caching technique would hit the API and cache the result.Now let's look at this example in SvelteLand:
The first thing to note is the added complexity of the injection of a custom
fetch
implementation. It's a detail but missing it is something that TypeScript can't check sincefetch
is monkey patched.Let's look at how the caching layer would work in this example in SSR mode:
If this page is hit the first time, no cache value would be present and the API would be hit, resulting in the custom
fetch
implementation to be used to fetch data. Since this implementation caches the result to ship it to the client, everything works as expected.Let's say there's another request to that page and the cache jumps in to provide the data. This time the custom
fetch
is not called, the page is prerendered with the cached data. The client receives a prerendered page but lacks the API data for hydration. It's making another API roundtrip.The rest of the application runs as expected since subsequent calls from the client side maintain their own cache.
I thought about this for a while and tried to come up with a workaround.
Workaround
SvelteKit offers the creation of API endpoints. To use a cache layer, I created an endpoint
/api/v1/page.json.ts
like this:and a
load
proxy like this:to use it in a page like this:
Now the caching on the server works as expected with the additional benefit that subsequent calls from the client side also hit the server side cache. But it adds a whole lot complexity (which I think stands in quite a contrast to the rest of Sveltes ideals and conventions) and subsequent API calls which end up not hitting the server side cache actually travel quite far:
client → server → API → server → client
.Also – and this is what actually stops me from following this approach – it means a lot more type-casting (
fetch() as SomeType
– maybe it isn't of that type?). You would have to cast theload
function's fetch response (with all itsawait json() => { data?: any; errors?: Array<{ message: string }>
goodies 😒) and on the component instance property then. NuxtJS 3 on the other hand with itsuseAsyncData
hook needs no type-casting at all.Solution
This is where it gets less technical and more hypothetical since I didn't really delve into SvelteKits codebase yet:
For a starter I propose getting rid of the custom
fetch
implementation and caching the result of the wholeload
function instead. This should even be backwards compatible.But this still requires maintaining proper type casting on the component instance property and so I'd like to go one step further and suggest to get inspired by the NuxtJS implementation of a hook-based solution that sits right in a component instance:
useLoad
would return a Svelte store with an additional methodreload
that can be used to rerun the provided callback at any time. Since it's a store, you would be able to subscribe to it from anywhere in your application. The store would be type-safe without casting it, which would be so great. Note that this would not replace the API endpoints functionality SvelteKit provides.It's a feature I'm dearly missing in SvelteKit and I'd like to hear your opinion about it. I'm not a pro in contributing to OSS, but I can try my best to implement this when the community thinks it would be a good idea.
Also sorry for the excessiveness of this post, it got a little bit out of hand. Have a nice weekend ✌️
Beta Was this translation helpful? Give feedback.
All reactions