-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Shadow endpoints (makes load
boilerplate unnecessary in many cases)
#3532
Comments
I really like that Some things to discuss for "get-after-post" error handlingWhat happens if the get or post fail. The more interesting case is post succeeds and get fails (e.g. shaky network) added latencyget only runs after post has returned, adding another roundtrip resulting in a delayed response. Is this still a net-win because the get would have happened next either way and we saved the client->kit-server part for that? customizationhow would developers be able to opt out of this if the additional get request would cause issues for the underlying api services. |
Good questions!
To clarify, get-after-post doesn't mean the client would be making any additional requests — everything would be happening on the server, which would coalesce the We can't compare the cost of calling In that scenario, we call the (That's for the case where we're relying on the browser's default behaviour. If we're progressively enhancing a la #3533, then the
In the rare case where it really is appropriate for export async function get({ request }) {
if (request.method !== 'GET') return {};
// ...
} The |
Huge fan of this proposal. I've often thought the Love this. |
Wow, this does look super clean. However, I am a bit worried if this behavior might not actually be too clean. |
The extra learning gap is unfortunate, but I think it shouldn't be too hard to pick up particularly if you see it in a project (or in the demo project). The fact that there are two files with the same name should tip you off that something is going on. A quick trip to the docs should then set you straight. While there are some hairy details, I think the conceptual "the endpoint gives props to the page" is simple enough, and the details can then be filled in. If you start with an empty Kit project and somehow don't come over this, the worst case scenario is basically that you revert to the current situation of explicitly loading props. And besides the learning gap I think this is great. I'd love to cut that boilerplate in all my pages. |
I like this a lot and it addresses most of the key concerns of #3483. I especially like the fact that one can access serverside resources (e.g. a database) almost directly. The problem with Thus, this solution is a significant improvement on |
Would there be an equivalent for layouts load functions? Something like a |
First of all, love that you're thinking about this. This has been one of my key frustrations with SvelteKit. The solution you've come to is basically what I ended up with, too — routes can either return JSON or HTML depending on the accept header. Second, regarding this comment:
I think that might be true. There would definitely be a bit more magic to learn, but there's already magic in knowing about But maybe there's a one-liner that could be added to make it explicit? Not sure what that would look like. |
@Glench In the documentation, write in one sentence that shadow endpoint is similar to __layout and by analogy even a novice will know what is going on. |
#3679 was merged, so I'll close this. You can see how we've tackled the documentation — essentially, we're teaching shadow endpoints as the default (no mention of the word 'shadow'), and standalone endpoints as the exception. To @f-elix's point:
That probably makes sense, yeah. But I think it might turn out to be a bit of a design rabbit hole because the word |
@Rich-Harris Thanks so much for implementing this so fast, this is a game changer! |
|
This change broke an important use case for us. For certain pages, we use endpoints to proxy HTML content. This allows us to display custom HTML pages for certain users, while having a normal SvelteKit based page for the rest. If the shadow endpoint's response has a header of |
I made a quick PR to show how this could potentially be done, if there's interest in the feature I can finish it up and add tests/fix types/etc. |
Amazed this was shipped so fast, I came across the issue a week ago then checked the docs today for an unrelated thing and saw it documented as the default! I ran into a few rough edges:
|
I also really like this new feature. What we also do often is to load some data in |
Unfortunately, this has broken the static adapter. Please see #3833 |
Sorry for posting in a closed discussion, arrived here after getting the link from the Discord server while asking for some hints on when to use thanks! |
Yes, the documentation should be more precise and detailed. I'm confused, I currently don't know where to locate the data retrieval from the external server. I'd like to have a centralized place where I handle external APIs and potential errors, but I guess I have to spread everything across multiple endpoints? I don't know. I feel chaos. |
@gkatsanos the page endpoint code always runs on the server. So if you access a protected API (eg. a database or similar which involves credentials that should not leak to the frontend), you should always use page-endpoints. In the page-endpoints, you do not have access to the Personally, I think page-endpoints should be preferred and only use Of course you can also combine the |
Describe the problem
A significant number of pages have
load
functions that are essentially just boilerplate:The
load
function even contains a bug — it doesn't handle the 404 case.While we definitely do need
load
for more complex use cases, it feels like we could drastically simplify this. This topic has come up before (e.g. #758), but we got hung up on the problems created by putting endpoint code inside a Svelte file (you need magic opinionated treeshaking, and scoping becomes nonsensical).Describe the proposed solution
I think we can solve the problem very straightforwardly: if a route maps to both a page and an endpoint, we regard the endpoint (hereafter 'shadow endpoint') as providing the data to the page. In other words,
/blog/[slug]
contains both the data (JSON) and the view of the data (HTML), depending on the request'sAccept
header. (This isn't a novel idea; this is how HTTP has always worked.)In the example above, no changes to the endpoint would be necessary other than renaming
[slug].json.js
to[slug].js
. The page, meanwhile, could get rid of the entirecontext="module"
script.One obvious constraint: handlers in shadow endpoints need to return an object of props. Since we already handle serialization to JSON, this is already idiomatic usage, and can easily be enforced.
POST requests
This becomes even more useful with
POST
. Currently, SvelteKit can't render a page as a result of aPOST
request, unless the handler redirects to a page. At that point, you've lost the ability to return data (for example, form validation errors) from the handler. With this change, validation errors could be included in the page props.We do, however, run into an interesting problem here. Suppose you have a
/todos
page like the one from the demo app......and a page that receives the
todos
,values
anderrors
props......then the initial
GET
would be populated withtodos
, but when rendering the page following aPOST
to/todos
, thetodos
prop would be undefined.I think the way to solve this would be to run the
get
handler after thepost
handler has run, and combine the props:It might look odd, but there is some precedent for this — Remix's
useLoaderData
anduseActionData
, the latter of which is only populated after a mutative request.This feels to me like the best solution to #1711.
Combining with
load
In some situations you might still need
load
— for example, in a photo app you might want to delay navigation until you've preloaded the image to avoid flickering. It would be a shame to have to reintroduce all theload
boilerplate at that point.We could get the best of both worlds by feeding the props from the shadow endpoint into
load
:Prerendering
One wrinkle in all this is prerendering. If you've deployed your app as static files, you have most likely lost the ability to do content negotiation, meaning that even though we work around filename conflicts by appending
/index.html
to pages, there's no way to specify you want the JSON version of/blog/my-article
.Also, the MIME type of
prerendered-pages/blog/my-article
would be wrong, since static fileservers typically derive the MIME type from the file extension.One solution: shadow endpoints are accessed via a different URL, like
/_data/blog/my-article.json
(hitting the endpoint directly with anAccept
header would essentially just proxy to the shadow endpoint URL). App authors would need to take care to ensure that they weren't manuallyfetch
ing data from/blog/my-article
in a prerendered app. In the majority of cases, shadow endpoints would eliminate the need forfetch
, so this seems feasible.Alternatives considered
The main alternative idea is #758. A similar version of this has been explored by https://svemix.com. I'm personally more inclined towards shadow endpoints, for several reasons:
The other alternative is to do none of this. I think that would be a mistake — the
load
boilerplate really is unfortunate, but moreover I don't see any other good way to solve #1711.Importance
would make my life easier
Additional Information
No response
The text was updated successfully, but these errors were encountered: