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?
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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