Add Streaming SSR to a merjs page
SkillMediaAdd streaming SSR to a merjs page. Use when the user wants shell-first rendering, skeleton placeholders, or parallel data fetching that resolves inline.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Add Streaming SSR to a merjs page skill
What this skill tells your AI
The instructions your AI receives, as published by justrach/merjs in .claude/skills/streaming-ssr/SKILL.md and read by ahel’s review.
Scaffold or upgrade app/$ARGUMENTS.zig to use renderStream — shell-first streaming with skeleton placeholders that resolve as data arrives.
How it works
renderStreamis called instead ofrenderwhen the route is hitstream.write(html)flushes bytes to the browser immediately (chunked transfer encoding)stream.placeholder(id, skeleton_html)writes a shimmer skeleton +<div id="P:id">into the live DOMmer.fetchAll()fetches multiple URLs in parallel (threads on dev server, two-phase WASM bridge on Cloudflare Workers)stream.resolve(id, real_html)injects a hidden div + inline<script>that swaps the skeleton with real contentstream.flush()ends the response
On Cloudflare Workers, mer.fetchAll() uses a two-phase JS bridge:
- Phase 1: WASM dry-run collects URLs (
collect_fetch_urls) - Phase 2: JS fetches all in parallel via Workers native
fetch() - Phase 3: results injected into WASM cache, full render proceeds
Steps
- Create or update
app/$ARGUMENTS.zig:
const std = @import("std");
const mer = @import("mer");
pub const meta: mer.Meta = .{
.title = "PAGE_TITLE",
.description = "PAGE_DESCRIPTION",
.extra_head = "<style>" ++ page_css ++ "</style>",
};
// Fallback for non-streaming clients (required)
pub fn render(req: mer.Request) mer.Response {
_ = req;
return mer.html("<p>Requires streaming.</p>");
}
pub fn renderStream(req: mer.Request, stream: *mer.StreamWriter) void {
const alloc = req.allocator;
// Shell hits browser immediately — before any fetch
stream.write(
\\<div class="page">
\\ <h1>PAGE_TITLE</h1>
);
// Skeleton placeholders — visible in DOM while fetching
stream.placeholder("section-a",
\\<div class="skeleton">Loading...</div>
);
// Fetch multiple URLs in parallel
const results = mer.fetchAll(alloc, &.{
.{ .url = "https://api.example.com/data-a" },
.{ .url = "https://api.example.com/data-b" },
});
defer for (results) |r| if (r) |ok| ok.deinit(alloc);
// Resolve skeleton → real content inline
if (results[0]) |res| {
stream.resolve("section-a", buildCard(alloc, res.body));
} else {
stream.resolve("section-a", "<p>Failed to load.</p>");
}
stream.write("</div>");
stream.flush();
}
fn buildCard(alloc: std.mem.Allocator, body: []u8) []const u8 {
_ = body;
return std.fmt.allocPrint(alloc, "<div class=\"card\">data</div>", .{}) catch "error";
}
const page_css =
\\.page { max-width: 640px; margin: 0 auto; }
\\.card { background: var(--bg2); border-radius: 10px; padding: 20px; }
\\.skeleton { background: var(--bg3); border-radius: 10px; padding: 20px; height: 80px;
\\ position: relative; overflow: hidden; }
\\.skeleton::after { content:''; position:absolute; inset:0;
\\ background:linear-gradient(90deg,transparent,rgba(255,255,255,0.25),transparent);
\\ animation:shimmer 1.5s infinite; }
\\@keyframes shimmer { 0%{transform:translateX(-100%)} 100%{transform:translateX(100%)} }
;
- Run
zig build codegento register the route - Run
zig build serveand visit the route — watch skeletons resolve
Key rules
- MUST export both
render(fallback) andrenderStream - MUST export
pub const meta: mer.Meta stream.placeholder(id, skeleton)must come BEFOREmer.fetchAllstream.resolve(id, html)must come AFTER results are readystream.flush()must be called at the endmer.fetchAllreturns[]?mer.FetchResult— always handle the null case- Always
deferdeinit on results to free memory - The
idpassed toplaceholderandresolvemust match exactly
Live example
- Page:
app/stream-demo.zig - URL: https://merlionjs.com/stream-demo
- GitHub: https://github.com/justrach/merjs/blob/main/app/stream-demo.zig
See also
src/mer.zig—StreamWriter,fetchAll,placeholder,resolvesrc/ssr.zig— streaming enginesrc/router.zig—dispatchStream/dispatchBufferedsrc/worker.zig— two-phase fetch exports (collect_fetch_urls,provide_fetch_result)PRIMITIVES.md— full API reference
Signals
- GitHub stars
- 354
- Forks
- 17
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
streaming-ssr- Source
- github.com/justrach/merjs