Phaze + Astro
Phaze ships an Astro integration that registers it as a renderer. @madenowhere/phaze-render-to-string is wired — every Astro client directive works: client:only, client:load, client:idle, client:visible, client:media, plus server-rendered (no-directive) islands and prerender:true static pages.
Two islands below — same component, different directives:
client:only — mounts in the browser, no SSR HTML
client:load — server-rendered HTML, then client mounts on page load
client:load: 10
pnpm add @madenowhere/phaze-astro @madenowhere/phaze astroimport { defineConfig } from 'astro/config'import phaze from '@madenowhere/phaze-astro'
export default defineConfig({ integrations: [phaze()],})The integration:
- registers Phaze as a renderer Astro can dispatch to
- configures Vite’s esbuild to use
jsxImportSource: '@madenowhere/phaze' - provides a client mount entry that calls
render(component, element)for each island
tsconfig.json
Section titled “tsconfig.json”Match the JSX import source so editor tooling agrees with the build:
{ "extends": "astro/tsconfigs/strict", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@madenowhere/phaze" }}Authoring an island
Section titled “Authoring an island”import { signal } from '@madenowhere/phaze'
export default function Counter({ start = 0 }: { start?: number }) { const count = signal(start) return ( <button onClick={() => count.set(count.current() + 1)}> {count} </button> )}---import Counter from '../components/Counter.tsx'---<html><body> <h1>Hello</h1> <Counter client:only="phaze" start={5} /></body></html>What works today
Section titled “What works today”- All Astro client directives —
client:only,client:load,client:idle,client:visible,client:media, plus directive-less server-only islands andprerender:truestatic pages. - SSR + hydration —
@madenowhere/phaze-astro/serverdelegates to@madenowhere/phaze-render-to-string, which runs the same JSX runtime against a linkedom virtual DOM and serializes the result. The browser then mounts viahydrate(when SSR’d HTML is present) orrender(forclient:only); the choice is made byphaze-astro/clientbased on whether the host element has children. - View transitions — Starlight/Astro’s
<ClientRouter />is supported;phaze-astro/clientregisters anastro:after-swaplistener that disposes phaze scopes whose host elements drop out of the document during a swap. - Props pass through serializable values normally; non-serializable values (functions, DOM nodes) need to live inside the island.
- Multiple islands per page; each gets its own dispose-bounded scope.