Direct visit
GET /dashboard
Complete document, streamed
HTML application framework
Native Fragments is a small framework for Cloudflare Workers that streams server-rendered HTML, upgrades native navigation, and uses Lit for interactive islands. Modern tooling, very little ceremony.
Cloudflare WorkersLitWeb ComponentsNative APIs
The working model
The server owns routing and the first render. The browser router asks for the same route as a fragment and swaps only its declared target. Without JavaScript, the anchor simply performs a document navigation.
Direct visit
GET /dashboard
Complete document, streamed
Link navigation
GET /dashboard · x-fragment
Only the target HTML, streamed
JavaScript off
Native anchor or form
The same route still works
Server
Route definitions keep path, metadata, actions, and HTML together. Deferred work starts immediately and fills its own boundary when ready. A slow dependency delays one region—not the whole document.
import { fragment, html, route } from "@nativefragments/core/server";
const activity = fragment("activity", {
loading: () => html`<p aria-live="polite">Loading activity…</p>`,
render: async ({ signal }) =>
activityFeed(await loadActivity({ signal })),
});
export const dashboard = route("/dashboard", {
meta: () => ({ title: "Dashboard" }),
render: (context) => html`
<h1>Dashboard</h1>
${context.defer(activity)}
`,
fragments: [activity],
});
Streaming navigation
Fragment navigation uses a framed HTML protocol. The first frame swaps the page immediately; later frames reveal deferred regions out of order. Old tabs that do not speak the protocol receive a safe buffered response.
00 msshellpainted
18 mspage fragmentswapped
112 mssummaryrevealed
384 msartworksrevealed
421 msprovenanceerror boundary
Design constraints
Native Fragments does not compete with the platform. It defines the contracts the platform is missing for an HTML application, then gets out of the way.
Routes return escaped HTML. The browser receives useful content before application JavaScript runs.
Real anchors and forms are the baseline. A small router upgrades them with history, prefetch, and streamed swaps.
Use Lit and Web Components for the parts that own state. Server rendering and hydration live in a replaceable adapter.
esbuild resolves modern ESM and creates deployable files. It is a fast implementation detail, not a compiler-shaped architecture.
Browser
Starting navigation returns three explicit capabilities. Lifecycle is owned by an AbortSignal; cache invalidation is an application decision; semantic DOM events make integrations observable.
import "@nativefragments/lit/client";
import { startRouter } from "@nativefragments/core/client/router.js";
const lifetime = new AbortController();
const router = startRouter({
prefetch: "intent",
signal: lifetime.signal,
});
await router.navigate("/dashboard");
router.prefetch("/settings");
router.invalidate("/dashboard");
Interactive islands
Components are normal Lit elements. The adapter pins the evolving Lit SSR surface and emits hydratable Declarative Shadow DOM, keeping labs APIs out of application code.
// app-card.js — ordinary Lit
import { LitElement, css, html } from "lit";
class AppCard extends LitElement {
static styles = css`:host { display: block }`;
render() { return html`<slot></slot>`; }
}
customElements.define("app-card", AppCard);
// Server route — Lit SSR stays behind one adapter
import { renderLit } from "@nativefragments/lit/server";
import { html } from "lit";
export const card = () =>
renderLit(html`<app-card>Ready now.</app-card>`);
Build policy
Source stays standards-based ESM. A tiny esbuild step resolves packages, bundles browser modules, and lets Wrangler run the Worker. You can import from npm without turning the framework into a compiler or forcing every dependency to publish browser-ready bare-specifier graphs.
npm run dev → esbuild + wrangler dev --live-reload
Start with the whole stack
npm create @nativefragments/app@latest my-app