All notable changes to merjs will be documented in this file.
Format based on Keep a Changelog. This project uses Semantic Versioning.
- turboapi-core dependency — merjs now imports turboapi-core, a shared Zig library providing a radix trie router, HTTP utilities (
percentDecode,queryStringGet,statusText,formatHttpDate), and a bounded response cache. This is the first step toward sharing routing primitives between merjs and turboAPI. See #66 for the full integration roadmap.
- Method-based API routing via turboapi-core's radix trie (GET vs POST on same path)
- Replace
queryParamFromStrwith turboapi-core'squeryStringGet - Optional: radix trie for dynamic page routes (perf upgrade for large route counts)
- Zig 0.16.0 support — Full migration from Zig 0.15 to 0.16. See MIGRATION_0.16.md for details.
- Cloudflare Workers installer —
merjs.trilok.ai/install.shnow serves the installer from the edge - One-line install —
curl -fsSL https://merjs.trilok.ai/install.sh | bash
- Updated all
std.net→std.Io.netAPIs - Updated
std.io→std.IoAPIs - Replaced
std.time.timestamp()→std.c.clock_gettime() - Updated
ArrayListUnmanaged .{}→.empty
- Shell-first HTML rendering — layout splits into head (CSS, meta, nav) and tail (footer, closing tags). The server flushes the head chunk immediately via chunked transfer encoding before the page's
render()runs. This is NOT true streaming SSR (render still blocks) — it's early shell flushing so the browser can start painting the layout while waiting for page content. mer.fetchAll()— parallel HTTP fetching. Spawns a thread per request, joins all. Cuts total latency to the slowest single fetch instead of the sum.<link rel="preload">hints for external scripts on sgdata pages (Leaflet, Chart.js)
- sgdata LCP 5.4s → 0.8s — root cause was render-blocking
<script>tags in<head>for Leaflet (170KB) and Chart.js (200KB). Added<link rel="preload">hints so the browser starts downloading them earlier. Layout preload hints andfetchpriority="high"also applied. - sgdata layout had duplicate wordmark element and missing
<header>tag
| Site | Score | LCP |
|---|---|---|
| merlionjs.com | 100/100 | 0.8s |
| sgdata.merlionjs.com | 99/100 | 0.8s |
| sgdata.merlionjs.com/weather | 82/100 | 4.7s (Leaflet CSS still blocking) |
0.1.0 — 2026-03-14
First stable release.
merCLI (131 KB binary) withinit,dev,build,--versioncommandsmer init <name>scaffolds a new project with embedded starter templatemer devcombines codegen + dev server in one commandmer buildruns production build with ReleaseSmall + prerender
- Cross-platform releases — macOS (aarch64/x86_64) + Linux (aarch64/x86_64) binaries published automatically via GitHub Actions
- Beta release workflow — push to
betabranch auto-publishes pre-release binaries - Stable release workflow — push a
v*.*.*tag to auto-publish a stable release with checksums - Pre-commit hooks —
zig fmt --check+zig buildon commit,zig build teston push Content-Lengthheaders on all responses (SSR, static, prerendered)<link rel="preload">andfetchpriority="high"for hero image- Explicit
width/heighton logo image to eliminate layout shift (CLS) - Versioning via
build.zig.zon,mer.version, and--versionflag - This changelog
- In-memory static file cache — disk I/O on first request only, served from memory thereafter
- Hash map router — O(1) exact route matching, replaces O(N) linear scan
- Write buffer 4 KB → 64 KB — reduces flush syscalls
- Read buffer 8 KB → 16 KB — fewer reads per request
- Arena reset between keep-alive requests — memory reuse instead of free + realloc
- CPU-based thread pool sizing — auto-scales to hardware, replaces hardcoded 128 threads
- Batch HTML/attribute escaping — writes chunks instead of byte-by-byte
- Binary stripping —
.strip = truefor release builds
| Metric | Before | After |
|---|---|---|
| Homepage SSR throughput | 2,400 req/s | 115,093 req/s (48x) |
| API JSON throughput | 2,400 req/s | 133,957 req/s (56x) |
| Avg latency | 41 ms | 0.39 ms (105x) |
| Binary size | 1.9 MB | 260 KB (-86%) |
| CLI binary | — | 131 KB |
Measured locally on Apple M-series with
wrk -t4 -c50 -d10s.
- CI E2E tests updated to use kuri (renamed from agentic-browdie)
- Homepage benchmark section updated with verified local numbers
- Deployed updated site to merlionjs.com via Cloudflare Workers
- Codebase formatted with
zig fmt(now enforced by pre-commit hook)
- Layout shift (CLS) from logo image without explicit dimensions
- Static files re-read from disk on every request (now cached)
- Thread pool hardcoded to 128 threads regardless of CPU count