No-Build Client Islands
ποΈ No-Build Client Islands
Long-Lasting, Framework-Free Web Apps with Preact, HTM, and Page.js
Are you tired of:
- Relearning your static site generator every 6 months?
- Chasing backwards-incompatible updates from Astro, Next.js, or Fresh?
- Deploying full Node.js stacks just to serve a couple buttons and forms?
What if you could build fast, reactive web apps that:
- Run forever without framework churn
- Need no build tools or hydration pipelines
- Use your favorite backend (Go, Rust, Java, Python…) without coupling to JS frameworks?
Welcome to No-Build Client Islands β a practical, zero-build architecture for interactive SPAs using native JavaScript modules, Preact + HTM for rendering, and Page.js for routing.
π§ Who Is This For?
This approach is designed for developers who:
- Donβt want to deploy Node.js just to render routes
- Donβt want to chase framework updates every few months (π Astro 1 β 2, Next.js App Router, Fresh rewritesβ¦)
- Prefer stability, clarity, and no build chains
- Are okay serving static HTML and JS and letting the client do just enough work
- Want to connect to backends built in Go, Rust, Java, or any language
Build it once. Ship it. Donβt touch it again unless you choose to.
βοΈ The Stack (Tiny, Stable, and Built to Last)
Tool | Purpose |
---|---|
page.js | Client-side routing (2.5 KB) |
preact | Fast rendering engine (4 KB gzipped) |
htm | JSX-like syntax without Babel |
@preact/signals | Fine-grained reactivity (1.3 KB gzipped) |
Everything is served as native ES modules via CDN β no bundler, no dev server, no tooling required.
π§ͺ What It Looks Like
π Project Structure:
index.html
index.js
views/
ββ home.js
ββ profile.js
islands/
ββ profileDetails.js
Each page renders its own shell, and “islands” of interactivity are mounted selectively.
// index.js
page('/', () => render(html`<${Home} />`, root));
page('/profile/:id', ctx => render(html`<${Profile} id=${ctx.params.id} />`, root));
Inside Home.js
:
export default function Home() {
return html`
<section>
<h1>π Home</h1>
<p>This page is static and fast.</p>
</section>
`;
}
And Profile.js
fetches data and renders:
export async function loadAndRenderProfile(id) {
const res = await fetch(`https://api.example.com/users/${id}`);
const profile = await res.json();
return html`
<section>
<h1>π€ Profile: ${profile.name}</h1>
<p>${profile.bio}</p>
<${ProfileDetails} name=${profile.name} />
</section>
`;
}
π islands/profileDetails.js
import { h } from 'https://esm.sh/preact';
import htm from 'https://esm.sh/htm';
import { signal } from 'https://esm.sh/@preact/signals';
const html = htm.bind(h);
export default function ProfileDetails({ name }) {
const toggle = signal(false);
return html`
<div style="margin-top:1em;">
<button @click=${() => toggle.value = !toggle.value}>
${toggle.value ? 'Hide' : 'Show'} details for ${name}
</button>
${toggle.value && html`<p>π Detailed info for ${name}</p>`}
</div>
`;
}
This lets you mount a reactive island inside a data-driven route.
π How It Compares
Feature | No-Build Client Islands | Next.js | Astro | Fresh |
---|---|---|---|---|
Build Step Required | β Never | β Yes | β Yes | β Yes |
Node.js Server Needed | β Static only | β Usually | β (build-time) | β Deno runtime |
Partial Hydration | β Manual, controlled | β οΈ Complex | β Built-in | β Built-in |
Framework Updates Break Apps | β Never | β Often | β Frequent | β Early-stage |
Bundle Size (Runtime) | π’ ~6 KB total | π΄ 100+ KB | π‘ Medium | π‘ Medium |
Backend Agnostic | β Yes | β οΈ Tight coupling | β οΈ Content-first | β οΈ Deno-focused |
Updatable in 2030? | β Yes | β Probably not | β Maybe not | β Unclear |
π Add Your Favorite Backend
Want authentication, storage, or live APIs? Just plug in:
- π§ PocketBase
- π³ Your own Go/Fiber/Actix server
- π Flask or FastAPI
- β Spring Boot
Thereβs zero lock-in. The frontend ships as static files β no server-side JS needed.
π₯ Why Use No-Build Client Islands?
- π‘ Zero tooling required β no npm, no vite.config.js, no webpack nightmares
- β‘ Fast load times β render only what you need, when you need it
- π§ No rehydration pain β everything starts fresh on the client
- π§© Modular by design β each interactive unit is its own reactive component
- π§ No churn β once deployed, itβll work next year and the one after that
β¨ Real-World Use Cases
- Internal tools and dashboards
- Control panels for IoT or admin UIs
- Lightweight apps served from a CDN
- Frontends talking to Rust/Go APIs
π§ Final Thoughts
Framework churn is real. Tooling fatigue is real. But you can ship interactive, modular, reactive frontends today with nothing but:
- A browser
- A text editor
- A few stable, fast libraries from CDN
Itβs time to build software that stays simple and runs forever.