Teleport data from server to client in universal `load` functions
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:
- It allows you to return different data between server and browser, in cases where that's desirable
- 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)
- 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,
teleportwould just befn => 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,reuseetc - key-first, key-second, key-optional
Importance
nice to have
Additional Information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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