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 ๐

SvelteKit and the Content Layer
By Scott Spence
SvelteKit and the Content Layer
- 174