emroute
Zero dependencies · Triple rendering

emroute

A file-based, storage-agnostic TypeScript router with triple rendering — SPA, SSR HTML, and SSR Markdown — and zero external dependencies.

Get Started · Pages · Architecture

deno add @emkodev/emroute or npm · bun · pnpm
GET /html/about rendered HTML
GET /md/about raw markdown
GET /app/about SPA navigation
01

Triple rendering

One component, three views. /html/* for browsers, /md/* for LLMs and shell scripts, /app/* for the SPA. Same content, three audiences, no duplication.

02

Zero dependencies

Native platform APIs only — Custom Elements, Shadow DOM, Navigation API, URLPattern, setHTMLUnsafe. No virtual DOM, no transpilation runtime, no framework runtime. Just a router.

03

File-based routing

The filesystem is the router. routes/about.page.md/about. Add a file, get a route. Dynamic segments via [id]. Nested layouts via directory index. No config file.

See it for yourself

The same emroute widget renders differently depending on the SPA mode it runs in. Open this page at /html/ and /app/ side-by-side — the clock below is the same component, but only one of them ticks.

In none (/html/) the server captures the time at request and ships a static <time> snapshot — refresh to update. In only (/app/) the client runs getData() and a setInterval started by hydrate() makes it tick live. In leaf and root modes the server renders the snapshot and the client hydrates: SSR-fast first paint, then live.

Same widget contract, anything inside. The counter below is rendered by Preact loaded on-demand from a CDN — the widget itself only owns the mount point and the hydrate() boundary:

Get started in 60 seconds

Or jump straight to the Pages reference, Widgets for interactive units, or the Architecture overview.