kit
kit copied to clipboard
Load's fetch re-requests data on every route change with prerendered pages
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

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
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!
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
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 had to say I've been struggling to avoid that fetch when changing routes. With.
prerender = true
andhydrate = 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 usemaxage
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?
I had to say I've been struggling to avoid that fetch when changing routes. With.
prerender = true
andhydrate = 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 usemaxage
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 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.
@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!
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.
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
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 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.
@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?
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.
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.
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.
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.
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.