SvelteKit Infinite Scroll: Instagram API Tutorial
Tags: svelte, webdev, javascript
Published: 2021-10-25 11:06:38

---
title: "SvelteKit Infinite Scroll: Instagram API Tutorial"
published: "true"
description: "SvelteKit infinite scroll: see how you can use Svelte stores and a reactive function to implement an infinite scrolling Instagram feed."
tags: "svelte, webdev, javascript"
canonical_url: "https://rodneylab.com/sveltekit-infinite-scroll/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unmybtyvg61spuk2cr7m.png"
---

## 🖱 Infinite Scrolling Feeds in Svelte

Let's look at SvelteKit infinite scroll.  The Instagram app itself is the perfect example of an infinite scrolling feed.  There is potentially a large number of posts available and the app does not load them all initially; doing so would slow the page down, impacting user experience.  Instead, it loads a few posts and as the user scrolls down, it starts <a aria-label="Read more about best practises for embeds"  href="https://web.dev/embed-best-practices/">lazy loading</a> more posts.  Lazy loading is just a way of saying we load content on demand (or ideally, when we anticipate demand).

![SvelteKit Infinite Scroll: Screenshot: Image shows a website screenshot with six images from an Instagram feed show in a 3-column grid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wsbhml2wv0zw18rabin.png)

We will implement infinite scroll on a SvelteKit app, using images from your Instagram feed.  In doing so, we need a trigger for automatically loading more content.  For this we can <a aria-label="Read more about the Intersection Observer A P I in Svelte Kit" href="https://rodneylab.com/tracking-page-views-sveltekit/">use the Intersection Observer API</a>.  When the user scrolls down and the footer becomes visible we will get an observe event and load more content (where there are more posts available). As well as Intersection Observer, from the Svelte toolkit, we will be using a reactive function and stores.

We focus on an Instagram application for infinite scrolling in this article.  However, it is not too much effort to apply the techniques here to a blog roll on your site, feeds from other social sites like Twitter or for user interactions on a social app you are building.  If that sounds like something you might find useful then why don't we get cracking?


## 🔑 Instagram Access Token

We will focus in the SvelteKit side in the post, so that it doesn't get too long.  If you want to code along, you will need an Instagram access token.  There are currently two Instagram APIs.  Here we just want to get images from a particular user's feed and the Instagram Basic Display API matches ours needs.  Follow <a aria-label="Open Facebook's get started tutorial for the Instagram Basic Display A P I" href="https://developers.facebook.com/docs/instagram-basic-display-api/getting-started">Facebook's Get Started with Instagram Basic Display API</a> to get your access token.

![SvelteKit Infinite Scroll: Screenshot: Instagram A P I key: image is screenshot of an authorisation screen.  User can select level of authorisation and click Allow on Don't Allow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41jvrkmzs96bfpydgb71.png)

You will see as part of the tutorial, you will set up a test user.  Use your own Instagram account (or at least the one you want to extract the feed from).  Select the **Media (optional)** box to be able to pull the feed images in, when asked to authorise your account. Once you have an access token you can move on to the setting up the SvelteKit app.

A temporary access token is fine for a proof of concept, though if you want to pursue the product to production you will eventually need longer living tokens.

## ⚙️ Svelte Setup

We'll create a skeleton SvelteKit project and put this thing together from there.  To get going, type these commands in the terminal:

```shell
pnpm init svelte@next sveltekit-infinite-scroll && cd $_
pnpm install
pnpm install dotenv @fontsource/playfair-display
```
Select a skeleton project, answer no to Typescript and yes to both Prettier and ESLint.  We include the `dotenv` package (as well as a font) in our install so we can read our Instagram API key from a `.env` file.  Let's create that file:

```plaintext
INSTAGRAM_ACCESS_TOKEN=IGQVJ...
```

then include `dotenv` config in `svelte.config.js`:

```javascript
/** @type {import('@sveltejs/kit').Config} */
import 'dotenv/config';
const config = {
  kit: {
    // hydrate the <div id="svelte"> element in src/app.html
    target: '#svelte',
  },
};
 
export default config;
```

Then finally spin up a dev server:

```shell
pnpm run dev
```

## 🧱 SvelteKit Infinite Scroll: API Routes

Next we'll build a couple of API routes.  We will use these to query the Instagram API from the client.  First create `src/routes/api/instargram-feed` (you will need to create the `api` folder).  Add the following content:

```javascript
export async function get() {
  try {
    const url = `https://graph.instagram.com/me/media?fields=caption,id,media_type,media_url,timestamp&access_token=${process.env['INSTAGRAM_ACCESS_TOKEN']}`;
    const response = await fetch(url, {
      method: 'GET',
    });
 
    const data = await response.json();
 
    return {
      body: { ...data },
    };
  } catch (err) {
    console.log('Error: ', err);
    return {
      status: 500,
      error: 'Error retrieving data in /api.instagram-feed.json',
    };
  }
}
```

We will call this code by sending a `GET` request to `/api-instagram-feed.json` and it will just return the data it receives from Instagram, if all is well!  That response will be JSON and something like this:


```json
{
   "data": [
      {
         "id": "17924392726111111",
         "media_type": "IMAGE",
         "media_url": "https://scontent-lhr8-1.cdninstagram.com/v/iamge-url",
         "timestamp": "2021-10-18T11:09:59+0000"
      },
      {
         "id": "17924392726111112",
         "media_type": "IMAGE",
         "media_url": "https://scontent-lhr8-1.cdninstagram.com/v/iamge-url",
         "timestamp": "2021-10-18T11:09:50+0000"
      },
   ],
   "paging": {
      "cursors": {
         "before": "aaa",
         "after": "bbb"
      },
      "next": "https://graph.instagram.com/v12.0/link-for-next-page"
   }
}
```

There will be up to 25 posts (I just included two here).  Note the `paging` object includes a `next` link.  We will use this when we need to download more images. Lets code up the endpoint for that next.

### Pulling more Images from Instagram API

To get more images, we just need the `next` link included in the previous call.  Create an endpoint for pulling more images at `src/routes/api/instagram-feed-more.json.js` and add this content:

```javascript
export async function post(request) {
  try {
    const { next } = request.body;
    const response = await fetch(next, {
      method: 'GET',
    });
 
    const data = await response.json();
 
    return {
      body: { ...data },
    };
  } catch (err) {
    console.log('Error: ', err);
    return {
      status: 500,
      error: 'Error retrieving data in /api.instagram-feed-more.json',
    };
  }
}
```

We will access this endpoint using the `POST` method and include the `next` link in the API call body.

With our API routes now all set up, let's add one more piece of plumbing before we code up the client page.

## 🛍 Svelte Store

Initially, we will show six images, though we would have pulled up to 25 in the first API call.  The store helps us out here.  We put all the images we pulled from Instagram into the store and then (initially) show the first six.  As the user scrolls down, we will load more images from the store.  Eventually, it's possible the user will want more images than there are available in the store.  At that point we make a `more` Instagram call, returning up to 25 more images.  We append those new images onto the end of what's in the store already and we're away!

That probably sounded more complicated than Svelte actually makes it, but I wanted to run through the logic before we implement it.  As it happens, we only need three lines of JavaScript to set this store up in SvelteKit!  Create a file at `src/lib/shared/store/instagram.js` (you will need to create some folders).  Add these lines to the file:

```javascript
import { writable } from 'svelte/store';
 
const feed = writable([]);
 
export { feed as default };
```

In line `3`, we are initialising the store to an empty array. Let's add something now from the client.

## 🧑🏽 Client Side

We'll start with the load function.  In SvelteKit, load functions run before the initial render.  Here it makes sense to make the first Instagram call in the load function.  Replace the existing code in `src/routes/index.svelte`:

```svelte
<script context="module">
  export async function load({ fetch }) {
    try {
      const response = await fetch('/api/instagram-feed.json', {
        method: 'GET',
        credentials: 'same-origin',
      });
      return {
        props: { data: { ...(await response.json()) } },
      };
    } catch (error) {
      console.error(error);
    }
  }
</script>
```

You see we call the first API route we created, sending a `GET` request.

### Stocking up the Store

You might have noticed, we returned props from the load function in line `9`.  This makes the Instagram data available to our client side JavaScript, which we add next:

```svelte
<script>
  import instagram from '$lib/shared/stores/instagram';
  import { onMount } from 'svelte';
  import { browser } from '$app/env';
  import '@fontsource/playfair-display/400.css';
  import '@fontsource/playfair-display/700.css';
 
  export let data;
 
  const INITIAL_POSTS = 6;
 
  const { data: feed, paging } = data;
  let next = paging?.next ? paging.next : null;
  instagram.set(feed);
 
  let limit = INITIAL_POSTS;
 
  function morePostsAvailable() {
    return limit < $instagram.length || next;
  }
```

We have the feed posts available in the data prop, which we import (Svelte syntax is to use the `export` keyword here) in line 24.  We destructure the feed and then adding the data to the store is simply done in line `30` with `instagram.set(feed)`.  Could there be less boiler plate? 😅

I should mention, we imported the store in line `18`.  In line `35` you see an example of how we can access the store.  We just write `$instagram` and that gives us the array which we set the store to be.  In this line, we check how many elements are currently in the store array.

### Intersection Observer

Okay, next we want to be able to show more posts (if we have them) whenever the footer comes into view.  The Intersection Observer API is our friend here.  If this is your first time using it in Svelte, check out the post on <a aria-label="Read post on tracking page views with the Intersection Observer A P I" href="https://rodneylab.com/tracking-page-views-sveltekit/">tracking page views, where we look at Intersection Observer</a> in more detail.  Add this code to the bottom of `src/routes/index.svelte`:

```svelte
  onMount(() => {
    if (browser && document.getElementById('footer')) {
      const handleIntersect = (entries, observer) => {
        entries.forEach((entry) => {
          if (!morePostsAvailable()) {
            observer.unobserve(entry.target);
          }
          showMorePosts();
        });
      };
      const options = { threshold: 0.25, rootMargin: '-100% 0% 100%' };
      const observer = new IntersectionObserver(handleIntersect, options);
      observer.observe(document.getElementById('footer').lastElementChild);
    }
  });
 
  $: showMorePosts;
  async function showMorePosts() {
    try {
      const newLimit = limit + 6;
      if (newLimit <= $instagram.length) {
        // load more images from store
        limit = newLimit;
      } else if (next) {
        // get another page from Instagram if there is another page available
        const response = await fetch('/api/instagram-feed-more.json', {
          method: 'POST',
          credentials: 'same-origin',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ next: next.replace(/%2C/g, ',') }),
        });
        const newData = await response.json();
        const { data: newFeed, next: newNext } = newData;
        instagram.set([...$instagram, ...newFeed]);
        next = newNext ?? null;
        limit = newLimit;
      }
    } catch (error) {
      console.error('Error fetching more posts in index');
    }
  }
</script>
```

We will set the minimum page height so that the footer is initially out of view (in styles which we add in a moment).  Our Intersection Observer parameters will observe an intersection event when the user scrolls down and the footer becomes visible.  This will call the `showMorePosts` function.

`showMorePosts` is declared as a reactive function (in line `54`).  This is a hint to the Svelte compiler that the function changes some elements in the DOM and a refresh might be needed when it is finished.

In line `69`, we just make sure we replace URL encoded commas in the `next` string with actual commas.  Let me know if anything here needs more explanation and I can update the post. Let's actually render the content next.

### Client Rendered Markup

Paste this code at the bottom of `src/routes/index.svelte`:

```svelte
<svelte:head>
  <title>SvelteKit Infinite Feed Scroll</title>
  <html lang="en-GB" />
</svelte:head>

<header>SvelteKit Infinite Scroll</header>
 
<main class="container">
  <h1>Instagram Feed</h1>
  <section class="feed">
    {#each $instagram?.slice(0, limit) as { caption, media_url }, index}
      <article aria-posinset={index + 1} aria-setsize={$instagram.length} class="feed-image">
        <img
          class="lazy"
          alt={caption ? caption : 'Image from instagram feed'}
          loading="lazy"
          decoding="async"
          width="256"
          height="256"
          \src={media_url}
        />
      </article>{:else}
      No feed images yet!
    {/each}
  </section>
</main>
<footer id="footer">
  <small>Copyright (c) 2021 Rodney Lab. All Rights Reserved.</small>
</footer>
```

There's a few things worth mentioning here:

- in line `93` we just take the number of posts we want from the store, rather that the whole thing,
- we add an `id="footer"` attribute which is used above by the Intersection Observer code,
- I've just included the footer content in the example for the sake of the Intersection Observer code.


### SvelteKit Infinite Scroll: Styling

Here's some (mostly) optional styling, just paste it at the bottom of our file.  Be sure at least to set the `min-height` as in line `135`:

```svelte
<style>
  :global(html) {
    font-family: 'Playfair Display';
    background: #e1306c;
  }
  :global(body) {
    margin: 0;
  }
 
  header {
    color: #ffdc80;
    max-width: 768rem;
    padding: 1.5rem;
    font-size: 3.052rem;
    font-weight: 700;
  }
  h1 {
    color: #ffdc80;
    font-size: 3.815rem;
    text-align: center;
  }
  .container {
    min-height: 100vh;
  }
 
  .feed {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: auto;
    row-gap: 0;
    max-width: 768px;
    margin: 3rem auto;
    width: 100%;
    height: auto;
  }
 
  .feed img {
    width: 100%;
    height: 100%;
  }
  .feed-image {
    width: 100%;
    height: 100%;
  }
 
  footer {
    background: #833ab4;
    color: #fff;
    text-align: center;
    padding: 1rem;
  }
 
  @media (max-width: 768px) {
    .feed {
      padding: 0 1.5rem;
      width: 100%;
    }
  }
</style>
```

## 💯  SvelteKit Infinite Scroll: Testing

That's it.  Give your browser a refresh and get scrolling!  If your internet connection is fast you might not notice more images loading.  Keen an eye on the vertical scroll bar though and you will see it jumps as more content (off screen) loads.

## 🙌🏽 SvelteKit Infinite Scroll: What we Learned

In this post we learned:
- using the Instagram API to fetch a user's posts,

- how you can use store in Svelte to buffer content received from an external feed,

- combining the Intersection Observer API with Svelte stores for a seamless user experience.

I do hope there is at least one thing in this article which you can use in your work or a side project.  For extensions, you could add a Twitter or try adapting the code to take Instagram Video posts as well as images.  Alternatively simply use the code to create an infinite feed of your blog posts.  The sky is the limit, you can really go to town on this!

As always get in touch with feedback if I have missed a trick somewhere!  You can see the <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/sveltekit-instagram-infinite-scroll">full code for this SvelteKit Instagram Infinite Scroll tutorial on the Rodney Lab Git Hub repo</a>.

## 🙏🏽 SvelteKit Infinite Scroll: Feedback

Have you found the post useful? Do you have your own methods for solving this problem? Let me know your solution. Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.