Create a minimal JSX-first, DSD-first openElement app, start the dev server, build static output, and learn where the v0.33.0 application lifecycle lives.
deno.json imports, JSX/VNode rendering, and the openElement() Vite facade from@openelement/app/vite.deno run -A jsr:@openelement/create my-app
cd my-appThe scaffold includes page routes, a sample island, Vite config, and the common Deno tasks needed for development and builds.
deno task devDev mode provides module loading and hot reload through Vite, with SSR/API behavior from the generated Hono entry.
deno task buildThe build command scans routes and islands, generates SSR wiring, renders DSD HTML, emits client island chunks, and writes the final static output to dist/.
my-app/
|-- app/
| |-- routes/
| | `-- index.tsx
| |-- islands/
| | `-- my-counter.tsx
| `-- components/
|-- deno.json
`-- vite.config.tsA page is a canonical object descriptor passed to definePage(). The framework turns the descriptor into a Web Component and renders it as Declarative Shadow DOM during SSR/SSG.
import { definePage } from '@openelement/app';
export default definePage({
route: { path: '/' },
head: { title: 'Home' },
render() {
return <main>Hello openElement</main>;
},
});The object form keeps app code in JSX while giving the framework a structured lifecycle: params, route source, metadata, redirect, not-found, and error fallback.
import { definePage, notFound, redirect } from '@openelement/app';
export default definePage({
route: { path: '/posts/[slug]', params: ['slug'] },
head: {
title: 'Post',
description: 'Blog post page',
},
renderIntent: {
mode: 'static',
revalidate: 300,
},
async load({ params, route }) {
if (!params.slug) notFound();
if (params.slug === 'old-post') redirect('/posts/new-post', 301);
return { slug: params.slug, source: route.filePath };
},
render({ data, route }) {
return (
<article>
<h1>{data.slug}</h1>
<p>Rendered from {route.filePath}</p>
</article>
);
},
error({ error }) {
return <main>{String(error)}</main>;
},
});Put browser-upgraded components under app/islands. Interactive UI should return VNodes and use JSX event handlers.
import { defineIsland } from '@openelement/app';
import { signal } from '@openelement/runtime';
const count = signal(0);
export default defineIsland(
'my-counter',
() => (
<button onClick={() => count.value++}>
Count: {count.value}
</button>
),
);import { defineConfig } from 'vite';
import { openElement } from '@openelement/app/vite';
export default defineConfig({
plugins: [openElement({ routesDir: 'app/routes', islandsDir: 'app/islands' })],
});The v1.0 target is a stable application engine. v0.33.0 makes the Application API structured and AI-readable.