Hologram v0.9 is out! The headline is a realtime layer - your server can now push updates to connected clients with no polling: broadcast an action, and Hologram runs the matching handler on every subscribed client, all in pure Elixir. It was the most complex feature shipped to date. The release also brings the with special form, AI assistant support, a new mix holo task, and more.
Thanks to @prehnRA for the with special form, @ankhers for cutting dev-time memory usage, @mward-sudo for a Node.js fix, @0bvim for porting :erlang.binary_to_term/1, and @sodapopcan for optional page/component callbacks.
When you say POST up, SSE down, do you mean all down stream messages are coming from SSE? In other word, does the reply from POST carry meaningful messages? If they don’t, do you need to have some extra metadata to keep track of the causality between the POST and the resulting message from the SSE channel? If both reply of POST and SSE carry messages, do you need to worry about ordering difference caused by 2 different channels?
A command is a plain POST to /hologram/command. The client sends the command name, params, and target component, the server runs your command/3 handler, and the resulting action comes back in that same response body.
That round trip is async with respect to the event loop: the client fires the POST as a fetch and keeps running, and when the response resolves the action is scheduled onto the normal action queue, like any other action. So “comes back in the response” means in-band rather than over a separate channel, not blocking.
SSE handles the other direction, broadcasting. Subscribed clients hold open an EventSource stream, and a broadcast goes out via pub/sub to every subscribed instance, which runs the matching handler locally.
The two meet in one place: if a command broadcasts on a channel the originating instance is itself subscribed to, that broadcast piggy-backs on the POST response too (a self-echo), and the instance is dropped from the SSE fan-out for it. So the originator gets both its command’s result and its own broadcasts back in the one response it’s already awaiting, and SSE only carries broadcasts out to the other clients.
Thanks for the explanation. My concern is that the server and the different client can see events in different order. Let’s say client A broadcasted message_a and client broadcasted message_b, each will have a corresponding outcome. Will it be possible that client A see outcome_a then outcome_b, but client B see outcome_b then outcome_a?
Also, let’s assume the server side see message_b before message_a and compute outcome_b before outcome _a. However, outcome_a may reach client A sooner than outcome_b (different channels, no enforced order). Will it confuse client A?
Both can happen, yes. The realtime layer is an abstraction over pub/sub, so it’s fire-and-forget with no ordering guarantee, the same semantics as Phoenix.PubSub or Phoenix.Channels. When you use it, ordering and data consistency are yours to manage.
That’s deliberate, and it’s exactly the gap the upcoming Local-First auto-sync data layer fills (I touched on this briefly in my ElixirConf EU 2026 talk). Instead of broadcasting actions, you subscribe to state, and the framework handles ordering and consistency for you. It will most likely be built on top of this same realtime layer.
The two are complementary, not either-or. Even once auto-sync lands, the raw realtime layer keeps its place for ephemeral, high-frequency events like cursor tracking, typing indicators, or live reactions, where each update supersedes the last and ordering or persistence would just be overhead.
There is still a difference. With Phoenix.Channel, I can manage ordering and data consistency at the server side, where I was better equipped. In your case, the hard work would need to happen on the client side.
By the way, I am developing something in a similar vein (in a much reduced scope, of course). I also use 2 communication channels (one POST for upstream and one long poll for downstream). In my design, all downstream communications happen in the downstream channel and the replies of POST carry no payload.
At the low level the realtime layer sits on Phoenix.PubSub, the same primitive Phoenix.Channels builds on, so the ordering guarantee is identical. Hologram broadcasts go through Phoenix.PubSub.broadcast, and each instance’s downstream stream is a single process writing to a single connection, just like a channel pushing through one socket. So per instance you get FIFO in mailbox order, and cluster-wide neither one gives you a total order: two concurrent broadcasts from different processes or nodes can land in different orders on different subscribers in either system. Channels doesn’t buy you a stronger ordering guarantee here, it’s the same pub/sub primitive underneath.
And the server-vs-client framing is really about where state lives by default, not the transport. Hologram puts UI state on the client, so naive reconciliation lands there, but it’s the same Elixir server underneath, so you can keep authoritative state server-side and order or version it there just as you would with Channels.
Quick update for anyone who’s already grabbed it: v0.9.1 is out It fixes a compile-time slowdown that can show up in projects with deeply nested templates - thanks @absowoot who flagged it. If you’re on v0.9, a quick bump keeps your builds fast.
Good catch - thanks! with was indeed stale: it’s fully supported, so it’s now marked done. Your comment also prompted a wider pass, and the Features and Roadmap pages are both up to date now.
These had already shipped but were still mislabeled, and are now marked done:
with Expression
JS Interop
Essential Stdlib Functions
Bitwise Module Operators
Exponentiation (**)
VS Code Extension
Server-Triggered Actions → Pub/Sub
One note on that last one: Server-Triggered Actions was originally scoped as a much simpler feature - just firing actions from server-side code. As I built it out, though, it grew into a full realtime layer, so it ended up shipping as Pub/Sub, which supersedes that narrower idea. More roadmap items coming soon - appreciate the nudge!