SKIFFLE is a framework that lets you embed a website in a Farcaster snap. A snapsite, if you will.
Navigate the snap just like you would click links on a website. Share any sub-URL back to the feed, and it still functions as an entry point to the snapsite no matter how deeply nested you are.
Webpage contents also render directly to snap images, so devs (and agents) can leverage the familiar HTML templating, CSS styling, param-based state and hierarchical data loading techniques of a modern route-based web framework.
- Live demo: https://snap.skiffle.dev
SKIFFLE works by extending SvelteKit with:
- an
HTML/CSS -> SVG -> PNGrender pipeline (satori+resvg) - middleware-based content negotiation for Snap JSON, HTML, and raster image variants
- relative URL navigation handlers for snap clients
- adapters that bridge Snap POST input into SvelteKit Form Actions
- TypeScript helpers for building Snap elements and full Snap JSON payloads
The most crucial code path is in src/hooks.server.ts plus src/lib/snap-routes.ts: page URLs are overloaded and resolved through normal SvelteKit routing, while output modality is selected via HTTP Accept headers and related request context.
SKIFFLE treats SvelteKit routes as the canonical source of truth, then projects that same route into:
- interactive web HTML
- shareable, navigable snap entry points
- server-rendered image previews
That keeps state, data loading, route hierarchy, and navigation semantics unified while still satisfying Farcaster frame/snap protocol requirements.
Given a single SvelteKit route URL, SKIFFLE can produce different representations:
- HTML: default browser rendering
- Snap JSON (
application/farcaster-frame): for Farcaster Snap clients - PNG raster (
?image): server-rasterized page preview from the HTML/CSS result
In practice:
GETwith SnapAcceptheader triggerssnapGetResponse(...)POSTwith Snap JFS payload triggerssnapPostResponse(...)GETwith?imageresolves the page as HTML, transforms to SVG with Satori, then to PNG with Resvg- Frame signature packets (
POST) are adapted into SvelteKit action-compatible requests - Responses include
linkalternates andvary: Acceptwhere relevant
Global middleware that:
- normalizes request URL origin/host for proxied deployments (
publicRequestUrl(...)) - adds snap alternate
linkmetadata to HTML responses - handles Snap CORS preflight (
OPTIONS) - routes Snap
GET/POSTflows - handles frame signature packet POST requests
- overloads
?imageinto HTML->PNG server rasterization
Snap protocol adapters that:
- force HTML resolution for canonical route parsing
- parse
fc:frame:*metadata from HTML - convert resolved pages to Snap JSON via
framePageToSnap(...) - detect SvelteKit internal action URLs (
?/actionName) - transform Snap inputs into form-encoded action submissions (
x-sveltekit-action: true) - merge action-returned
frame/snap/titleoverrides back into a final Snap response
Snap composition logic for:
- converting frame-like page data into final snap UI trees
- constructing action rows, page footer/meta, and route navigation controls
- generating page raster preview URLs
src/lib/snap-components.ts: typed helpers for buttons, groups, and higher-level page buildingsrc/lib/snap-spec.ts: Snap media type/version/constantssrc/lib/snap-jfs.ts: JFS payload parsing and signature envelope handlingsrc/lib/frame-satori.ts: HTML document preparation for Satori parsing
Install and run:
pnpm install
pnpm devRun on a specific port:
pnpm dev --port 4000Optional public tunnel for client integrations:
pnpm tunnel:ngrokpnpm dev- run Vite dev serverpnpm build- production buildpnpm preview- preview production buildpnpm check- Svelte type-check pipelinepnpm lint/pnpm format- lint and formatpnpm test:unit- Vitestpnpm test:integration- Playwrightpnpm test:integration:snap- snap-focused Playwright config
When rendering HTML/CSS through satori / resvg, some CSS features differ from browser behavior. In this codebase that often means:
- preferring solid colors over layered/translucent gradients in snap-rendered surfaces
- limiting complex multi-shadow text styling for raster stability
- keeping route output deterministic across HTML, Snap JSON, and image variants
- keep layout primitive (
flex/block flow, simpleposition, no grid-like assumptions) - avoid browser-only features like
calc,z-index, and 3D transforms - keep sizing/background values conservative (single-layer backgrounds, explicit sizes)
- treat text rendering as approximate vs browser typography (limited shaping/RTL behavior)
- if a style looks valid in browser CSS but fails in snap image rendering, verify it against Satori's supported property/value table first