# Fre's Offscreen Mode: How It Powers Suspense in the Fre Framework

> Discover Fre's Offscreen mode for creating suspenseful UIs. Render subtrees without DOM commit, allowing background content preparation with Suspense.

- Repository: [frejs/fre](https://github.com/frejs/fre)
- Tags: deep-dive
- Published: 2026-03-02

---

**Fre's Offscreen mode is a fiber flag that renders subtrees without committing them to the DOM, enabling Suspense to prepare primary content in the background while displaying a fallback UI.**

Fre is a lightweight React-like library that implements a concurrent fiber-based reconciler. Its Offscreen mode is the core mechanism that makes Suspense boundaries possible, allowing the framework to prepare expensive UI updates in memory while keeping the fallback visible. This article examines the implementation details in `frejs/fre` to show exactly how Offscreen mode works and how it integrates with Suspense.

## What Is Fre's Offscreen Mode?

Offscreen mode is defined as a binary flag in the `MODE` enum located in [`src/type.ts`](https://github.com/frejs/fre/blob/main/src/type.ts):

```ts
// src/type.ts
export const enum MODE {
  OFFSCREEN = 1 << 1   // flag value: 2
}

```

Every fiber node in Fre carries a `mode` property that can hold multiple flags. When `MODE.OFFSCREEN` is set, the fiber and its entire subtree are treated as **invisible to the DOM**. The commit phase explicitly checks for this flag in [`src/commit.ts`](https://github.com/frejs/fre/blob/main/src/commit.ts):

```ts
// src/commit.ts
export const commit = (fiber?: FiberFinish) => {
  if (!fiber) return
  // Off-screen fibers are skipped during DOM insertion
  if (fiber.mode & MODE.OFFSCREEN) return commitSibling(fiber.sibling)
  // ... normal DOM insertion logic
}

```

This early return prevents Offscreen fibers from being inserted, updated, or removed from the actual DOM. However, the reconciler still fully processes these fibers—running component logic, executing hooks, and building the fiber tree—just without the final DOM commit.

## How Offscreen Mode Powers Suspense

Suspense in Fre is implemented entirely within the reconciler ([`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts)). When a component inside a Suspense boundary throws a Promise, Fre catches it and invokes `suspenseRender`. This function creates **two distinct child fragments** under the Suspense boundary:

1. **Primary fragment**: Contains the actual UI that depends on the async data. It is created with `mode: MODE.OFFSCREEN`, rendering it in memory but keeping it out of the DOM.
2. **Fallback fragment**: Contains the loading UI. It is rendered normally and immediately committed to the DOM.

The relevant logic from [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) demonstrates this:

```ts
// src/reconcile.ts (excerpt)
const suspenseRender = (fiber, promise) => {
  const boundary = getBoundary(fiber, Suspense)
  
  // Primary UI – rendered off-screen
  const primaryChildFragment = {
    type: null,
    props: { children: primaryChildren },
    mode: MODE.OFFSCREEN,   // hides until promise resolves
    kids: [],
  }
  
  // Fallback UI – rendered immediately
  const fallbackFragment = simpleVnode(boundary.props.fallback)
  fallbackFragment.key = SUSPENSE_FALLBACK_KEY
  
  // Reconcile both fragments under the Suspense boundary
  reconcileChildren(boundary, [primaryChildFragment, fallbackFragment])
}

```

When the promise resolves, Fre schedules an update on the Suspense boundary. During the subsequent reconciliation, the primary fragment is recreated **without** the `MODE.OFFSCREEN` flag. The commit phase then inserts the primary UI into the DOM and removes the fallback, completing the transition.

## Step-by-Step Execution Flow

The interaction between Offscreen mode and Suspense follows a precise lifecycle:

| Phase | Action | Offscreen Behavior |
|-------|--------|-------------------|
| **Render** | Component throws a Promise inside Suspense boundary. | `suspenseRender` creates primary fragment with `mode: MODE.OFFSCREEN`. |
| **Reconcile** | Both primary (off-screen) and fallback fragments are reconciled. | Primary fibers are built in memory; fallback fibers are prepared for DOM insertion. |
| **Commit** | Commit loop processes the fiber tree. | Fibers with `mode & MODE.OFFSCREEN` are skipped via early return in `commit()`. Only fallback appears in DOM. |
| **Resolve** | Promise settles; `update(boundary)` is scheduled. | New reconciliation begins; primary fragment recreated without OFFSCREEN flag. |
| **Commit (final)** | Second commit phase executes. | Primary UI is inserted into DOM; fallback is removed. Offscreen flag is absent, so commit proceeds normally. |

## Practical Implementation Example

The repository includes a working demonstration in [`demo/src/suspense.tsx`](https://github.com/frejs/fre/blob/main/demo/src/suspense.tsx). The essential pattern involves using `lazy` to create a component that suspends, wrapped in a `Suspense` boundary:

```tsx
import { render, lazy, Suspense, h, useState } from '../../src/index'

// Component that "loads" after 1 second
const Lazy = lazy(() => new Promise(resolve =>
  setTimeout(() => resolve({ default: () => <div>Loaded Content</div> }), 1000)
))

export function App() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      
      <Suspense fallback={<div>Loading...</div>}>
        {/* Rendered off-screen until promise resolves */}
        <Lazy />
      </Suspense>
    </div>
  )
}

render(<App />, document.getElementById('app'))

```

**Browser behavior:**
- **Immediately**: The button and "Loading..." text appear. The `Lazy` component's fiber tree is constructed in memory with `MODE.OFFSCREEN` active.
- **After 1 second**: "Loading..." disappears and "Loaded Content" appears. The Offscreen flag was cleared during the update, allowing the commit phase to insert the prepared DOM nodes.

## Key Source Files

Understanding Fre's Offscreen implementation requires examining these specific locations:

| File | Relevant Code | Purpose |
|------|-------------|---------|
| [`src/type.ts`](https://github.com/frejs/fre/blob/main/src/type.ts) | `export const enum MODE { OFFSCREEN = 1 << 1 }` | Defines the bitmask flag that marks fibers as off-screen. |
| [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) | `mode: MODE.OFFSCREEN` in `primaryChildFragment` within `suspenseRender` | Assigns the Offscreen flag to the primary content of a Suspense boundary during initial render. |
| [`src/commit.ts`](https://github.com/frejs/fre/blob/main/src/commit.ts) | `if (fiber.mode & MODE.OFFSCREEN) return commitSibling(fiber.sibling)` | Prevents DOM insertion for off-screen fibers during the commit phase. |
| [`demo/src/suspense.tsx`](https://github.com/frejs/fre/blob/main/demo/src/suspense.tsx) | Example usage of `<Suspense>` with `lazy` components | Demonstrates the practical effect of Offscreen mode in a running application. |

## Summary

- **Fre's Offscreen mode** is a fiber flag (`MODE.OFFSCREEN`) defined in [`src/type.ts`](https://github.com/frejs/fre/blob/main/src/type.ts) that prevents DOM insertion while allowing full component rendering.
- **Commit phase skipping** occurs in [`src/commit.ts`](https://github.com/frejs/fre/blob/main/src/commit.ts), where fibers with the Offscreen flag are bypassed via an early return, leaving the subtree in memory but invisible to the user.
- **Suspense integration** leverages Offscreen mode by wrapping the primary async-dependent UI in an Offscreen fragment during the pending state, while rendering the fallback normally.
- **Transition mechanism** triggers when the promise resolves: the boundary updates, the primary fragment loses its Offscreen flag, and the commit phase inserts the prepared UI while removing the fallback.

## Frequently Asked Questions

### What is the difference between Fre's Offscreen mode and CSS visibility?

Fre's Offscreen mode operates at the reconciler level, not the presentation layer. While CSS `visibility: hidden` or `display: none` hide elements that are already in the DOM, Fre's `MODE.OFFSCREEN` prevents the DOM nodes from being inserted entirely during the commit phase. The fiber tree is built and hooks are executed, but no DOM operations occur until the flag is cleared, making it fundamentally a rendering strategy rather than a styling technique.

### Does Offscreen mode work with concurrent features in Fre?

Yes, Offscreen mode is designed to work within Fre's concurrent architecture. Because the reconciler can pause and resume work, an Offscreen fiber tree can be prepared in the background across multiple time slices without blocking the main thread. The commit phase is the synchronous barrier where the decision to skip (due to OFFSCREEN) or insert (when cleared) is made atomically, ensuring consistency with Fre's concurrent rendering model.

### How does Fre handle cleanup for Offscreen fibers?

Cleanup for Offscreen fibers follows the standard fiber destruction path. When a Suspense boundary updates and the primary content replaces the fallback, the Offscreen flag is removed from the new primary fragment, but the previous fallback fibers are deleted. If an Offscreen fiber tree is discarded entirely (e.g., the Suspense component unmounts), Fre's reconciler walks the fiber tree and executes cleanup effects (like `useEffect` return functions) even for fibers that were never committed to the DOM, ensuring no memory leaks occur for prepared-but-unshown UI.

### Can Offscreen mode be used outside of Suspense boundaries?

While the primary implementation in Fre couples Offscreen mode with Suspense via `suspenseRender`, the underlying flag system in [`src/type.ts`](https://github.com/frejs/fre/blob/main/src/type.ts) and the commit phase logic in [`src/commit.ts`](https://github.com/frejs/fre/blob/main/src/commit.ts) are generic. In theory, any fiber could be marked with `MODE.OFFSCREEN` to prepare UI in the background. However, the current Fre codebase specifically utilizes this mechanism within the Suspense reconciler logic to handle async dependencies, making Suspense the practical and intended use case for Offscreen rendering in this framework.