|
| 1 | +--- |
| 2 | +title: Backpatching | Advanced |
| 3 | +contributors: |
| 4 | + - thejackshelton |
| 5 | +updated_at: '2025-08-31T10:17:00Z' |
| 6 | +created_at: '2025-08-31T10:17:00Z' |
| 7 | +--- |
| 8 | + |
| 9 | +# Backpatching |
| 10 | + |
| 11 | +Similar to the [Qwikloader](../qwikloader/index.mdx) that executes a small script, backpatching updates nodes already streamed on the server without waking up the Qwik runtime. |
| 12 | + |
| 13 | +> Most useful when building component libraries or apps with interdependent elements that render in varying orders. |
| 14 | +
|
| 15 | +### What it is |
| 16 | + |
| 17 | +Backpatching solves a fundamental difference between client and server rendering: |
| 18 | + |
| 19 | +**Client rendering**: Components can render in any order, then establish relationships between each other afterward. |
| 20 | + |
| 21 | +**SSR streaming**: Once HTML is sent to the browser, it's immutable—you can only stream more content forward. |
| 22 | + |
| 23 | +This creates problems for component libraries where elements need to reference each other (like form inputs linking to their labels via `aria-labelledby`). If the input streams before its label, it can't know the label's ID to set the relationship. |
| 24 | + |
| 25 | +Backpatching automatically fixes these relationships by updating attributes after the entire page has streamed, giving you the same flexibility as client-side rendering. |
| 26 | + |
| 27 | +> Note: This is not Out-of-Order Streaming. It only corrects already-sent attributes without delaying the stream. |
| 28 | +
|
| 29 | +### Example |
| 30 | + |
| 31 | +```tsx |
| 32 | +const fieldContextId = createContextId<{ isDescription: Signal<boolean> }>('field-context'); |
| 33 | + |
| 34 | +export const Field = component$(() => { |
| 35 | + const isDescription = useSignal(false); |
| 36 | + |
| 37 | + const context = { |
| 38 | + isDescription, |
| 39 | + } |
| 40 | + |
| 41 | + useContextProvider(fieldContextId, context); |
| 42 | + |
| 43 | + return ( |
| 44 | + <> |
| 45 | + <Label /> |
| 46 | + <Input /> |
| 47 | + {/* If the description component is not passed, it is a broken aria reference without backpatching, as the input would try to describe an element that does not exist */} |
| 48 | + <Description /> |
| 49 | + </> |
| 50 | + ) |
| 51 | +}) |
| 52 | + |
| 53 | +export const Label = component$(() => { |
| 54 | + return <label>Label</label>; |
| 55 | +}); |
| 56 | + |
| 57 | +export const Input = component$(() => { |
| 58 | + const context = useContext(fieldContextId); |
| 59 | + |
| 60 | + return <input aria-describedby={context.isDescription.value ? "description" : undefined} />; |
| 61 | +}); |
| 62 | + |
| 63 | +export const Description = component$(() => { |
| 64 | + const context = useContext(fieldContextId); |
| 65 | + |
| 66 | + useTask$(() => { |
| 67 | + context.isDescription = true; |
| 68 | + }) |
| 69 | + |
| 70 | + return <div id="description">Description</div>; |
| 71 | +}); |
| 72 | +``` |
| 73 | + |
| 74 | +- Without backpatching, `<Input />` would never know about `<Description />`, leading to incorrect accessibility relationships. |
| 75 | + |
| 76 | +- With backpatching, the aria-describedby attribute on `<Input>` will be automatically corrected even if `<Description>` runs after the input was streamed. |
| 77 | + |
| 78 | +### Limitations |
| 79 | + |
| 80 | +- **Attributes only**: Backpatching is currently limited to updating attributes. It does not change element children/text/structure. |
| 81 | + |
| 82 | +### How it works (high level) |
| 83 | + |
| 84 | +Here's how backpatching works under the hood: |
| 85 | + |
| 86 | +1. **During Server-side streaming**: When a component tries to update an attribute on an element that's already been sent to the browser, Qwik detects this and remembers the intended change. |
| 87 | + |
| 88 | +2. **Element Tracking**: Qwik assigns each element a unique index based on its position in the DOM tree, so it can reliably find the same element in the browser. |
| 89 | + |
| 90 | +3. **Script Generation**: Instead of blocking the stream, Qwik generates a tiny JavaScript snippet that will run later to apply the fix. |
| 91 | + |
| 92 | +4. **Browser Execution**: On page load, this script uses efficient DOM traversal to find and update the target elements with their correct attribute values. |
| 93 | + |
| 94 | +5. **Zero Runtime Impact**: This all happens without waking up the Qwik framework, keeping your app fast and lightweight. |
| 95 | + |
0 commit comments