Remove network calls from demo app
#4264
Development PRs
fix: #4264
I am accessing the Demo app from Tokyo but the response is not good. I considered using indexedDB or Session Storage but did not adopt them because it is important that this process be performed on the back end. And the process should not be complex to avoid making noise, so I finally opted for a simple in-memory object. Any opinions for a better demo app?
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
Tests
- Run the tests with
pnpm testand lint the project withpnpm lintandpnpm check
Changesets
- If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running
pnpx changesetand following the prompts. All changesets should bepatchuntil SvelteKit 1.0
Fixes #3503 Fixes #696 Reverts #3631 Reverts #835
Okay this is the last one of my SvelteKit wishlist items. Not sure what you'll think but it certainly makes sense to me.
This does two things, each in its own commit.
- Exposes
eventto the load() function - Reverts and deletes all the logic related to passing headers and cookies to the upstream service calls.
#3631 was clearly a mistake. If we have a network call graph that looks something like this:

It doesn't make any sense for the Node service to pretend to be a browser. Some of the reasons:
- You're lying about the user agent. If you passed one at all it should be, perhaps,
node/sveltekitor something - You're passing headers unidirectionally, sending them out but but not sending the response headers back to the browser, because of course that wouldn't make any sense. The calls are many to one so what should you do, merge them?
- The type is different (HTML vs most likely JSON), so the
acceptheader could be wrong - Some of the headers are nonsensical for service-to-service calls, like
refererwhich is a browser concern
There just seems to be some confusion and it seems like people are considering calls A-C in the graph to be the Z call proxied onward. When in fact those calls are unrelated to call Z, except that call Z is the reason calls A-C are being made.
But then even with all the complexity, we still can't pass along the most important information: the auth cookies. To get the full details of the incoming request you have to use an endpoint (or a hook). Endpoints should be a feature you can use if you want but shouldn't be mandatory.
In a more ops-driven, "close to the metal" kind of enterprise setup, our frontend services live among a constellation of other services, both in front as reverse proxies, and behind as upstream services. In this world things like SSL termination and endpoints are simply not wanted or needed. In this world if we want to avoid the moving target of CORS and serve our data on the same domain as our HTML, we simply put a reverse proxy in front and split traffic.
What this change does is restores control. If you want to call your backend services and spoof like you're a browser, you can do so! Copy all your headers over. If you want to forward a backend service's "set-cookie" directive to the browser, go ahead. But if you do nothing the calls to the backends get no headers, just as if you made a curl call, which seems like a pretty reasonable thing to do by default.
Here's some examples:
export default async function load({ event, params, fetch }) {
let response = await fetch('https://a.backend.com/data/' + event.id, {
headers: {
// Use end user credentials
// You could parse the cookie string and slice out a specific auth cookie, or
// or just pass the whole darn thing.
cookie: event?.request.headers.get('cookie'), // only relevant server-side
credentials: 'include', // only relevant client-side
},
});
...
}
If your backend sets a cookie and you want to set the same cookie in the browser, there's no way to do that explicitly, and I stripped out the automatic logic for that because, hey, making service calls shouldn't have side effects. You get the data, you do what you want with the data, end of story. So I extended some interfaces with a set_cookies string array.
export default async function load({ event, params, fetch }) {
let response = await fetch('https://a.backend.com/data/' + event.id, {
headers: {
cookie: event?.request.headers.get('cookie'), // only relevant server-side
credentials: 'include', // only relevant client-side
},
});
let data = await response.json();
let set_cookie = response.headers.get('set-cookie');
return {
props: {
widgets: data.widgets
},
set_cookies: set_cookie && [set_cookie],
}
More rationale: Even in the demo TODO app, you need access to the user's cookies to make the backend call. I know that backend is slated for removal in #4264 but as long as it remains it does prove a point. In my repro case for #4902 I have code like this:
johnnysprinkles/sveltekit_after_navigate_bug@53dd01a (comment)
where I have to put the userid in session because there's no way to read it from the request in load(). If you don't want to put it in session and you don't want to use an endpoint, you're currently out of luck.
Anyway, I hope this might at least provoke some discussion. I know this goes against a SvelteKit value of abstracting away the differences between client and server, but that abstraction is kind of a fiction anyway.
Also, I have no idea how much this would break. Shadow endpoints? Various adapters?
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
Tests
- Run the tests with
pnpm testand lint the project withpnpm lintandpnpm check
Changesets
- If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running
pnpm changesetand following the prompts. All changesets should bepatchuntil SvelteKit 1.0
Playable here: https://kit-git-sverdle-svelte.vercel.app/sverdle
The /todos app in the default template is a nice idea, but it's a bit misleading. People understandably imagine that the back end runs locally and then are surprised by the latency.
This PR replaces it with a Wordle clone that keeps all the game logic and data on the server, and persists user state using cookies.
It's not 100% finished — there's a subtle bug with the keyboard showing some letters as wrong instead of right-letter-wrong-place, and it needs some instructions and probably some style tweaks. I'd also love to get some help on the a11y front (cc @geoffrich!)
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
Tests
- Run the tests with
pnpm testand lint the project withpnpm lintandpnpm check
Changesets
- If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running
pnpm changesetand following the prompts. All changesets should bepatchuntil SvelteKit 1.0
Issue
Describe the bug
I have seen so many people complaining that SvelteKit is slow. No one realizes that a locally created demo app would be making network calls.
E.g.
This would also solve our issues around eventual consistency: #1564
Reproduction
I don't know if these people have particularly slow network connections or the app or host is sometimes being quite slow
Logs
No response
System Info
N/A
Severity
annoyance
Additional Information
No response
Info
Even without the speed concerns, I'd be in favor of a tiny ad-hoc in-memory data store thing for the todo app rather than calling out to the Cloudflare KV API.
I'm wondering if it's an issue with the API being served from somewhere in North America and certain users on other continents experiencing unusually slow network performance?
I also think some sort of in-memory data store would provide a better/more consistent user experience as long as it's still able to demonstrate the same functionality that is shown with the current setup. Specifically things like utilizing fetch, handling redirects, method overrides, etc... Having a demo that interacts with an external API is very useful as most people will need to do the same, and showing some best practices helps them get started on the right foot.
An update here could also be an opportunity to add functionality demonstrating shadow endpoints (unless that would create too much confusion for new users).
Semi-related issue here: #4225. Certainly not averse to overhauling the demo app.
The trouble is we're forced to choose between two options:
- Provide a backend (api.svelte.dev) that allows people to play around with endpoints and test dynamic server rendering in a way that works locally and when the app is deployed, or
- Use something in-memory or filesystem-based, which provides a great development experience locally but won't persist any data when you deploy the app
I'm a little uncomfortable with the idea of a demo app that stops working the minute you deploy it. Which leaves option 3 — don't demonstrate persistence and dynamic SSR — but then we're failing to demo a significant aspect of SvelteKit's functionality.
IMO...
By default, the Demo app will use an in-memory object. And as default, commenting out files that connect to the API, If users want to try to use API also, then users can use the commented-out files. (Or users can toggle these modes on the app.) Then users don't need to feel bad experience. And even users can try persistence and dynamic SSR if necessary.
I was thinking using something like json-server could provide a good middle-ground, where a full rest api could be utilized but the backend is running in the same environment as the frontend.
To me, persisting data between a local dev environment and a deployed environment isn't that important, considering in most real-world scenarios you wouldn't want that either.
The main downside I can see of using something like json-server is introducing a 3rd party dependency to the demo app that might require future maintenance. Secondarily, it adds a little more code and complexity to the demo app.
I prefer the app to send these requests, it is closer to reality. We need a more friendly interactive experience. For example, you could add a status display to each new todo item instead of waiting for the request to complete before adding it to the list.
To be clear, the issue isn't persisting data between local and deployed, it's having a backend that works both locally and deployed
it's having a backend that works both locally and deployed
I may not be getting it correctly, but #4266 stores data for each user, so there is no data conflict even if multiple people connect to the deployed app. The only issue is losing all data when the app is deployed. (But personally, I think this is a smaller issue than a bad experience.)
If the confusion is around the loading speed difference between static pages and the TODOs page, perhaps a simpler solution would be to just add some Loading states in the UI that explicitly show that the app is performing some requests in the background?
- Show a Preloading Indicator when navigating through pages.
- Show a loading spinner when creating/updating/deleting a TODO.
I see the recent skeleton is using https://api.svelte.dev .. My browser offered up some autocomplete data and because I had not reviewed all the skeleton code first (trying it out so to speak) I was surprised to see only local calls in the browser's console but 3 local calls taking almost a second each. The autocomplete entries added from the browser and confusion poses a small security risk. Also, I was wondering if there was a bug in the update and rendering but I found this issue which clarified everything.
Maybe add an checkbox to use the remote API or not so it is easy to verify the feel of a the local only API calls and with a store when unchecked. And of course a loading indicator.
Could we imagine providing both experiences with an environment variable switch?
(Defaulting to false)
const base = localDev ? 'http://localhost:3000/api/local_api_svelte_dev' : 'https://api.svelte.dev';
And implement get post patch del in api/local_api_svelte_dev?
After, the question is: Is it a realistic demo?
Something else I've just realised: for whatever reason, the todos page doesn't work on StackBlitz (https://sveltekit.new). Unclear whether it's a fixable bug or an inherent limitation, but could turn out to be another good reason to change the demo
Yes, userid is set in cookies and it seems that StackBlitz can't support this without customizing some browser settings.
In a demo remote fetch is nice to showcase imo. But maybe only reading data is sufficient? And a second part with some local things?
I'll be happy to help, let me know how.
Here's a screen recording showing the janky behavior you get when clicking on the todos tab in the demo app.
It freezes the ui for a short second giving a rather janky first impression of sveltekit.
https://user-images.githubusercontent.com/1503031/176706300-e7711381-8fa9-4337-b68d-f151f0657af3.mov
As I understand it, this is because sveltekit waits for a request to finish before doing the transition to the todos page. Is there a way to make the transition happen before the request has a response and then show a loading indication on the page until it does?
OK, so I've tried investigating this a bit further to see if I can find a path forward here.
Rich's comment here gives me the understanding that the point of the todos page is to demonstrate persistence and dynamic SSR. I take it that persistence here means that there has to be a network request to a remote server that persists the data. And dynamic SSR means the ui should be able to render on the server using data that is not statically stored with the source code, but rather fetched from somewhere else at render time.
Neither of these two requirements make the ui freeze when clicking todos necessary as far as I can tell. When clicking todos we are doing client side rendering and not SSR, right? So waiting for SSR should not be a limitation. I see no reason why the ui couldn't do the page transition even if the server is doing some async fetching of persisted data before returning a response. After the page transition we could just use the await block to show some loading state while waiting for this data in the todos component.
In theory this makes sense to me. But as far as I can tell Sveltekit endpoints are based on load functions which are implemented such that they always block rendering when they are async.
In SSR I think it makes sense to block, but in client-side navigation, it makes less sense (you can fill a store that will update when it will update).
So depending on cases (client-side navigation or SSR), I trick the effect of await in my apps.
You can check #4447 for some more detail.
How about just displaying the latency / response time after each submit below the submit form? Or some status updates about what the request is doing?
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)