Nils Röhrig | REWE digital
What is Edge Computing?
What use does Edge Computing have in web development?
What are SvelteKit & Cloudflare Pages?
How could an app be built with these tools?
Edge computing is computing that takes place at or near the physical location of either the user or the source of the data.
Core
End user
Core
Service provider edge
End user
Core
End-user premises edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
End-user premises edge
Device edge
Service provider edge
End user
Core
CDN
Core
Web Frontend
Web Frontend
Web Frontend
Service Provider Edge
Edge Node
Parts of
Application
Edge Node
Parts of
Application
Edge Node
Parts of
Application
Core
SvelteKit is a framework for building extremely high-performance web apps.
Cloudflare Pages is a JAMstack platform for frontend developers to collaborate and deploy websites.
CDN
deploys to
-Providers
CDN
integrates with
deploys to
-Providers
CDN
integrates with
deploys to
uses
-Providers
CDN
integrates with
deploys to
uses
runs code at
1. Create a GitHub repository
1. Create a GitHub repository
2. Create a Cloudflare Pages project from repository
1. Create a GitHub repository
3. Create a SvelteKit app
2. Create a Cloudflare Pages project from repository
3. Create a SvelteKit app
4. Push app to repository
1. Create a GitHub repository
2. Create a Cloudflare Pages project from repository
src/routes
src/routes/name/[param]
src/routes/products/[id]
<script lang="ts">
export let data: PageData;
let product: Product, category: Category | string | undefined;
$: ({ product } = data);
$: ({ category } = product);
</script>
<article>
<Card>
<Section first>
<img class="image" alt="" src="/products/{product.filename}"/>
</Section>
</Card>
<div class="info">
{#if category && typeof category !== 'string'}
<Badge>{category.name}</Badge>
{/if}
<h2 class="title">{product.brand} {product.name}</h2>
<p class="short-description">{product.shortDescription}</p>
<div class="price">{formatPrice(product.price)}</div>
<AddToCart {product}/>
<div class="description">
<p class="origin">Country of origin: {product.origin}</p>
{@html product.description}
</div>
</div>
</article>
const converter = new showdown.Converter();
export const load: PageServerLoad = async ({ params, platform }) => {
const productService = createProductService(platform);
const categoryService = createCategoryService(platform);
const product = await productService
.getSingleProduct(params.id)
.then((option) => option.getOrThrow(error(404)));
const { description, category: categoryId, ...rest } = product;
const category = await categoryService
.getSingleCategory(categoryId)
.then((category) => category.getOrUndefined());
return {
product: {
...rest,
category,
description: converter.makeHtml(product.description),
},
};
};
+page.svelte
+page.server.ts
src/routes/products/[id]
<script lang="ts">
export let data: PageData;
let product: Product, category: Category | string | undefined;
$: ({ product } = data);
$: ({ category } = product);
</script>
<article>
<Card>
<Section first>
<img class="image" alt="" src="/products/{product.filename}"/>
</Section>
</Card>
<div class="info">
{#if category && typeof category !== 'string'}
<Badge>{category.name}</Badge>
{/if}
<h2 class="title">{product.brand} {product.name}</h2>
<p class="short-description">{product.shortDescription}</p>
<div class="price">{formatPrice(product.price)}</div>
<AddToCart {product}/>
<div class="description">
<p class="origin">Country of origin: {product.origin}</p>
{@html product.description}
</div>
</div>
</article>
const converter = new showdown.Converter();
export const load: PageServerLoad = async ({ params, platform }) => {
const productService = createProductService(platform);
const categoryService = createCategoryService(platform);
const product = await productService
.getSingleProduct(params.id)
.then((option) => option.getOrThrow(error(404)));
const { description, category: categoryId, ...rest } = product;
const category = await categoryService
.getSingleCategory(categoryId)
.then((category) => category.getOrUndefined());
return {
product: {
...rest,
category,
description: converter.makeHtml(product.description),
},
};
};
+page.svelte
+page.server.ts
src/routes/products/[id]
<script lang="ts">
export let data: PageData;
let product: Product, category: Category | string | undefined;
$: ({ product } = data);
$: ({ category } = product);
</script>
<article>
<Card>
<Section first>
<img class="image" alt="" src="/products/{product.filename}"/>
</Section>
</Card>
<div class="info">
{#if category && typeof category !== 'string'}
<Badge>{category.name}</Badge>
{/if}
<h2 class="title">{product.brand} {product.name}</h2>
<p class="short-description">{product.shortDescription}</p>
<div class="price">{formatPrice(product.price)}</div>
<AddToCart {product}/>
<div class="description">
<p class="origin">Country of origin: {product.origin}</p>
{@html product.description}
</div>
</div>
</article>
const converter = new showdown.Converter();
export const load: PageServerLoad = async ({ params, platform }) => {
const productService = createProductService(platform);
const categoryService = createCategoryService(platform);
const product = await productService
.getSingleProduct(params.id)
.then((option) => option.getOrThrow(error(404)));
const { description, category: categoryId, ...rest } = product;
const category = await categoryService
.getSingleCategory(categoryId)
.then((category) => category.getOrUndefined());
return {
product: {
...rest,
category,
description: converter.makeHtml(product.description),
},
};
};
+page.svelte
+page.server.ts
src/routes/products/[id]
const converter = new showdown.Converter();
export const load: PageServerLoad = async ({ params, platform }) => {
const productService = createProductService(platform);
const categoryService = createCategoryService(platform);
const product = await productService
.getSingleProduct(params.id)
.then((option) => option.getOrThrow(error(404)));
const { description, category: categoryId, ...rest } = product;
const category = await categoryService
.getSingleCategory(categoryId)
.then((category) => category.getOrUndefined());
return {
product: {
...rest,
category,
description: converter.makeHtml(product.description),
},
};
};
+page.svelte
+page.server.ts
<script lang="ts">
export let data: PageData;
let product: Product, category: Category | string | undefined;
$: ({ product } = data);
$: ({ category } = product);
</script>
<article>
<Card>
<Section first>
<img class="image" alt="" src="/products/{product.filename}"/>
</Section>
</Card>
<div class="info">
{#if category && typeof category !== 'string'}
<Badge>{category.name}</Badge>
{/if}
<h2 class="title">{product.brand} {product.name}</h2>
<p class="short-description">{product.shortDescription}</p>
<div class="price">{formatPrice(product.price)}</div>
<AddToCart {product}/>
<div class="description">
<p class="origin">Country of origin: {product.origin}</p>
{@html product.description}
</div>
</div>
</article>
src/routes/products/[id]
const converter = new showdown.Converter();
export const load: PageServerLoad = async ({ params, platform }) => {
const productService = createProductService(platform);
const categoryService = createCategoryService(platform);
const product = await productService
.getSingleProduct(params.id)
.then((option) => option.getOrThrow(error(404)));
const { description, category: categoryId, ...rest } = product;
const category = await categoryService
.getSingleCategory(categoryId)
.then((category) => category.getOrUndefined());
return {
product: {
...rest,
category,
description: converter.makeHtml(product.description),
},
};
};
+page.svelte
+page.server.ts
<script lang="ts">
export let data: PageData;
let product: Product, category: Category | string | undefined;
$: ({ product } = data);
$: ({ category } = product);
</script>
<article>
<Card>
<Section first>
<img class="image" alt="" src="/products/{product.filename}"/>
</Section>
</Card>
<div class="info">
{#if category && typeof category !== 'string'}
<Badge>{category.name}</Badge>
{/if}
<h2 class="title">{product.brand} {product.name}</h2>
<p class="short-description">{product.shortDescription}</p>
<div class="price">{formatPrice(product.price)}</div>
<AddToCart {product}/>
<div class="description">
<p class="origin">Country of origin: {product.origin}</p>
{@html product.description}
</div>
</div>
</article>
src/routes/products/[id]
Worker Code </>
Worker Code </>
uses
Runtime API
list()
get()
put()
delete()
Worker Code </>
Runtime API
list()
get()
put()
delete()
uses
updates
Edge Nodes
KV Cache
KV Cache
KV Cache
Worker Code </>
Runtime API
list()
get()
put()
delete()
uses
updates
updates
Edge Nodes
KV Cache
KV Cache
KV Cache
Worker Code </>
Edge Nodes
KV Cache
KV Cache
KV Cache
Runtime API
list()
get()
put()
delete()
uses
replicates
replicates
updates
updates
interface Product {
id: string;
name: string;
shortDescription: string;
description: string;
price: number;
brand: string;
origin: string;
category: Category | string | undefined;
filename: string;
}
export function createProductService(platform: App.Platform) {
const store = platform.env.PRODUCTS;
return {
getProductsByCategory(category: Category): Promise<Product[]> {
return store
.list()
.then(prop('keys'))
.then(map(prop('name'))).
.then(keyNames => Promise.all(
map(keyName => store.get<Product>(keyName, { type: 'json'}))
))
.then(filter(propEq('category', category.id)))
}
};
}
Product.ts
ProductService.ts
interface Product {
id: string;
name: string;
shortDescription: string;
description: string;
price: number;
brand: string;
origin: string;
category: Category | string | undefined;
filename: string;
}
export function createProductService(platform: App.Platform) {
const store = platform.env.PRODUCTS;
return {
getProductsByCategory(category: Category): Promise<Product[]> {
return store
.list()
.then(prop('keys'))
.then(map(prop('name'))).
.then(keyNames => Promise.all(
map(keyName => store.get<Product>(keyName, { type: 'json'}))
))
.then(filter(propEq('category', category.id)))
}
};
}
Product.ts
ProductService.ts
interface Product {
id: string;
name: string;
shortDescription: string;
description: string;
price: number;
brand: string;
origin: string;
category: Category | string | undefined;
filename: string;
}
export function createProductService(platform: App.Platform) {
const store = platform.env.PRODUCTS;
return {
getProductsByCategory(category: Category): Promise<Product[]> {
return store
.list()
.then(prop('keys'))
.then(map(prop('name'))).
.then(keyNames => Promise.all(
map(keyName => store.get<Product>(keyName, { type: 'json'}))
))
.then(filter(propEq('category', category.id)))
}
};
}
Product.ts
ProductService.ts
application
Target platforms
application
Target platforms
Platform-specific adapter
application
Target platforms
Platform-specific adapter
uses
application
Target platforms
Platform-specific adapter
uses
adapts to
adapter-cloudflare
adapter-cloudflare
svelte.config.js
app.d.ts
import adapter from '@sveltejs/adapter-cloudflare';
import preprocess from 'svelte-preprocess';
const config = {
preprocess: preprocess(),
kit: {
adapter: adapter(),
},
};
export default config;
/// <reference types="@sveltejs/adapter-cloudflare" />
declare namespace App {
interface Platform {
env?: {
PRODUCTS: KVNamespace;
CATEGORIES: KVNamespace;
CONTENT: KVNamespace;
};
}
}
svelte.config.js
app.d.ts
import adapter from '@sveltejs/adapter-cloudflare';
import preprocess from 'svelte-preprocess';
const config = {
preprocess: preprocess(),
kit: {
adapter: adapter(),
},
};
export default config;
/// <reference types="@sveltejs/adapter-cloudflare" />
declare namespace App {
interface Platform {
env?: {
PRODUCTS: KVNamespace;
CATEGORIES: KVNamespace;
CONTENT: KVNamespace;
};
}
}
adapter-cloudflare
svelte.config.js
app.d.ts
import adapter from '@sveltejs/adapter-cloudflare';
import preprocess from 'svelte-preprocess';
const config = {
preprocess: preprocess(),
kit: {
adapter: adapter(),
},
};
export default config;
/// <reference types="@sveltejs/adapter-cloudflare" />
declare namespace App {
interface Platform {
env?: {
PRODUCTS: KVNamespace;
CATEGORIES: KVNamespace;
CONTENT: KVNamespace;
};
}
}
adapter-cloudflare
Edge web apps run close to the user
Edge web apps run close to the user
Tools like SvelteKit can ease the development
Tools like SvelteKit can ease the development
Platforms like Cloudflare can ease deployment & execution
Edge web apps run close to the user
Twitter:
LinkedIn:
Xing:
Code:
Live:
Guide:
import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';
import { isNil } from 'ramda';
export const handle: Handle = async ({ event, resolve }) => {
if (dev && isNil(event.platform)) {
const { categoryStore, contentStore, productStore } = await import(
'./kv-mock'
);
event.platform = {
env: {
PRODUCTS: productStore,
CATEGORIES: categoryStore,
CONTENT: contentStore,
},
};
}
return resolve(event);
};
src/hooks/index.ts
src/hooks/kvMock.ts
import { KVNamespace } from '@miniflare/kv';
import { MemoryStorage } from '@miniflare/storage-memory';
export const productStore = new KVNamespace(new MemoryStorage());
export const categoryStore = new KVNamespace(new MemoryStorage());
export const contentStore = new KVNamespace(new MemoryStorage());
import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';
import { isNil } from 'ramda';
export const handle: Handle = async ({ event, resolve }) => {
if (dev && isNil(event.platform)) {
const { categoryStore, contentStore, productStore } = await import(
'./kv-mock'
);
event.platform = {
env: {
PRODUCTS: productStore,
CATEGORIES: categoryStore,
CONTENT: contentStore,
},
};
}
return resolve(event);
};
src/hooks/index.ts
src/hooks/kvMock.ts
import { KVNamespace } from '@miniflare/kv';
import { MemoryStorage } from '@miniflare/storage-memory';
export const productStore = new KVNamespace(new MemoryStorage());
export const categoryStore = new KVNamespace(new MemoryStorage());
export const contentStore = new KVNamespace(new MemoryStorage());
src/hooks/index.ts
src/hooks/kvMock.ts
import { KVNamespace } from '@miniflare/kv';
import { MemoryStorage } from '@miniflare/storage-memory';
export const productStore = new KVNamespace(new MemoryStorage());
export const categoryStore = new KVNamespace(new MemoryStorage());
export const contentStore = new KVNamespace(new MemoryStorage());
import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';
import { isNil } from 'ramda';
export const handle: Handle = async ({ event, resolve }) => {
if (dev && isNil(event.platform)) {
const { categoryStore, contentStore, productStore } = await import(
'./kv-mock'
);
event.platform = {
env: {
PRODUCTS: productStore,
CATEGORIES: categoryStore,
CONTENT: contentStore,
},
};
}
return resolve(event);
};
src/hooks/index.ts
src/hooks/kvMock.ts
import { KVNamespace } from '@miniflare/kv';
import { MemoryStorage } from '@miniflare/storage-memory';
export const productStore = new KVNamespace(new MemoryStorage());
export const categoryStore = new KVNamespace(new MemoryStorage());
export const contentStore = new KVNamespace(new MemoryStorage());
import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';
import { isNil } from 'ramda';
export const handle: Handle = async ({ event, resolve }) => {
if (dev && isNil(event.platform)) {
const { categoryStore, contentStore, productStore } = await import(
'./kv-mock'
);
event.platform = {
env: {
PRODUCTS: productStore,
CATEGORIES: categoryStore,
CONTENT: contentStore,
},
};
}
return resolve(event);
};