Your server renders the page.
Aegis makes parts of it alive.

One ES module, no build step, no npm. Django, Laravel, Rails, Go, PHP: keep your templates, add data-aegis where the page has to react.

import { island } from 'https://aegisjs.com/0.7.0/aegis.min.js'

Pinned, with an integrity hash: the import map. v0.7.0 · MIT · when not to use it

page.html — rendered by Django / Rails / Laravel / Go / PHP …
<div data-aegis="counter" data-start="5">
    <button>Clicked 5 times</button>
</div>

<script type="module">
import { island } from 'https://aegisjs.com/0.7.0/aegis.min.js';

island('counter', ({ props, signal, html }) => {
    const count = signal(props.start);
    return html`<button @click=${() => count.value++}>
        Clicked ${count} times
    </button>`;
}, { types: { start: Number } });
// no mount() call: every [data-aegis] on the page is hydrated after island()
</script>
0build steps, npm packages, dependencies
7 · 30 · 93 KBgzip, production build: signals alone · islands + templates · everything. The pinned CDN file is the whole engine with dev warnings, 108 KB — which size is which
1085tests green in Chrome, Firefox and WebKit on every push
v0.7.0MIT · one module, no dependencies · what 0.x means

Where are you coming from?

Four doors. Each one is two minutes to your own page, not a tour of the engine.

Four rules, no surprises

The whole mental model fits on a card.

  1. ${x.value} is a snapshot, ${x} and ${() => …} are live. A signal or a function in a template re-renders on change; a plain value renders once (dev warns with E019).
  2. Everything lives in a scope. Effects, listeners, timers and resources created inside island() / mount() die with the component. Outside a scope you get E001.
  3. Use the ctx versions. on, effect, interval, observe from the setup context are bound to the component's scope; the imported ones are not.
  4. Data never goes through innerHTML. Data goes through ${} in html`` (text nodes, never parsed). Server HTML goes through swap() / adopt().

Three jobs it does on your page

The server keeps rendering. Aegis takes the parts that have to react.

A live piece on a server page

<div data-aegis="likes" data-count="12"> is rendered by the server, visible at once, indexed; the island replaces it with a live template and keeps working after any swap.

Getting started →

A form that works without JavaScript

Your <form> submits as it always did. wireForm() adds live validation from the browser's own rules and lands a 422 on the right fields.

See the form →

A list or table with search and cache

resource() turns a query signal into requests that abort, dedupe, cache and revalidate; list() keeps rows keyed and moves only what changed.

See the table →

Also in the same file, when a page needs it: a router · swap and morph for server fragments · offline and optimistic updates · springs and FLIP · focus traps and live regions · typed attribute sinks. All of it in the API reference.

Works with what you have

No npm required. The same island file for Django, Rails, Laravel, Go and PHP; markup inserted by htmx, Turbo or jQuery is hydrated too.

import { island, mutation, api, configure } from 'aegis';

configure({ csrf: 'django' });   // or 'laravel', 'rails', { header, cookie }

island('likes', ({ props, signal, html }) => {
    const count = signal(props.count);
    const like = mutation(() => api.post(`/api/products/${props.id}/like`), {
        optimistic: () => { count.value++; },
        onError: () => { count.value--; },
    });
    return html`<button @click=${like} ?disabled=${like.pending}>${count} likes</button>`;
}, { types: { id: Number, count: Number } });

Ten names

Aegis exports more, but these ten are the core: stable through 0.x, enough for most pages. The rest is for specific jobs.

Which name for which job →

Numbers, with the neighbours

A 1 000-row table is created in 17.1 ms (median of 5, headless Chrome, bench.html). Numbers without a neighbour are a poster, so the site has a second page that runs the same three jobs in your own browser next to Alpine and Vue: a 10 000-row table, a search filter, an optimistic PATCH.

Run the comparison

Add one script tag. Ship.

No npm, no bundler, no config. A pinned URL with a hash, on the pages your server already renders.