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
- 94