Skip to content
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

Auto-generated endpoints from component code #758

Closed
Rich-Harris opened this issue Mar 29, 2021 · 14 comments
Closed

Auto-generated endpoints from component code #758

Rich-Harris opened this issue Mar 29, 2021 · 14 comments
Labels
documentation Improvements or additions to documentation

Comments

@Rich-Harris
Copy link
Member

good point via Twitter:

The current docs confused me a bit as a Next.js user by comparing load to getServerSideProps/getStaticProps, while it’s more like getInitialProps in the way that it exposes everything to the client.

They currently say this, which isn't totally accurate:

load is the SvelteKit equivalent of getStaticProps or getServerSideProps in Next.js or asyncData in Nuxt.js.

@Rich-Harris Rich-Harris added the documentation Improvements or additions to documentation label Mar 29, 2021
@TravisSpomer
Copy link
Contributor

FWIW, I was confused by the same, especially by how it could be like those Next.js equivalents while not allowing me to use server-only / build-only Node code like fs.readFile.

@Nick-Mazuk
Copy link
Contributor

Added a PR to update the docs: #813

@egoist
Copy link

egoist commented Apr 1, 2021

Will SvelteKit ever consider adding a getServerSideProps equivalent then? 🤔

@Rich-Harris
Copy link
Member Author

Maybe. We can't just declare endpoints inside components...

<script context="module">
  import db from '$lib/db';

  export async function get(request) {
    return {
      body: await db.get(request.params.id)
    };
  }
</script>

...because that code (including import db from '$lib/db') will be present in the client-side component as well. So we'd need some way of declaring a block of server-only code. You could do that with a preprocessor that runs differently in SSR mode vs CSR mode (keeps it for SSR, adds a load function that hits the auto-generated endpoint for CSR)...

-<script context="module">
+<script context="module server">
  import db from '$lib/db';

  export async function get(request) {
    return {
      body: await db.get(request.params.id)
    };
  }
</script>

...but now your editor isn't totally sure what's going on because it only gets the result of preprocessing in SSR mode or CSR mode, not both. The scoping is a little confusing as well.

Meanwhile, not having an explicit API makes debugging harder — you can't just slap a .json on the URL to see what data is being loaded from the adjacent index.json.js endpoint. So you've traded off one convenience for another. Given how easy it is to create endpoints, I'm not totally convinced this is worth it, but I also wouldn't rule it out if we can come up with a really good design.

@dummdidumm
Copy link
Member

Sounds related to the rfc past-you made sveltejs/rfcs#31

@TravisSpomer
Copy link
Contributor

It's easy to create endpoints, but it's not easy to use them, at least if you're trying to do static site stuff. (I'm way more interested in Next.js's getStaticProps than getServerSideProps.) If all you want to do is do some build-time transforms (like Markdown processing or something along those lines) you have to create an endpoint, do your work there, serialize it into JSON, then fetch it from the page that needs it and pull the data out, and then do something with it. And then that fetched data (say, the HTML from your Markdown) gets serialized into the page as a string as well so it can get rehydrated, so you have two full copies of it in your final output file.

It's all very roundabout and difficult to understand compared to Next.js, which was very simple even for a newbie web dev like me. (And for me personally, the ability to easily write endpoints in JavaScript isn't really a plus anyway since anything that I don't want to happen at build time I'd rather write in C#.)

@Rich-Harris
Copy link
Member Author

Yep, that's fair. One point — this...

that fetched data (say, the HTML from your Markdown) gets serialized into the page as a string as well so it can get rehydrated, so you have two full copies of it in your final output file.

...would still be true, since the hydrating code needs the data and can't infer it from the markup. But since duplicated content compresses nicely, it shouldn't have too big an impact on page weight.

@TravisSpomer
Copy link
Contributor

Right; I think to fully solve that problem, I'd need to be able to mark some component as "I promise this will never change, so just render it once and then don't treat it as a component at all after that point," which sounds simple in theory but is almost certainly complex to implement. :) That's a tangent—but definitely that's the thing that would be most useful to someone like me who's more focused on content-heavy static sites.

@dummdidumm
Copy link
Member

Isn't this what export const hydrate = false would do?

@Rich-Harris
Copy link
Member Author

Not exactly — firstly there's a bug (#823), but secondly that will also prevent any interactivity at all on the page, which might coincide with your requirements but often won't. Partial hydration needs solving at a lower level (i.e. Svelte) if we want it in SvelteKit

@Nick-Mazuk
Copy link
Contributor

@Rich-Harris, are there any technical reasons to…

  1. Have a single load function vs. getStaticProps and getServerSideProps?
  2. Have the load function also run on the browser?

As for #2, it seems unnecessary to have a load function which also runs in the browser. Both the data is already included in the output and if you really want to fetch data in the browser, you can do it inside a normal script tag. So from a conceptual standpoint, it seems redundant to not make it server-only.

I do realize there may be valid reasons for the current system that I'm just not seeing.


Here's an idea: since the output from load needs to be serialized in the browser for rehydration, would it be possible to just replace the function in the bundle step to remove server-side logic? So this:

<script type='module'>
  export const load = async () => {
    const response = await fetch('/data')
    const json = await response.json()

    return {
      props: { json }
    }
  }
</script>

With this in the browser-side code:

<script type='module'>
  export const load = async () => {
    return {
      props: { json: JSON.parse(serializedJSON) }
    }
  }
</script>

Effectively eliminating all sensitive implementation details and letting everything else still work as expected.

@Rich-Harris
Copy link
Member Author

@Nick-Mazuk the output from load isn't serialized; only the fetch responses are. This was a deliberate decision (and a change from Sapper), made for two reasons in particular:

  1. It's not uncommon to load data over the wire in a compressed format, then decompress it in the client. If you serialize outputs rather than inputs then you have to be very careful to do the decompression in a reactive block in the component, and not in the load function; if you lack the expertise to do so then you could end up inlining very large quantities of data on the page. (This isn't hypothetical, I've seen it happen)
  2. Not everything can be serialized. For example one of your props might be a component constructor, intended for use with <svelte:component> — such things are impossible if your load function only runs on the server

Beyond that there are good reasons to run load in the client. If you look at hn.svelte.dev (which is a Sapper app, for now, though there's a SvelteKit rewrite in the examples folder in this repo) and inspect the network tab, you'll notice that when you navigate to a new page, the data is coming from https://api.hnpwa.com, not from the app itself. This results in better performance (the user gets data in one hop — API to browser — rather than getting it via a redundant proxy), saves us from having to worry about faithfully recreating the underlying cache headers, and is cheaper because we're serving fewer requests. Better for users, better for the people paying the AWS/whatever bills.

@Rich-Harris Rich-Harris changed the title Docs are misleading about load Auto-generated endpoints from component code Apr 2, 2021
@iamdimitar
Copy link

Not exactly related, but running find .|grep -e "\.json.ts$" in the terminal gives me a neat view of all my routes. No details, or components' magic, but is obviously quick and easy.

@Rich-Harris
Copy link
Member Author

closing in favour of #3532

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

6 participants