kit icon indicating copy to clipboard operation
kit copied to clipboard

Load's fetch re-requests data on every route change with prerendered pages

Open madeleineostoja opened this issue 3 years ago • 16 comments

Describe the bug

If I understand correctly, using the fetch implementation provided by load on a prerendered route should effectively just hydrate the page with JSON fetched during build, unless the request is different than it was on the server. Initial page loads suggest this is happening, but on every route change fetch is called again, and it's not consistently cached, resulting in laggy navigation on a fully prerendered site.

Reproduction

  • Branch of website this is occurring on: https://github.com/heybokeh/website/tree/load\
  • Data fetching function: https://github.com/heybokeh/website/blob/load/src/lib/prismic.ts#L18
  • Example simple route: https://github.com/heybokeh/website/blob/load/src/routes/pricing.svelte
  • Repro in staging: https://bokeh-website-git-load-tomorrowstudio.vercel.app/

Repro steps:

  • Load website in incognito (to avoid service worker caching) & open inspector
  • Navigate back and forth between routes
  • See repeated graphql requests in network tab

Logs

Screen Shot 2022-02-10 at 9 57 42 AM

System Info

System:
    OS: macOS 12.1
    CPU: (8) arm64 Apple M1
    Memory: 150.48 MB / 8.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.0 - /usr/local/bin/node
    Yarn: 1.22.15 - /usr/local/bin/yarn
    npm: 8.1.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 98.0.4758.80
    Firefox: 94.0.2
    Safari: 15.2
  npmPackages:
    @sveltejs/adapter-auto: ^1.0.0-next.17 => 1.0.0-next.17 
    @sveltejs/kit: ^1.0.0-next.260 => 1.0.0-next.260 
    svelte: ^3.46.4 => 3.46.4 

Severity

serious, but I can work around it

Additional Information

  • Deployed to vercel with adapter-vercel
  • Every route prerendered with export const prerender = true

madeleineostoja avatar Feb 09 '22 20:02 madeleineostoja

This is intentional — load doesn't distinguish between data that was used for prerendering pages and data that wasn't. And nor should it, since you might publish new content while users are already on the site, and using the initial serialized data each time would result in different behaviour for the page that the user happened to first hit — if they started on /a, then navigated to /b then /c then back to /a it would be inconsistent if /b and /c involved fetching data but /a didn't.

You can tell SvelteKit 'cache the result of this load call' by returning a maxage value from load. I've just noticed it isn't really documented, but in addition to setting a cache-control header on the page itself, it means that the data will be kept in memory for maxage seconds (upon hydration and after client-side navigation) and reused if you navigate back to it.

Site looks great btw!

Rich-Harris avatar Feb 09 '22 21:02 Rich-Harris

Ahh okay, that makes sense. Thanks Rich!

This issue can be closed then, but might be worth documenting the behaviour of maxage more? I thought it was just cache control so I hadn't bothered with it during dev. In general I think the load ergonomics are a little unintuitive at first for someone coming from the world of Next et al, but that's more an education/docs issue than a framework issue.

And thank you! Made the decision to rewrite our whole platform in Svelte the day your new job was announced :)

EDIT: Would also be great to be able to set a maxage when using shadow endpoints, because re-fetching __data.json on every route change is a limitation with that setup too

madeleineostoja avatar Feb 09 '22 22:02 madeleineostoja

I had to say I've been struggling to avoid that fetch when changing routes. With. prerender = true and hydrate = false, every initial load does indeed use the generated JSON, but I'm trying to generate static pages with the fetch done only at build time, so external fetches are not done for every route navigation, but load the JSON instead. I guess I could still use maxage but what would be my alternative, to call fetch from a page endpoint?

marlosin avatar Mar 01 '22 21:03 marlosin

I had to say I've been struggling to avoid that fetch when changing routes. With. prerender = true and hydrate = false, every initial load does indeed use the generated JSON, but I'm trying to generate static pages with the fetch done only at build time, so external fetches are not done for every route navigation, but load the JSON instead. I guess I could still use maxage but what would be my alternative, to call fetch from a page endpoint?

@Rich-Harris , for my use case, I don't want that the client executes external fetches for hydration on every route change. I have a CMS (strapi) running in Heroku where I expose my api, and my sveltekit client is hosted in Netlify, so my intention is to run a build every time my CMS changes. With that I think maxage wouldn't be the preferable choice for me because I just don't want to set like 6 months as I'm not sure whether this content will be updated some time before that. I'm using a personal fork and just changed this line https://github.com/marlosin/kit/commit/9b12d50ff084a31fd3a86942e01b90d24fa76f0c . I was wondering whether you see a way of accomplishing the same results without needing to do it?

marlosin avatar Mar 07 '22 19:03 marlosin

I had to say I've been struggling to avoid that fetch when changing routes. With. prerender = true and hydrate = false, every initial load does indeed use the generated JSON, but I'm trying to generate static pages with the fetch done only at build time, so external fetches are not done for every route navigation, but load the JSON instead. I guess I could still use maxage but what would be my alternative, to call fetch from a page endpoint?

I'm experiencing the same issue here.

prerender'd pages with load() in them will use the statically generated page if you directly visit the page, but on client-side navigation it will actually make the fetch inside load() which I would like to avoid, since the content already exists in the build!

Did you figure out why this is happening? Is it classified as a bug?

treboryx avatar May 27 '22 08:05 treboryx

@treboryx maybe endpoints would be the better alternative for you. In static generated pages they will produce a __data.json during prerender phase. That way the request will not be executed on the client.

manuelJung avatar May 30 '22 14:05 manuelJung

@treboryx maybe endpoints would be the better alternative for you. In static generated pages they will produce a __data.json during prerender phase. That way the request will not be executed on the client.

This is exactly what I wanted to do! Thank you very much!

treboryx avatar May 30 '22 17:05 treboryx

Just wanted to mention that after building a couple SSG client sites with this setup, it’s really not that clear, and I agree that maxage isn’t a solution for SSG sites that get rebuilt on content changes.

I think the docs should be much more explicit around all the ways that sveltekit loads data, because right now it feels like a real minefield and it’s the thing I spend the most of my time explaining to colleagues when getting them to try svelte in general.

I would also LOVE if there was a way for load to act like a page endpoint and generate a static bundle of data. Maybe I’m an outlier here but I find separate files for data fetching in every route really clunky, especially when it’s a 3 line API call, and especially when there are still cases that shadow endpoints silently fall over (index, error, layout, etc).

I think shadow endpoints and endpoints in general have a really strong use case in complex server logic (eg: serverside glue code for otherwise clientside apps), but are confusing as a tool for isomorphic code that you only want to run during build, which is super common for CMS backed SSGs, which themselves are a super common use case for sveltekit.

Sorry for the short essay but the data layer of sveltekit is the only part that really irks me, and the part that almost every dev I try to introduce it to gets hung up on.

madeleineostoja avatar May 30 '22 21:05 madeleineostoja

How did you manage to get it running? We experience the same problem. We have a strapi backend and want to load the data only in build step and don't reload data from client side.

Svelte-kit documentation is sadly pretty small with details..

We use currently the load function in our +page.js files

FabianClemenz avatar Oct 20 '22 10:10 FabianClemenz

How did you manage to get it running? We experience the same problem. We have a strapi backend and want to load the data only in build step and don't reload data from client side.

Svelte-kit documentation is sadly pretty small with details..

We use currently the load function in our +page.js files

Hi there.

In your +page.js of each page you want pre-rendered you need to add export const prerender = true;

If the content of said page is dynamic and is loading data with load, you need to make sure SvelteKit has access to that URL so it can crawl it.

Let me know if you get stuck.

treboryx avatar Oct 20 '22 10:10 treboryx

@treboryx Thanks for your reply. We have the prerender option in our main +layout.js - it should be applied to all of our pages. We want to get the content of our strapi backend only in build process and generate a static site. So for every content changes, we want to rebuild and redeploy. But currently it reloads the data after the site is initialised and on every route change.

FabianClemenz avatar Oct 20 '22 11:10 FabianClemenz

@treboryx Thanks for your reply. We have the prerender option in our main +layout.js - it should be applied to all of our pages. We want to get the content of our strapi backend only in build process and generate a static site. So for every content changes, we want to rebuild and redeploy. But currently it reloads the data after the site is initialised and on every route change.

In the build step, do you see all the URLs crawled / prerendered in the console?

treboryx avatar Oct 20 '22 11:10 treboryx

Revisiting this issue, since a few things have changed since it was opened. If you fetch data in load, SvelteKit will read the cache headers and add a data-ttl="..." attribute (based on cache-control and age headers) when it inlines the response body, and use that value to intercept fetches during subsequent client-side navigation. We don't currently do that for data returned directly from +page.server.js and +layout.server.js load functions, but we probably should.

However, none of that applies to prerendered pages, because you can't trust a ttl value for a page that was generated at some indeterminate time in the past. In theory you could replace it with an expires value, but only if everyone's clocks are more or less synchronised. So I'm not sure what a solution to this could look like, though I'm open to ideas.

Rich-Harris avatar Nov 10 '22 21:11 Rich-Harris

I agree on the point of @madeleineostoja:

... I find separate files for data fetching in every route really clunky, especially when it’s a 3 line API call, and especially when there are still cases that shadow endpoints silently fall over (index, error, layout, etc).

Perhaps add a <server> tag to Svelte compiler to allow keeping +page.ts content in +page.svelte files? Since it is a compiler, it can extract tag content during build. Not sure how existing tools need to be adjusted, <server> is not semantic HTML, but it makes a lot of sense for SSR/SSG.

Another addition could be ability to declare data "static" and let compiler prerender the page and remove client-side loading. And idea about "expiration" timestamp is good. I would not worry about accuracy of clocks and timezones, just make it in UTC. Client can conditionally re-load it after expiration time, and most use cases should not split hairs on milliseconds. If that path is chosen, one great feature could be getting static/expiration info from the data endpoint, so CMS system backend can decide / inform server data caching policy per page.

iva2k avatar Dec 05 '22 16:12 iva2k

A lot has changed in sveltekit since I made that comment. Back then the data layer was a mess of colocated load functions in script module, shadow endpoints, and data endpoints named funky things like data.json.ts, which is what I found so cumbersome. The file separation in sveltekit now makes sense, and while it could be overkill for certain scenarios at least now it's consistent, understandable, and streamlined. No complaints from me.

madeleineostoja avatar Dec 06 '22 00:12 madeleineostoja

Yes, svelte-kit is shaping very well. I did my first attempt on May 6, retried on May 25, and stopped due to few unresolved blocking issues. I'm doing a third attempt now, and it looks like I will get through with my very demanding list of functionality.

iva2k avatar Dec 06 '22 17:12 iva2k

Closing this since SvelteKit has changed a lot since this was opened, and most of the things mentioned are now adressed in the docs or outright more clear through the API. If there's a specific thing that you'd like to see improved in the docs, please opene a new issue.

dummdidumm avatar Feb 08 '23 15:02 dummdidumm