Search code examples
amazon-s3sveltesapper

How should I structure a Sapper/Svelte route when the data is loaded from S3?


Let's say I have a bunch of data docs that are JSON docs in S3, one per year. I have a route defined in Sapper via dir structure:

├── routes
│   ├── _error.svelte
│   ├── _layout.svelte
│   ├── about.svelte
│   ├── data
│   │   ├── [year].svelte

And the code in my script block:

  let yearData;
  onMount(async () => {
    const f = await fetch(yearDataUrl(year), {
      headers: { 'Access-Control-Allow-Origin': '*' }
    });
    const jsonResults = await f.json();
    yearData = jsonResults;
  });

Each data set is in S3 as [s3 url]/[year here].json. There are a set of links in the nav bar that show each data set. I can get the data to load in an onMount fine, but subsequent clicks on the nav bar links don't load new data. So onMount is probably the wrong choice.

How should I structure this? Also worth noting that I'd like to render these pages as static files down the road (the data changes rarely, especially for the previous years).


Solution

  • There are two options. The first and most idiomatic — and correct, in your situation, since it sounds like you want to use server-side rendering — is to use preload:

    <script context="module">
      export async function preload(page) {
        const f = await this.fetch(yearDataUrl(page.params.year), {
          headers: { 'Access-Control-Allow-Origin': '*' }
        });
    
        return {
          yearData: await f.json()
        };
      }
    </script>
    
    <script>
      export let yearData;
    </script>
    
    <!-- use yearData here -->
    

    The preload function will run before the component is created, supplying it with the yearData prop. Whenever page.params changes, preload will run again, setting a new prop.

    Because this works on the server as well as on the client (hence the use of this.fetch rather than fetch, which works in both environments) you will get server-rendered pages, rather than a loading message that flickers into a page as soon as onMount runs.

    The second option, included for the sake of completeness, is to use the page store:

    <script>
      import { stores } from '@sapper/app';
    
      const { page } = stores();
    
      let yearData;
    
      $: if (process.browser) {
          fetch(yearDataUrl($page.params.year))
            .then(f => f.json())
            .then(data => {
              yearData = data;
            });
      }
    </script>
    

    In this case yearData won't be server-rendered. (To be complete, you would also need to handle race conditions and errors, which aren't an issue with preload.)