An open API service indexing awesome lists of open source software.

https://github.com/ije/mono-jsx

`<html>` as a `Response`.
https://github.com/ije/mono-jsx

Last synced: 2 months ago
JSON representation

`<html>` as a `Response`.

Awesome Lists containing this project

README

          

# mono-jsx

![`` as a `Response`](./.github/og-image.png)

mono-jsx is a JSX runtime that renders the `` element to a `Response` object.

- πŸš€ No build step needed
- πŸ¦‹ Lightweight (12KB gzipped), zero dependencies
- 🚦 Signals as reactive primitives
- ⚑️ Use web components, no virtual DOM
- πŸ’‘ Complete Web API TypeScript definitions
- ⏳ Streaming rendering
- πŸ—‚οΈ Built-in router (SPA mode)
- πŸ”‘ Session storage
- πŸ₯· [htmx](#using-htmx) integration
- 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.

Playground: https://val.town/x/ije/mono-jsx

## Installation

mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, and Cloudflare Workers.
You can install it via `npm`, `deno`, or `bun`:

```bash
# Node.js, Cloudflare Workers, or other node-compatible runtimes
npm i mono-jsx

# Deno
deno add npm:mono-jsx

# Bun
bun add mono-jsx
```

### Setup JSX Runtime

To use mono-jsx as your JSX runtime, add the following configuration to your `tsconfig.json` (or `deno.json` for Deno):

```jsonc
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "mono-jsx"
}
}
```

You can also run `mono-jsx setup` to automatically add the configuration to your project:

```bash
# Node.js, Cloudflare Workers, or other node-compatible runtimes
npx mono-jsx setup

# Deno
deno run -A npm:mono-jsx setup

# Bun
bunx mono-jsx setup
```

### Zero Configuration

Alternatively, you can use the `@jsxImportSource` pragma directive without installing mono-jsx (no package.json/tsconfig/node_modules). The runtime (Deno/Bun) automatically installs mono-jsx to your computer:

```js
// Deno, Valtown
/** @jsxImportSource https://esm.sh/mono-jsx */

// Bun
/** @jsxImportSource mono-jsx */
```

## Usage

mono-jsx allows you to return an `` JSX element as a `Response` object in the `fetch` handler:

```tsx
// app.tsx

export default {
fetch: (req) => (

Welcome to mono-jsx!



)
}
```

For Deno/Bun users, you can run the `app.tsx` directly:

```bash
deno serve app.tsx
bun app.tsx
```

If you're building a web app with [Cloudflare Workers](https://developers.cloudflare.com/workers/wrangler/commands/#dev), use `wrangler dev` to start your app in development mode:

```bash
npx wrangler dev app.tsx
```

**Node.js doesn't support JSX syntax or declarative fetch servers**, we recommend using mono-jsx with [srvx](https://srvx.h3.dev) and [tsx](https://tsx.is) (as JSX loader) to start your app:

```bash
# npm i srvx tsx
npx srvx --import tsx app.tsx
```

> [!NOTE]
> Only the root `` element will be rendered as a `Response` object. You cannot return a `

` or any other element directly from the `fetch` handler. This is a limitation of mono-jsx.

## Using JSX

mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with key differences.

### Using Standard HTML Property Names

mono-jsx adopts standard HTML property names, avoiding React's custom naming conventions:

- `className` β†’ `class`
- `htmlFor` β†’ `for`
- `onChange` β†’ `onInput`

### Composition with `class`

mono-jsx allows you to compose the `class` property using arrays of strings, objects, or expressions:

```tsx

;
```

### Using Pseudo Classes and Media Queries in `style`

mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes), [pseudo elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements), [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries), and [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting) in the `style` property:

```tsx


Link
;
```

### Using View Transition

mono-jsx supports [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) to create smooth transitions between views. To use view transitions, add the `viewTransition` attribute to the following components:

- ``
- ``
- ``
- ``
- ``

You can set custom transition animations by adding [`::view-transition-group`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-group), [`::view-transition-old`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-old), and [`::view-transition-new`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-new) pseudo-elements with your own CSS animations. For example:

```tsx
function App(this: FC<{ show: boolean }>) {
return (



Hello world!



this.show = !this.show}>Toggle

)
}
```

You can also set the `viewTransition` attribute a html element which contains signal children.

```tsx
function App(this: FC<{ message: string }>) {
this.message = "Hello world!";
return (

{this.message}


)
}
```

You can also set the view transition name in the style property with the `viewTransition` attribute set to `true`.

```tsx
function App(this: FC<{ message: string }>) {
this.message = "Hello world!";
return (

{this.message}


)
}
```

### Using `` Element

mono-jsx uses [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) elements to render slotted content (equivalent to React's `children` property). You can also add the `name` attribute to define named slots:

```tsx
function Container() {
return (


{/* Default slot */}

{/* Named slot */}


)
}

function App() {
return (

{/* This goes to the named slot */}

This is a description.


{/* This goes to the default slot */}

Hello world!



)
}
```

### Using `html` Tag Function

mono-jsx provides an `html` tag function to render raw HTML, which is similar to React's `dangerouslySetInnerHTML`.

```tsx
function App() {
return

{html`

Hello world!

`}
;
}
```

Variables in the `html` template literal are escaped. To render raw HTML without escaping, call the `html` function with a string literal.

```tsx
function App() {
return

{html(`${

Hello world!

}`)}
;
}
```

The `html` function is globally available without importing. You can also use `css` and `js` functions for CSS and JavaScript:

```tsx
function App() {
return (

{css`h1 { font-size: 3rem; }`}
{js`console.log("Hello world!")`}

)
}
```

> [!WARNING]
> The `html` tag function is **unsafe** and can cause [**XSS**](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerabilities.

### Event Handlers

mono-jsx lets you write event handlers directly in JSX, similar to React:

```tsx
function Button() {
return (
alert("BOOM!")}>
Click Me

)
}
```

Event handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**

```tsx
import { doSomething } from "some-library";

function Button(this: FC<{ count: 0 }>, props: { role: string }) {
const message = "BOOM!"; // server-side variable
this.count = 0; // initialize a signal
console.log(message); // only prints on server-side
return (
{
alert(message); // ❌ `message` is a server-side variable
console.log(props.role); // ❌ `props` is a server-side variable
doSomething(); // ❌ `doSomething` is imported on the server-side
Deno.exit(0); // ❌ `Deno` is unavailable in the browser
document.title = "BOOM!"; // βœ… `document` is a browser API
console.log(evt.target); // βœ… `evt` is the event object
this.count++; // βœ… update the `count` signal
}}
>


)
}
```

mono-jsx allows you to use a function as the value of the `action` attribute of the `` element. The function will be called on form submission, and the `FormData` object will contain the form data.

```tsx
function App() {
return (
console.log(data.get("name"))}>

Submit

)
}
```

## Async Components

mono-jsx supports async components that return a `Promise` or an async function. With streaming rendering, async components are rendered asynchronously, allowing you to fetch data or perform other async operations before rendering the component.

```tsx
async function Loader(props: { url: string }) {
const data = await fetch(props.url).then((res) => res.json());
return ;
}

export default {
fetch: (req) => (

Loading...} />

)
}
```

You can also use async generators to yield multiple elements over time. This is useful for streaming rendering of LLM tokens:

```tsx
async function* Chat(props: { prompt: string }) {
const stream = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
stream: true,
});

for await (const event of stream) {
const text = event.choices[0]?.delta.content;
if (text) {
yield {text};
}
}
}

export default {
fetch: (req) => (

●} />

)
}
```

You can use `pending` to display a loading state while waiting for async components to render:

```tsx
async function Sleep({ ms }) {
await new Promise((resolve) => setTimeout(resolve, ms));
return ;
}

export default {
fetch: (req) => (

Loading...}>

After 1 second




)
}
```

You can set the `rendering` attribute to `"eager"` to force synchronous rendering (the `pending` property will be ignored):

```tsx
export default {
fetch: (req) => (


After 1 second




)
}
```

You can add the `catch` attribute to handle errors in the async component. The `catch` attribute should be a function that returns a JSX element:

```tsx
async function Hello() {
throw new Error("Something went wrong!");
return

Hello world!

;
}

export default {
fetch: (req) => (

{err.message}

} />

)
}
```

## Lazy Rendering

mono-jsx renders HTML on the server side and sends no hydration JavaScript to the client. To render a component dynamically on the client, you can use the `` element to ask the server to render a component:

To render a component by name, you can use the `` element with the `name` prop, and ensure the component is registered in the `components` prop of root `` element.

```tsx
function Foo(props: { bar: string }) {
return

{props.bar}

;
}

export default {
fetch: (req) => (

Loading...} />

)
}
```

You can use the `` element with the `is` prop to render a component by function reference without registering the component in the `components` prop of root `` element.

```tsx
export default {
fetch: (req) => (

Loading...} />

)
}
```

Or you can use the `` element with the `as` prop to render a component by JSX element.

```tsx
export default {
fetch: (req) => (

} pending={

Loading...

} />

)
}
```

You can also use [signals](#using-signals) for `name` or `props` attributes of a component. Changing the signal value will trigger the component to re-render with the new name or props:

```tsx
import { Profile, Projects, Settings } from "./pages.tsx"

function Dash(this: FC<{ page: "Profile" | "Projects" | "Settings" }>) {
this.page = "Profile";

return (
<>


this.page = "Profile"}>Profile
this.page = "Projects"}>Projects
this.page = "Settings"}>Settings


Loading...} />

>
)
}

export default {
fetch: (req) => (



)
}
```

You can use the `` element to control when to render a component:

```tsx
async function Lazy(this: FC<{ show: boolean }>, props: { url: string }) {
return (



Loading...} />

this.show = true }>Load `Foo` Component

)
}

export default {
fetch: (req) => (



)
}
```

## Using Signals

mono-jsx uses signals for updating the view when a signal changes. Signals are similar to React's state, but they are more lightweight and efficient. You can use signals to manage state in your components.

### Using Component Signals

You can use the `this` keyword in your components to manage signals. Signals are bound to the component instance, can be updated directly, and the view will automatically re-render when a signal changes:

```tsx
function Counter(this: FC<{ count: number }>, props: { initialCount?: number }) {
// Initialize a signal
this.count = props.initialCount ?? 0;

// or you can use `this.init` to initialize the signals
this.init({ count: props.initialCount ?? 0 });

return (


{/* render signal */}
{this.count}

{/* Update signal to trigger re-render */}
this.count--}>-
this.count++}>+


)
}
```

### Using App Signals

You can define app signals by adding the `app` prop to the root `` element. The app signals are available in all components via `this.app.`. Changes to the app signals will trigger re-renders in all components that use them:

```tsx
interface IAppSignals {
themeColor: string;
}

function Header(this: WithAppSignals) {
return (

Welcome to mono-jsx!



)
}

function Footer(this: WithAppSignals) {
return (

(c) 2025 mono-jsx.



)
}

function Main(this: WithAppSignals) {
return (


Theme Color:



)
}

export default {
fetch: (req) => (





)
}
```

### Using Computed Signals

You can use `this.computed` to create a derived signal based on other signals:

```tsx
function App(this: FC<{ input: string }>) {
this.input = "Welcome to mono-jsx";
return (


{this.computed(() => this.input + "!")}




)
}
```

> [!TIP]
> You can use `this.$` as a shorthand for `this.computed` to create computed signals.

### Using Effects

You can use `this.effect` to create side effects based on signals. The effect will run whenever the signal changes:

```tsx
function App(this: FC<{ count: number }>) {
this.count = 0;

this.effect(() => {
console.log("Count changed:", this.count);
});

return (


{this.count}
this.count++}>+

)
}
```

The callback function of `this.effect` can return a cleanup function that gets run once the component element has been removed via ``, `` or `` conditional rendering:

```tsx
function Counter(this: FC<{ count: number }>) {
this.count = 0;

this.effect(() => {
const interval = setInterval(() => {
this.count++;
}, 1000);

return () => clearInterval(interval);
});

return (


{this.count}

)
}

function App(this: FC<{ show: boolean }>) {
return (





this.show = !this.show }>{this.computed(() => this.show ? 'Hide': 'Show')}

)
}
```

### Using `` Element with Signals

The `` element conditionally renders content based on the `when` prop. You can use signals to control the visibility of the content on the client side.

```tsx
function App(this: FC<{ show: boolean }>) {
const toggle = () => {
this.show = !this.show;
}

return (



Welcome to mono-jsx!



{this.$(() => this.show ? "Hide" : "Show")}


)
}
```

mono-jsx also provides a `` element that is similar to ``, but it conditionally hides the content based on the `when` prop.

```tsx
function App(this: FC<{ hidden: boolean }>) {
return (



Welcome to mono-jsx!




)
}
```

If you need `if-else` logic in JSX, use `` element instead:

```tsx
function App(this: FC<{ ok: boolean }>) {
return (



True
False


)
}
```

### Using `` Element with Signals

The `` element renders different content based on the `value` prop. Elements with matching `slot` attributes are displayed when their value matches, otherwise default slots are shown. Like ``, you can use signals to control the value on the client side.

```tsx
function App(this: FC<{ lang: "en" | "zh" | "πŸ™‚" }>) {
this.lang = "en";

return (



Hello, world!


δ½ ε₯½οΌŒδΈ–η•ŒοΌ


βœ‹πŸŒŽβ—οΈ




this.lang = "en"}>English
this.lang = "zh"}>δΈ­ζ–‡
this.lang = "πŸ™‚"}>πŸ™‚



)
}
```

### Form Input Two-way Binding

You can use the `$value` attribute to bind a signal to the value of a form input element. The `$value` attribute is a two-way data binding, which means that when the input value changes, the signal will be updated, and when the signal changes, the input value will be updated.

```tsx
function App(this: FC<{ value: string }>) {
this.value = "Welcome to mono-jsx";
this.effect(() => {
console.log("value changed:", this.value);
});
// return this.value = e.target.value} />;
return ;
}
```

You can also use the `$checked` attribute to bind a signal to the checked state of a checkbox or radio input element.

```tsx
function App(this: FC<{ checked: boolean }>) {
this.effect(() => {
console.log("checked changed:", this.checked);
});
// return this.checked = e.target.checked} />;
return ;
}
```

### Limitations of Signals

1\. Arrow functions are non-stateful components.

```tsx
// ❌ Won't work - uses `this` in a non-stateful component
const App = () => {
this.count = 0;
return (


{this.count}
this.count++}>+

)
};

// βœ… Works correctly
function App(this: FC) {
this.count = 0;
return (


{this.count}
this.count++}>+

)
}
```

2\. Signals cannot be computed outside of the `this.computed` method.

```tsx
// ❌ Won't work - updates of a signal won't refresh the view
function App(this: FC<{ message: string }>) {
this.message = "Welcome to mono-jsx";
return (


{this.message + "!"}


this.message = "Clicked"}>
Click Me


)
}

// βœ… Works correctly
function App(this: FC) {
this.message = "Welcome to mono-jsx";
return (


this.message + "!")}>{this.$(() => this.message + "!")}


this.message = "Clicked"}>
Click Me


)
}
```

3\. The callback function of `this.computed` must be a pure function. This means it should not create side effects or access any non-stateful variables. For example, you cannot use `Deno` or `document` in the callback function:

```tsx
// ❌ Won't work - throws `Deno is not defined` when the button is clicked
function App(this: FC<{ message: string }>) {
this.message = "Welcome to mono-jsx";
return (


{this.computed(() => this.message + "! (Deno " + Deno.version.deno + ")")}


this.message = "Clicked"}>
Click Me


)
}

// βœ… Works correctly
function App(this: FC<{ message: string, denoVersion: string }>) {
this.denoVersion = Deno.version.deno;
this.message = "Welcome to mono-jsx";
return (


{this.computed(() => this.message + "! (Deno " + this.denoVersion + ")")}


this.message = "Clicked"}>
Click Me


)
}
```

## Using `this` in Components

mono-jsx binds a scoped signals object to `this` of your component functions. This allows you to access signals, context, and request information directly in your components.

The `this` object has the following built-in properties:

- `app`: The app global signals.
- `context`: The context object defined on the root `` element.
- `request`: The request object from the `fetch` handler.
- `session`: The session storage.
- `refs`: A map of refs defined in the component.
- `computed(fn)`: A method to create a computed signal.
- `$(fn)`: A shortcut for `computed(fn)`.
- `effect(fn)`: A method to create side effects.

```ts
type FC = {
readonly app: AppSignals & { refs: AppRefs; url: WithParams }
readonly context: Context;
readonly request: WithParams;
readonly session: Session;
readonly refs: Refs;
readonly computed: (fn: () => T) => T;
readonly $: FC["computed"]; // A shortcut for `FC.computed`.
readonly effect: (fn: () => void | (() => void)) => void;
} & Signals;

// define `AppSignals` type
function Component(this: WithAppSignals) {
this.app.title // type: 'string'
}

// define `Context` type
function Component(this: WithContext) {
this.context.secret // type: 'string'
}
```

### Using Signals

See the [Using Signals](#using-signals) section for more details on how to use signals in your components.

### Using Refs

You can use `this.refs` to access refs in your components. Refs are defined using the `ref` attribute in JSX, and they allow you to access DOM elements directly. The `refs` object is a map of ref names to DOM elements.

```tsx
function App(this: WithRefs) {
this.effect(() => {
this.refs.input?.addEventListener("input", (evt) => {
console.log("Input changed:", evt.target.value);
});
});

return (



this.refs.input?.focus()}>Focus

)
}
```

You can also use `this.app.refs` to access app-level refs:

```tsx
function Layout(this: FC) {
return (
<>

Welcome to mono-jsx!






>
)
}
```

The `` element also supports the `ref` attribute, which allows you to control the component rendering manually. The `ref` will be a `ComponentElement` that has the `name`, `props`, and `refresh` properties:

- `name`: The name of the component to render.
- `props`: The props to pass to the component.
- `refresh`: A method to re-render the component with the current name and props.

```tsx
import type { ComponentElement } from "mono-jsx";

function App(this: WithRefs) {
this.effect(() => {
// updating the component name and props will trigger a re-render of the component
this.refs.component.name = "Foo";
this.refs.component.props = {};

const timer = setInterval(() => {
// re-render the component
this.refs.component.refresh();
}, 1000);
return () => clearInterval(timer); // cleanup
});
return (




)
}
```

### Using Context

You can use the `context` property in `this` to access context values in your components. The context is defined on the root `` element:

```tsx
function Dash(this: WithContext) {
const { auth } = this.context;
return (


Welcome back, {auth.name}!


Your UUID is {auth.uuid}



)
}

export default {
fetch: async (req) => {
const auth = await doAuth(req);
return (

{!auth &&

Please Login

}
{auth && }

)
}
}
```

### Accessing Request Info

You can access request information in components via the `request` property in `this` which is set on the root `` element:

```tsx
function RequestInfo(this: FC) {
const { request } = this;
return (


Request Info


{request.method}


{request.url}


{request.headers.get("user-agent")}



)
}

export default {
fetch: (req) => (



)
}
```

## Using Router (SPA mode)

mono-jsx provides a built-in `` element that allows your app to render components based on the current URL. On the client side, it listens to all `click` events on `` elements and asynchronously fetches the route component without reloading the entire page.

To use the router, you need to define your routes as a mapping of URL patterns to components and pass it to the `` element as the `routes` prop. The `request` prop is also required to match the current URL against the defined routes.

```tsx
const routes = {
"/": Home,
"/about": About,
"/blog": Blog,
"/post/:id": Post,
}

export default {
fetch: (req) => (


Home
About
Blog



)
}
```

The mono-jsx router requires [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) to match routes:

- βœ… Deno
- βœ… Cloudflare Workers
- βœ… Node.js (>= 24)

For Bun users, mono-jsx provides a `buildRoutes` function that uses Bun's built-in server routing:

```tsx
import { buildRoutes } from "mono-jsx"

const routes = {
"/": Home,
"/about": About,
"/blog": Blog,
"/post/:id": Post,
}

Bun.serve({
routes: buildRoutes((req) => (


Home
About
Blog



))
})
```

### Using Route `params`

When you define a route with a parameter (e.g., `/post/:id`), mono-jsx will automatically extract the parameter from the URL and make it available in the route component. The `params` object is available in the `request` property of the component's `this` context.

You can access the `params` object in your route components to get the values of the parameters defined in the route pattern:

```tsx
// router pattern: "/post/:id"
function Post(this: FC) {
this.request.url // "http://localhost:3000/post/123"
this.request.params?.id // "123"
}
```

### Using Route Form

mono-jsx allows you to define a `FormHandler` function for route components to handle form data from form submissions on the current route page on the client side. To submit the form data to the `FormHandler` function, you need to set the `route` attribute on the `` element.

mono-jsx provides two built-in elements to allow you to control the post-submit behavior:

- `{message}` to set custom validation state for the form elements.
- `` to redirect to a new route/URL.

```tsx
async function Login(this: FC) {
return (



Login

)
}

// `FormHandler` function will be called when the form in '/login' route is submitted.
Login.FormHandler = function(this: FC, data: FormData) {
const user = await auth(data)
if (!user) {
return Invalid Username/Password
}
this.session.set("user", user)
return
}

const routes = {
"/login": Login,
// ... other routes ...
}
```

> [!NOTE]
> You can use `:invalid` CSS selector to style the form elements with invalid state.

You can also return regular HTML elements from the route form post response. The `formslot` element is used to
mark the position where the returned HTML elements will be inserted.

- ``: Replace the `formslot` element's children with the HTML. This is the default mode.
- ``: Insert HTML after the `formslot` element.
- ``: Insert HTML before the `formslot` element.

```tsx
function MyRoute(this: FC) {
return (

{/* <- new message will be inserted here */}


Send

)
}

MyRoute.FormHandler = function(this: FC, data: FormData) {
const message = data.get("message") as string | null;
if (!message) {
return Message is required
}
return

{message}


}
```

You can also use the `name` attribute to specify the name of the formslot element. And you can use the `formslot` attribute to specify the name of the slot to insert the HTML into.

```tsx
function MyRoute(this: FC) {
return (




Send


)
}

MyRoute.FormHandler = function(this: FC, data: FormData) {
return

Hello, world!


}
```

`formslot` element accepts the `onUpdate` attribute to set a callback function that will be called when the formslot element is updated.

```tsx
function MyRoute(this: FC) {
return (


Send
console.log("message updated:", evt.target.textContent)} />

)
}

MyRoute.FormHandler = function(this: FC, data: FormData) {
return

{data.get("message")}


}
```

The `hidden` attribute can be used to hide the formslot payload from the form handler.

```tsx
console.log("message updated:", evt.target.textContent)} hidden />
```

### Using `this.app.url` Signal

`this.app.url` is an app-level signal that contains the current route URL and parameters. The `this.app.url` signal is automatically updated when the route changes, so you can use it to display the current URL in your components or control the view with ``, `` or `` elements:

```tsx
function App(this: FC) {
return (


Current Pathname: {this.$(() => this.app.url.pathname)}



)
}
```

### Navigation between Pages

To navigate between pages, you can use `` elements with `href` attributes that match the defined routes. The router will intercept the click events of these links and fetch the corresponding route component without reloading the page:

```tsx
export default {
fetch: (req) => (


Home
About
Blog



)
}
```

### Nav Links

Links under the `` element will be treated as navigation links by the router. When the `href` of a nav link matches a route, an active class will be added to the link element. By default, the active class is `active`, but you can customize it by setting the `data-active-class` attribute on the `` element. You can add styles for the active link using nested CSS selectors in the `style` attribute of the `` element.

```tsx
export default {
fetch: (req) => (


Home
About
Blog



)
}
```

### Fallback (404)

You can add fallback(404) content to the `` element as children, which will be displayed when no route matches the current URL.

```tsx
export default {
fetch: (req) => (


Page Not Found


Back to Home




)
}
```

## Using Session

mono-jsx provides a built-in session storage that allows you to manage user sessions. To use the session storage, you need to set the `session` prop on the root `` element with the `cookie.secret` option.

```tsx
function Index(this: FC) {
const user = this.session.get<{ name: string }>("user")
if (!user) {
return

Please Login


}
return

Welcome, {user.name}!


}

async function Login(this: FC) {
return (



Login

)
}

Login.FormHandler = async function(this: FC, data: FormData) {
const user = await auth(data)
if (!user) {
return Invalid Username/Password
}
this.session.set("user", user)
return
}

const routes = {
"/": Index,
"/login": Login,
}

export default {
fetch: (req) => (



)
}
```

### Session Storage API

```ts
function Component(this: FC) {
// set a value in the session
this.session.set("user", { name: "John" })
// get a value from the session
this.session.get<{ name: string }>("user") // { name: "John" }
// get all entries from the session
this.session.entries() // [["user", { name: "John" }]]
// delete a value from the session
this.session.delete("user")
// destroy the session
this.session.destroy()
}
```

## Caching

mono-jsx renders HTML dynamically per request; large apps may tax your CPU resources. To improve rendering performance, mono-jsx introduces two built-in elements that can cache the rendered HTML of the children:

- `` with specified `key` and `maxAge`
- `` for elements that rarely change, such as ``

```tsx
function BlogPage() {
return (



)
}

function Icon() {
return (

...

)
}
```

## Customizing HTML Response

You can add `status` or `headers` attributes to the root `` element to customize the HTTP response:

```tsx
export default {
fetch: (req) => (

Page Not Found



)
}
```

### Using htmx

mono-jsx integrates with [htmx](https://htmx.org/) and [typed-htmx](https://github.com/Desdaemon/typed-htmx). To use htmx, add the `htmx` attribute to the root `` element:

```tsx
export default {
fetch: (req) => {
const url = new URL(req.url);

if (url.pathname === "/clicked") {
return (

Clicked!

);
}

return (


Click Me


)
}
}
```

#### Adding htmx Extensions

You can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `` element:

```tsx
export default {
fetch: (req) => (


Click Me


)
}
```

#### Specifying htmx Version

You can specify the htmx version by setting the `htmx` attribute to a specific version:

```tsx
export default {
fetch: (req) => (


Click Me


)
}
```

#### Setting Up htmx Manually

By default, mono-jsx imports htmx from the [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also set up htmx manually with your own CDN or local copy:

```tsx
export default {
fetch: (req) => (







Click Me



)
}
```

## License

[MIT](LICENSE)