PWFabric runtime
The execution engine that brings surfaces to life. Handles rendering, state management, and event processing across all environments.
Runtime components
- Renderer — Transforms surface definitions into rendered output. Supports SSR, CSR, and static generation.
- State engine — Manages surface state with reactive updates. Supports local, shared, and persistent state.
- Event system — Handles user interactions and system events. Supports bubbling, capturing, and custom events.
Rendering modes
Server-side rendering (SSR)
Fetch the surface definition server-side and render with
SurfaceRenderer. Works with Next.js Server Components.
// Next.js Server Component
import { SurfaceRenderer } from '@pwfabric/runtime'
import { createClient } from '@pwfabric/sdk'
const client = createClient({ baseUrl: '...', token: '...' })
export default async function Page({ params }) {
const surface = await client.surfaces.get(params.id)
return <SurfaceRenderer surface={surface} />
}Client-side rendering (CSR)
Fetch and render on the client for highly interactive surfaces. Best for dashboard-style experiences.
'use client'
import { useState, useEffect } from 'react'
import { SurfaceRenderer } from '@pwfabric/runtime'
import { createClient } from '@pwfabric/sdk'
const client = createClient({ baseUrl: '...', token: '...' })
export default function Page({ id }) {
const [surface, setSurface] = useState(null)
useEffect(() => {
client.surfaces.get(id).then(setSurface)
}, [id])
if (!surface) return null
return <SurfaceRenderer surface={surface} />
}Static generation
For surfaces that don’t change frequently, prefer static generation —
fetch at build time and emit pre-rendered HTML. Works with
generateStaticParams in Next.js App Router.
Block registry
The runtime maintains a per-World block registry that maps block type
identifiers to render components. The default registry is intentionally
empty (per ADR-131): blocks arrive on a per-World basis through the
pwpack install pipeline (ADR-122). The runtime assembles the registry at
request time from the World’s installed pwpacks.
import { SurfaceRenderer } from '@pwfabric/runtime'
import { getBlockRegistryForWorld } from '@pwfabric/runtime/blocks'
const registry = await getBlockRegistryForWorld(worldId)
<SurfaceRenderer surface={surface} registry={registry} />On the server you can build the same registry from a known installation set (useful for tests and previews):
import { assembleCanonicalRegistry } from '@pwfabric/runtime/blocks'
const registry = await assembleCanonicalRegistry({
installedBlocksOverride: ['@pwpack/phiwebs/ui', '@pwpack/phiwebs/data'],
})The 4 first-party packs (phiwebs-ui, phiwebs-data, phiwebs-forms,
phiwebs-nav) together expose 45 blocks. Worlds receive these as their
default plan bundle on signup; additional community blocks install
through the marketplace.
State management
The runtime exposes a reactive state engine that surfaces can opt into
via the state capability. Common patterns:
- Local state — Block-scoped reactive state (e.g., form inputs)
- Shared state — Surface-scoped state visible across blocks (e.g., selected tab)
- Persistent state — User-scoped state that survives page reloads (e.g., theme preference)
Event system
Blocks can emit and listen for events. Standard events include
onClick, onChange, onSubmit, plus surface-level lifecycle events
(onMount, onUnmount). Custom events bubble through the block tree
and can be intercepted by parent blocks.
See also
- Architecture overview — How runtime fits into the layered system.
- SDK reference — Surface client API.
- API reference — REST endpoints the runtime calls.