Skip to content

Remove $page.stuff and allow load functions to run concurrently #4911

Closed
@Rich-Harris

Description

@Rich-Harris

Describe the problem

I've come to the view that stuff (as it's currently designed) is a mistake, because it forces nested routes to load serially and because it prevents us from taking advantage of nested routes to stream responses (see #4910).

Ideally if we had a route like this...

<!-- src/routes/__layout.svelte -->
<script context="module">
  export async function load({ fetch }) {
    const res = await fetch('/one.json');
    const { one } = await res.json();

    return {
      props: { one }
    };
  }
</script>

<script>
  export let one;
</script>

<h1>{one}</h1>
<div>
  <slot/>
</div>
<!-- src/routes/foo/__layout.svelte -->
<script context="module">
  export async function load({ fetch }) {
    const res = await fetch('/two.json');
    const { two } = await res.json();

    return {
      props: { two }
    };
  }
</script>

<script>
  export let two;
</script>

<h2>{two}</h2>
<div>
  <slot/>
</div>
<!-- src/routes/foo/bar.svelte -->
<script context="module">
  export async function load({ fetch }) {
    const res = await fetch('/three.json');
    const { three } = await res.json();

    return {
      props: { three }
    };
  }
</script>

<script>
  export let three;
</script>

<p>{three}</p>

...then two things would happen. Firstly, we'd fetch one.json, two.json and three.json concurrently. Secondly, as soon as one.json returned, we'd flush

<h1>1</h1>
<div>

then as soon as two.json returned, we'd flush

<h2>2</h2>
<div>

then as soon as three.json returned, we'd flush the remainder:

<p>3</p>
</div>
</div>

At present, that's not possible. We run load functions in sequence for two reasons...

  1. we want to bail out early if a layout errors or causes a redirect — subsequent load functions should not run
  2. a nested route should be able to access any stuff that was returned from an rootward layout

...and we can't render anything until everything has loaded, because the root layout might make use of $page.stuff.

Ideally, we'd be able to solve all these problems without relying on a design that forces serial loading and delayed rendering.

Describe the proposed solution

Honestly, I haven't really figured this out yet — I'm really just trying to articulate the problem in hopes that a solution will present itself. But here's a straw man:

<script context="module">
  export async function load({ fetch, parent }) {
    const stuff = await parent(); // merges stuff from all parent layouts

    return {
      stuff: {
        b: stuff.a + 1
      },
      props: (stuff) => ({
        // because `props` is a function, rendering is
        // delayed until we have the final `stuff` object
        title: stuff.title
      })
    };
  }
</script>

<script>
  // this comes from the leaf component
  export let title;
</script>

<svelte:head>
  <title>{title}</title>
</svelte:head>

This doesn't feel totally intuitive, but it would speed up performance in the common case where load isn't blocked on its parents, and $page.stuff isn't used. And we would still have the ability to bail out if a parent load redirects (maybe await parent() throws an error for non-200 responses?)

Alternatives considered

I am all ears. (I'm also interested to know how people are using stuff)

Importance

would make my life easier

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions