SvelteKit and the Content Layer

Modern Frontends Live - 17 November 2022

SCOTT SPENCE

Developer Relations Engineer

Scott Spence

  • Developer Relations Engineer @Storyblok
  • Svelte LDN meetup organise
  • Working with Svelte workshops
  • Cat dad 😺

History Lesson

The Past

CMS

How it started

Traditional CMS

  • Website based

  • Theme based

  • Server rendered

  • Tightly coupled

  • You pay for everything

  • Often slow

The Present

Terminology

Buzzwords

Hype

Tangent

Serverless

  • You're not making calls to a specific server

  • You're making calls to an API

  • The API abstracts away the server

  • Guaranteeing uptime, reliability and scaleability  

Headless

  • You're not making calls to a specific server

  • You're making calls to an API

  • The API abstracts away the server

  • Guaranteeing uptime, reliability and scaleability  

Headless = Serverless?

Serverless functions enable front-end developers to add powerful "back-end" logic to our apps just by writing JavaScript — no devops, no servers, just results.

— Jason Lengstorf

The thing to focus on with headless is that it abstracts away the view, leaving development teams to focus on using their preferred tech stack

— Scott Spence

Modern Frontends Live

The Present

Today:
Omnichannel experience

Headless CMS

  • Frontend agnostic

  • Decoupled

  • Developer flexibility

  • CI/CD workflows with Git 

Visual Editor

Visually create, edit and publish content in real-time with Storyblok’s Visual Editor.

Let content creators, marketers and editor, see their changes in real time

Experience your content in different viewports without leaving the app

Publicly available preview link available for external stakeholders

Collaboration made simple: leave comments, discuss and share feedback.

The Future

GraphQL

  • Simple to understand

  • You get what you ask for

  • Client (browser) or server side

query {
  products {
    # from Storyblok
    name
    description {
      richtext
    }
    # from 3rd party API
    inventory {
      inStock
    }
    # rest of query
  }
}

Svelte.

SvelteKit.

What is Svelte?

Declarative code

Declarative code

Imperative code

vs

function component() {
  let count = 0;

  const button = document.createElement('button');
  button.textContent = `Clicks: ${count}`;

  button.addEventListener('click', () => {
    count += 1;
    button.textContent = `Clicks: ${count}`;
});
  return button;
}
<script>
  let count = 0;
</script>

<button on:click={() => count += 1}>
  Clicks: {count}
</button>

HTML

HTML Superset

Why Svelte?

Awesome DX

(Developer Experience)

Businesses don't really care about “Developer Experience”

The business does care about outcomes

 

Outcomes are the business experience

Svelte is more succinct (clear, precise) than other frameworks

Improved parsing of the code by other developers

Lead times reduced by 40%

Bug fixes are reduced

But the business doesn't care about any of that

Solving bugs faster = reduced lead time == saving budget

Businesses get that

It’s smaller

It’s smaller

  • Speed

    • Smaller bundle
  • Devices

    • Works on lower quality devices
  • Performance

    • Faster time to interactive

How users interact with your site

and it will impact rankings

Clickthrough Rates

Time spent on page

Return rates

Bounce Rates & Conversions

where it really matters

💷 💳 💸 🤑

How fast is fast enough?

2 seconds ⏱

Performance Matters

Client side

(the browser)

URQL

<script>
  import { createClient, setContextClient } from '@urql/svelte'
  import '../global.css'

  const client = createClient({
    url: `https://rickandmortyapi.com/graphql`,
  })
  setContextClient(client)
</script>

<main class="container">
  <slot />
</main>

<style>
  /* layout styles go here */
</style>

src/routes/+layout.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

<style>
 /* page styles go here */
</style>

src/routes/+page.svelte

src/routes/+page.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>
<pre>{JSON.stringify($charactersQueryStore, null, 2)}</pre>

SvelteKit debug 👀

{
  "characters": {
    "results": [
      {
        "name": "Rick Sanchez",
        "id": "1",
        "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg",
        "__typename": "Character"
      },
      {
        "name": "Morty Smith",
        "id": "2",
        "image": "https://rickandmortyapi.com/api/character/avatar/2.jpeg",
        "__typename": "Character"
      },
      {
        "name": "Summer Smith",
        "id": "3",
        "image": "https://rickandmortyapi.com/api/character/avatar/3.jpeg",
        "__typename": "Character"
      },
    ],
    "__typename": "Characters"
  }
}

src/routes/+page.svelte

<script>
  import { getContextClient, gql, queryStore } from '@urql/svelte'

  const charactersQueryStore = queryStore({
    client: getContextClient(),
    query: gql`
      query AllCharacters {
        characters {
          results {
            name
            id
            image
          }
        }
      }
    `,
  })
</script>

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

<style>
 /* page styles go here */
</style>

src/routes/+page.svelte

src/routes/+page.svelte

<div class="wrapper">
  {#if $charactersQueryStore.fetching}
    <p>Loading...</p>
  {:else if $charactersQueryStore.error}
    <p>Oopsie! {$charactersQueryStore.error.message}</p>
  {:else}
    {#each $charactersQueryStore.data.characters.results as character}
      <section>
        <a data-sveltekit-prefetch href={`/character/${character?.id}`}>
          <img src={character?.image} alt={character?.name} />
          <h2>{character?.name}</h2>
        </a>
      </section>
    {/each}
  {/if}
</div>

Closing Comments

  • Great client side tools to use

  • Awesome developer experience (DX)

  • Decrease in lead time for development

  • Small bundle sizes

  • Increased performance

Thanks 🙏