Flight Wire Format
React Server Components use a streaming protocol called Flight to transfer the component tree from server to browser. You do not need to understand Flight to use ruact — but knowing the basics helps when debugging unexpected output or adding support for new Ruby types.
What is Flight?
Flight is a line-based text format where each line is a row encoding one piece of the React element tree:
1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]
0:["$","$L1",null,{"postId":42}]- Row
0is always the root — the top-level React element tree for the page. - Row
1,2, ... are supporting data: import registrations, async chunks, or referenced sub-trees.
Row types
| Prefix | Meaning | Example |
|---|---|---|
| (none, just JSON) | Model row — a JSON value | 0:{"title":"Hello"} |
I | Import row — registers a client module | 1:I["/Btn.jsx","Button",["/Btn.jsx"]] |
E | Error row — encodes a serialized error | 2:E{"message":"Not found"} |
Row IDs are hexadecimal: 0, 1, 2, ..., 9, a, b, ..., f, 10, 11, ...
How Ruby values map to Flight rows
| Ruby value | Flight output |
|---|---|
nil | 0:null |
true / false | 0:true / 0:false |
42 | 0:42 |
"hello" | 0:"hello" |
"$danger" | 0:"$$danger" ← $ prefix escaped to $$ |
[1, "a", true] | 0:[1,"a",true] |
{ debug: true } | 0:{"debug":true} |
ClientReference | Import row + model row referencing $L{id} |
Client component reference example
When your ERB includes <LikeButton postId={@post.id} />, ruact emits:
1:I["/LikeButton.jsx","LikeButton",["/LikeButton.jsx"]]
0:["$","$L1",null,{"postId":42}]Breaking it down:
- Row 1 (import): registers
/LikeButton.jsxwith export name"LikeButton", bundle chunks["/LikeButton.jsx"] - Row 0 (root): a React element —
["$", type, key, props]:"$"— the React element marker"$L1"— the type: a lazy reference to import row1(the LikeButton module)null— the element key{"postId":42}— the props
Why import rows come first
The browser processes rows as they arrive. A model row that references "$L1" cannot be rendered until import row 1 is registered. ruact always emits import rows before the model rows that reference them.
Debugging Flight output
The simplest way to see the raw Flight output for any page:
curl -H "Accept: text/x-component" http://localhost:3000/posts/1The text/x-component Accept header tells ruact to return only the Flight payload (no HTML shell). This is what the browser sends during client-side navigation.
Adding support for a new Ruby type
If you need ruact to serialize a custom Ruby type, follow the fixture-first workflow described in CONTRIBUTING.md:
- Write a
.txtfixture with the expected wire output - Write a failing spec using
match_flight_fixture - Add a
case/whenbranch ingem/lib/ruact/flight/serializer.rb - Verify the spec passes with no regressions