Type to search the documentation.
A link-sharing page, built from an empty directory and deployed at the end. Around twenty minutes. Every step explains why, not just what.
You need Node 18 or newer. Nothing else.
The scaffold gives you two pages and a component, so routing and composition are visible from the start.
npx azoxjs create linkbox cd linkbox npm install
Start the dev server and leave it running:
npm run dev
Open http://localhost:4321. Every save rebuilds and reloads the page.
Look at pages/index.azox. Three parts, all optional except the markup.
<head> <!-- copied into the document head --> </head> <script> // imports and state </script> <main> <!-- the markup: one root element --> </main>
The <script> block runs once when the page is built, not on every update. There is no render function, so there is no "runs on every render" to worry about.
Start with static markup. No state yet — it is worth seeing that a page with nothing reactive ships no update code at all.
<head>
<title>Linkbox</title>
</head>
<main class="page">
<h1>Linkbox</h1>
<p class="tagline">Somewhere to keep good links.</p>
<ul class="links">
<li><a href="https://azox.dev">Azox</a></li>
<li><a href="https://developer.mozilla.org">MDN</a></li>
</ul>
</main>Save. The page reloads. Nothing is styled yet — that is next.
Azox has no CSS pipeline on purpose. Files in public/ are copied to the build root and served at the same path, so public/style.css is /style.css.
:root {
color-scheme: dark;
--bg: #0d0d12;
--text: #f0f0f5;
--dim: #9a9ab0;
--accent: #6ee7f0;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font: 16px/1.6 system-ui, sans-serif;
}
.page {
max-width: 34rem;
margin: 0 auto;
padding: 4rem 1.5rem;
}
h1 { font-size: 2.2rem; margin: 0 0 0.3rem; }
.tagline { color: var(--dim); margin: 0 0 2.5rem; }
.links { list-style: none; padding: 0; display: grid; gap: 0.5rem; }
.links a {
display: block;
padding: 0.9rem 1.1rem;
background: #16161f;
border: 1px solid #26263a;
border-radius: 10px;
color: var(--accent);
text-decoration: none;
}
.links a:hover { border-color: var(--accent); }Now add state. A signal holds a value; reading it in markup binds that spot in the DOM to it.
<script>
import { signal } from 'azox/reactivity';
const clicks = signal(0);
</script>
<main class="page">
<h1>Linkbox</h1>
<p class="tagline">Somewhere to keep good links.</p>
<ul class="links">
<li><a href="https://azox.dev">Azox</a></li>
<li><a href="https://developer.mozilla.org">MDN</a></li>
</ul>
<p class="count">Visited {clicks()} times</p>
<button on:click={() => clicks.set(clicks() + 1)}>
I clicked one
</button>
</main>Only the text inside .count updates when you click. The heading, the tagline and the links are never touched again after the page loads — there is no code that could touch them.
The two links are the same shape. A component declares what it accepts with props().
<script>
const { href, label, note } = props();
</script>
<li class="link">
<a href={href}>
<strong>{label}</strong>
<span class="note">{note}</span>
</a>
</li>Use it with a capitalised tag:
<script>
import Link from '../components/Link.azox';
import { signal } from 'azox/reactivity';
const clicks = signal(0);
</script>
<main class="page">
<h1>Linkbox</h1>
<ul class="links">
<Link
href="https://azox.dev"
label="Azox"
note="The framework this is built with"
/>
<Link
href="https://developer.mozilla.org"
label="MDN"
note="Web documentation"
/>
</ul>
</main>Pass a prop the component never declared and the build stops with the name of the offending prop. Silently dropping it would hide a typo.
Create pages/about.azox. It becomes /about — the file layout is the routing table, and there is nothing to register.
<head>
<title>About — Linkbox</title>
</head>
<main class="page">
<h1>About</h1>
<p class="tagline">
Built with Azox, which compiles this page into HTML
and a small module that wires up the bindings.
</p>
<p><a href="/">Back home</a></p>
</main>Link to it from the home page with a plain <a href="/about">. There is no client-side router in this version, so each navigation is a real page load — which also means each page arrives as server-rendered HTML.
doctor parses every page and resolves every component reference, so a mistake surfaces before you deploy.
npx azox doctor
Azox Framework v1.2.0 - The Sound of Future Web ✓ Node 22.11.0 ✓ Azox v1.2.0 ✓ pages/ (2 pages) ✓ / (pages/index.azox) ✓ /about (pages/about.azox) Everything looks good.
npm run build
Look at what came out. Each route is a directory with an index.html, which is what makes clean URLs work everywhere without rewrite rules.
index.html / page.client.js azox-runtime.js shared by every page about/ ├── index.html /about └── page.client.js style.css copied from public/
Serve it locally to check:
cd .azox/build python3 -m http.server 4321
The output is a plain static site, so any host works. Two settings, whichever you use:
| Setting | Value |
|---|---|
| Build command | npm run build |
| Output directory | .azox/build |
See deployment for Vercel, Netlify, Cloudflare Pages and GitHub Pages specifics.
<head> and <script> blockssignal binds one spot in the DOM to one valuepublic/ is copied as-is