sveltejs / sveltejs/kit

Teleport data from server to client in universal `load` functions

Open
#9,160 14 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
20.8k
Forks
2.3k
Avg merge
1d 16h
Merged PRs (30d)
156

Description

Describe the problem

One point of confusion with load functions is that universal loads run during SSR and upon hydration (and then for subsequent client-side navigations). This is most visible if you return something non-deterministic:

// src/routes/+page.js
export function load() {
  return {
    random: Math.random()
  };
}

The value of data.random inside +page.svelte will differ between the server-rendered HTML and the hydrated document. If it were a +page.server.js instead, the result of calling the load function would be serialized, and would therefore be consistent.

There are good reasons for re-running universal load functions upon hydration:

  1. It allows you to return different data between server and browser, in cases where that's desirable
  2. It means we don't need to serialize the output, which is often larger than the input (for example, on a project at the NYT we were doing statistical analysis of some JSON data. The input was large, in the 100s of kbs, but the output was huge — megabytes of moving averages and so on)
  3. We can return non-serializable objects such as components and stores, enabling advanced whizzbangery

(Note that event.fetch calls are not repeated — the responses are serialized into, and read from, the HTML.)

Despite that, it would be useful to have a mechanism to avoid re-running code in cases where you just want to use the same value between server and client, and are happy with the serialization constraints.

Describe the proposed solution

I propose a new event.teleport helper:

export function load({ teleport }) {
  const random = teleport(() => Math.random());
  return {
    random
  };
}
  • During SSR, the result of calling the callback would be serialized (using the same mechanisms we use for server load functions) and assigned to an automatically-generated ID
  • During hydration, teleport(...) would just return the data associated with the automatically-generated ID
  • During client-side navigation, teleport would just be fn => fn()

Automatically generating IDs requires that teleport be called synchronously inside the load body (and not be inside if blocks etc — in other words, hooks rules). In some circumstances that might be untenable, so users could specify a key (if a key is reused, we would throw an error):

// either this...
const random = teleport('random', () => Math.random());

// ...or this:
const random = teleport(() => Math.random(), 'random');

Bikeshedding alert

If the key is optional, having it be the second argument would be more logical. But having it be the first argument would result in neater code:

// prettier prefers string-first-function-second...
const object = teleport('object', () => ({
  answer: 42
}));

// ...to string-second-function-first:
const object = teleport(
  () => ({
    answer: 42
  }),
  'object'
)

Reusing entire load functions

If you wanted to, you could easily reuse the entire function body:

export const load = (event) => {
  return event.teleport(() => ({
    stuff: get_stuff(event.params.stuff),
    more_stuff: get_more_stuff(event.params.stuff),
  }));
};

Promises

We could use the same promise serialization mechanism we currently use:

export async function load({ teleport }) {
  const randomized = await teleport(async () => {
    const response = await fetch('https://api.example.com/things');
    const { things } = await response.json();
    return randomize(things);
  });

  return {
   randomized
  };
}
Alternatives considered
  • different name — keep, sticky, reuse etc
  • key-first, key-second, key-optional
Importance

nice to have

Additional Information

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the universal load example in src/routes/+page.js and compare it with the serialized result from src/routes/+page.server.js. Trace the existing server-load serialization and hydration behavior, then assess how event.teleport would preserve values during hydration while executing callbacks during SSR and client-side navigation. Done means the proposed API and its serialization, key, synchronous-call, and promise behaviors are defined and covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.