diff --git a/text/0000-server-preload.md b/text/0000-server-preload.md new file mode 100644 index 0000000..e7f131c --- /dev/null +++ b/text/0000-server-preload.md @@ -0,0 +1,214 @@ + +- Start Date: 2020-09-03 +- RFC PR: [#31](https://github.com/sveltejs/rfcs/pull/31) +- Svelte Issue: (leave this empty) + +# Server preload + +## Summary + +Allow Sapper page components to contain the server-side logic for fetching the data the page needs. + +## Motivation + +In many cases — perhaps the significant majority — a Sapper page's `preload` function is doing nothing more than passing the page parameters to a `this.fetch` call to a sibling server route. In other words, a blog post page like the `blog/[slug].svelte` one in the [Sapper template](https://github.com/sveltejs/sapper-template/blob/master/src/routes/blog/%5Bslug%5D.svelte) might have some code like this... + +```svelte + +``` + +...which necessitates an additional `blog/[slug].json.js` file like this: + +```js +export async function get(req, res, next) { + const post = await get_post_somehow(req.params.slug); + if (post) { + res.writeHead(200, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify(post)); + } else { + res.writeHead(404, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify({ + message: 'Not found' + })); + } +} +``` + +Essentially, the `preload` function is nothing but boilerplate, which is anathema to the Svelte philosophy. + +Furthermore, when server-rendering this route, we have to do something rather odd: we must make an HTTP request *to ourselves*, while jumping through a number of hoops to ensure that we have the correct cookies for the request. This feels complex and wasteful. + +## Detailed design + +Building on @lukeed's work in [#27](https://github.com/sveltejs/rfcs/pull/27), this RFC proposes that we support server-only `preload` functions. + +Our blog post component could be rewritten thusly: + +```svelte + +``` + +> 🐃 I leave the question of whether it's `module:ssr`, `module ssr` or something else entirely to [#27](https://github.com/sveltejs/rfcs/pull/27) — though I'll just throw out the suggestion that having a space-separated list of contexts has a nice future-proof feeling to it + +When server-rendering this page, Sapper could generate preloaded props trivially: + +```js +const props = await Component.preload.call({ + redirect: (statusCode, location) => {...}, + error: (statusCode, error) => {...} +}, page, session); +``` + +Upon client-side navigation, Sapper would behave as though an implicit preload function had been generated. It would need to make a request to some known path; one logical choice would be to use the same path as for the page itself, but using an `Accept` header to determine whether to return HTML or JSON: + +```svelte + +``` + +> 🐃 A possible alternative to the `preload` signature would be to use the existing `get` signature of server routes. This could potentially allow a page to define `post`, `patch` and `delete` handlers as well. It does however reintroduce boilerplate around `Content-Type` etc. + +One minor benefit of this approach: client-side components get a little bit smaller. + +### Custom client-side preload logic + +In some cases, you _do_ need different logic on the client as on the server: + +* You might have client-side storage that contains multiple pages of items, loaded via infinite scroll, rather than the single page you get when server-rendering +* On a couple of occasions I've added code to `preload` that delays returning a value in client-side navigation until some images have been preloaded, for the sake of a layout that is immediately stable + +It should be possible to take advantage of a server preload function while retaining that flexibility. One idea: a new `this.load` function for client preloads: + +```svelte + +``` + +## How we teach this + +> What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Svelte patterns, or as a +wholly new one? + +I would argue that this pattern is generally applicable enough that it could completely replace the current way of doing things. We would still need a concept of server routes (some routes, like `/auth/logout`, don't belong in a component), but I suspect we could get rid of `this.fetch` entirely. + +> 🐃 Given that, it's possible that we should take the opportunity to have a broader rethink of `preload`, such as whether it should continue to only return data corresponding to props, or should instead return an object with metadata (obviating the need for `this.redirect` and `this.error`, but also adding cache headers, differentiating between pages that can be generated at build time vs server-rendered at runtime, etc): + +```svelte + +``` + +> Would the acceptance of this proposal mean the Svelte guides must be +re-organized or altered? Does it change how Svelte is taught to new users +at any level? + +Paradoxically, it makes Sapper both easier and harder to learn — it makes it easier to start building an app, but ultimately increases the API surface area. I *think* it would be a net positive. + +> How should this feature be introduced and taught to existing Svelte +users? + +Using the existing migration guide. + +## Drawbacks + +* At present, if you follow the `blog/[slug].svelte`/`blog/[slug].json.js` convention, it's very easy to treat your web app as an API — just slap a `.json` on the end of a URL and you're done. We'd lose that. +* A shared `preload` function allows you to immediately redirect if `session.user === undefined`, for example. Under this RFC's model, the client-side Sapper runtime would need to make an HTTP request to determine that, if we were relying on implicit client preloads. +* `Accept: application/json` implies our preload functions can only return JSON — at present, `preload` functions can return anything that [devalue](https://github.com/Rich-Harris/devalue) supports, though I suspect this benefit is more academic than practical +* While this is an opportunity to revisit our design decisions around the `preload` signature, including whether to provide things like `this.fetch`, we're also talking about breaking changes. No-one likes breaking changes if they can be avoided. + + +## Alternatives + +The yaks in this RFC (🐃) are somewhat hairy, so I've leaving space open for discussion around those rather than explicitly considering alternative designs. + +If we were to rethink aspects of `preload`, a couple of things I strongly recommend reading are the [documentation on pages and data fetching](https://nextjs.org/docs/basic-features/pages) from Next, and the section on location-based cache in [this Remix blog post](https://blog.remix.run/p/remix-preview), not because they directly pertain to the topic of this RFC, but because they would likely inform whatever updated design we adopted. + +## Unresolved questions + +* How to identify SSR/client script blocks (i.e. [#27](https://github.com/sveltejs/rfcs/pull/27)) +* The exact signature of server/client `preload` functions (but also whether we continue to call them `preload`, or adopt new functions that permit non-GET requests) +* Also, something I haven't thought too deeply about, but which I assume is solvable: how the Sapper client runtime can 'know' whether a page has a server preload function, so that it knows to use the implicit client preload
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: