Search code examples
sveltesappersvelte-3

Send data from one page to another instead of preloading?


Say I have a blog route that loads a complete array of all blog posts. The individual blog posts live at blog/[postId]. Is there a Sapper-idiomatic way to pass the data for the individual post from blog to blog/[postId]?

Essentially, if you're on blog I'd like to preload the code for displaying blog/[postId]. And then when you click a link to blog/[postId], navigate there instantly and display data from blog. But of course if you navigate directly to blog/[postId] then preload() should still be called.

prefetch doesn't quite do it because that still requires a network request. I also tried a preload that checks a store and doesn't issue the network request unless its empty, but you can't use stores in <script context="module">.


Solution

  • Thanks @three for your comment which led me to the answer.

    If a route or _layout component exports preload(), it always gets called on navigation. So I ended up doing this by storing the fetched data in the second parameter to preload(), which is conventionally called session and appears to be shared among all components. And I only called this.fetch() if the data was not already present.

    <!-- blog/index.svelte -->
    <script context="module">
      export async function preload (page, session) {
        let {posts} = session;
        if (undefined === posts) {
          const response = await this.fetch('blog/posts.json');
          posts = await response.json();
          session.posts = posts
        }
    
        return {posts}
      }
    </script>
    
    <script>
      export let posts;
    </script>
    
    {#each posts as post}
      <!-- ... -->
    {/each}
    

    Then in [slug].svelte check session.posts for the post you're looking for, and if it isn't present, load it.

    <!-- blog/[slug].svelte -->
    <script context="module">
      export async function preload (page, session) {
        const {slug} = page.params
        const {posts} = session
        let post
    
        if (undefined === posts) {
          const response = await this.fetch(`blog/${slug}.json`)
          post = await response.json()
        } else {
          post = posts.find(p => p.slug === slug)
        }
    
        if (undefined === post) {
          this.error(404, "Sorry we couldn\'t find that post")
        }
    
        return {post}
      }
    </script>
    
    <script>
      export let post
    </script>
    
    <!-- ... render post -->
    

    Now navigation is instantaneous. For ultimate robustness you could add checks to reload if the data were stale (say older than 5 minutes, whatever).

    Edit

    You don't actually need to be using sessions, per se. If you just want to make the session parameter to preload(page, session) not be undefined you can use this in server.js:

    sapper.middleware({
      // Define session parameter as an empty object
      session: (req, res) => ({})
    })