Nils Röhrig | Loql
Was ist Edge Computing?
Was bringt Edge Computing in der Web-Entwicklung?
Was sind SvelteKit & Cloudflare Pages?
Wie kann eine App mit diesen Tools aussehen?
Edge Computing ist Computing, das nahe am physischen Standort der Nutzerinnen und Nutzer oder der Datenquelle stattfindet.
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
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. GitHub-Repository erstellen
1. GitHub-Repository erstellen
2. Cloudflare Pages-Projekt aus Repository erstellen
1. GitHub-Repository erstellen
3. SvelteKit-App erstellen
2. Cloudflare Pages-Projekt aus Repository erstellen
3. SvelteKit-App erstellen
4. App ins Repository pushen
1. GitHub-Repository erstellen
2. Cloudflare Pages-Projekt aus Repository erstellen
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
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>
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 laufen in der Nähe des Nutzers
Edge-Web-Apps laufen in der Nähe des Nutzers
Tools wie SvelteKit vereinfachen die Entwicklung
Tools wie SvelteKit vereinfachen die Entwicklung
Plattformen wie Cloudflare vereinfachen das Deployment & die Ausführung
Edge-Web-Apps laufen in der Nähe des Nutzers
Code:
Live:
Guide:
Twitter:
LinkedIn:
Xing:
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);
};