Instant First-Frame Rendering (IFR) v0.5

IFR paints real content during Lynx loadTemplate, before the background thread has started — so the blank frame that waits on background boot, first Vue render, and IPC is gone.

This page is the product thesis — how IFR is fast and why that is an architecture win. Cost ledger, gzip/TTI, cost-space charts, and FCP numbers: IFR Benchmarks · unified matrix.

1. How IFR is fast

Without IFR, visible content waits for the background thread:

Main Thread:       empty page ─────────────────────────▶ apply ops ─▶ paint
Background Thread:             boot ─▶ render ─▶ IPC ──┘

With IFR, the main-thread bundle carries the Vue runtime and application. Vue renders synchronously inside loadTemplate while the background thread starts in parallel:

Main Thread:       loadTemplate ─▶ render ─▶ paint
Background Thread:              boot ─▶ render ─▶ hydrate

Two layers make that sync paint cheap enough to ship:

LayerRole
WhenIFR — paint on MT during loadTemplate; BG boot overlaps
How cheapVDOM: Element Templates with enableIFR · Vapor: template()REGISTER_TREE / CLONE_TREE (no ET)

Plain IFR without ET still replays roughly a full create path on the main thread. That is why VDOM ships IFR with Element Templates by default — see the strategy ladder on IFR Benchmarks.

2. Architecture advantage

IFR is not “a faster first paint in any harness.” It removes a blank frame that exists because of a real thread boundary. Single-process / same-isolate benches stay flat — there is no IPC wait to hide. On Lynx for Web (and native dual-thread), that blank frame is real, so IFR shows up as product FCP.

That is why the unified matrix (Worker boundary) and the IFR examples harness answer different questions: one measures the win, the other measures the bill.


Conclusions

Fast how?Paint in loadTemplate on MT; BG boot overlaps. Cheap create path (ET / Vapor trees) required at scale.
CostDual bundles · ~ gzip · sync MT work — see IFR Benchmarks.
ArchitectureWins only where a real thread boundary creates a blank frame.
ShipVDOM: enableIFR: true (brings ET). Don’t ship IFR without ET at scale. Always mount a real shell — gate fetches, don’t skip app.mount().
Skip whenNothing to paint before a fetch; large web bundles under heavy CPU throttle (measure first); or ~2× gzip is unacceptable without measuring.

Enable

lynx.config.ts
import { defineConfig } from '@lynx-js/rspeedy'
import { pluginVueLynx } from 'vue-lynx/plugin'

export default defineConfig({
  plugins: [
    pluginVueLynx({
      enableIFR: true, // VDOM: also turns on Element Templates
      // vapor: true,  // optional — Vapor IFR uses tree clone, not ET
    }),
  ],
})
HydrationSame app + initial data on both threads; structural mismatch drops the IFR win (dev log), not correctness.
First-screen rulesDeterministic render; side effects in Composition API lifecycles (suppressed on MT); Options API mounted() is not yet suppressed.
Shell IFRFetch-driven screens should still mount a useful shell on the main thread — gate network work (e.g. useQuery({ enabled: !isIfrMainThread() })), do not skip app.mount(). Skipping mount keeps the larger IFR bundle with none of the first-paint benefit.
Bisect ETenableIFR: true, enableElementTemplates: false — debug only, not a perf knob.
VaporExperimental (Vue 3.6 prerelease). See Vapor mode.

Strategy ladder, real-thread FCP, cost space, examples gzip/TTI, native: IFR Benchmarks · product FCP matrix: Unified Matrix.