# How Fre's render() Function Works: A Deep Dive into the Main Entry Point

> Understand how Fre's render() function bootstraps the rendering process. Learn about virtual nodes, DOM containers, Fiber trees, and reconciliation tasks.

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

---

**Fre's `render()` function in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) bootstraps the rendering process by accepting a virtual node and DOM container, creating an initial Fiber tree, and queuing the first reconciliation task for the scheduler.**

The `render()` function serves as the primary public API in the **frejs/fre** repository, translating virtual DOM descriptions into concrete browser UI. As implemented in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts), this main entry point bridges declarative component trees with the underlying Fiber-based reconciliation engine. It initializes the work-in-progress tree and delegates execution to the cooperative scheduler, initiating the full reconciliation lifecycle.

## The Anatomy of Fre's render() Function

### Source Location and Signature

The `render()` implementation resides at **lines 21-30** of [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts). This function receives two critical arguments: a **virtual node** (`vnode`) representing the root of your UI tree, and a real **DOM container** (`node`) where the application mounts. 

```typescript
// Conceptual signature based on src/reconcile.ts
function render(vnode: VNode, node: HTMLElement): void

```

Upon invocation, `render()` immediately captures the container's existing first child as `currentDom`. This reference enables **server-side hydration** support by allowing Fre to reuse existing DOM nodes rather than recreating them from scratch.

### Root Fiber Initialization

Inside `render()`, the function constructs a **rootFiber** object—a lightweight data structure representing the initial unit of work. This fiber's `props.children` property is populated with the supplied virtual node, effectively wrapping your application root in a work unit that the scheduler can process. The root fiber acts as the starting point for the entire reconciliation traversal.

## From render() to the Scheduler

### Enqueuing Work with update()

Immediately after fiber creation, `render()` invokes `update(rootFiber)` at **lines 32-36** of [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts). The `update()` function performs two critical operations: it marks the fiber as dirty (indicating pending changes) and pushes a task into the internal **task queue**. This transition moves the work from the synchronous setup phase into the asynchronous scheduling system.

### Cooperative Time-Slicing

The scheduler implementation in [`src/schedule.ts`](https://github.com/frejs/fre/blob/main/src/schedule.ts) manages execution through `schedule(callback)`, which appends the reconciliation callback to a queue and triggers `startTransition`. The `flush` function then repeatedly executes tasks while `shouldYield()` returns false, checking against a `deadline` time slice. 

This **cooperative multitasking** approach ensures that rendering yields control to the browser when approaching frame boundaries, preventing layout thrashing and maintaining responsive user interactions during heavy component trees.

## The Reconciliation Work Loop

### The capture() Phase

Once scheduled, the reconciliation loop begins at `reconcile(fiber)` in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) (**lines 39-44**). The `reconcile` function walks the fiber tree by invoking `capture()` on each node (**lines 86-101**). This phase determines whether the current fiber represents a **component** (function or class) or a **host element** (DOM node), routing each to specialized handlers.

### Component Processing via updateHook()

For component fibers, `capture()` delegates to `updateHook()` (**lines 56-70**). This function:
- Resets the hook cursor for fresh state management
- Prepares a fragment node to contain return values
- Executes the component function and recursively reconciles its children
- Intercepts thrown `Promise` objects to trigger **Suspense** fallbacks
- Catches other errors to activate **ErrorBoundary** recovery

### Host Element Handling via updateHost()

Host elements traverse through `updateHost()` (**lines 72-92**), which handles concrete DOM operations. This function creates or reuses existing DOM nodes via `createElement`, updates properties and attributes, and manages special modes like SVG context switching. When `currentDom` is present from server rendering, `updateHost` performs hydration diffing instead of full node creation.

## Diffing and Commit

### The Diff Algorithm

As the work loop traverses children, `reconcileChildren` (**lines 97-108**) constructs the new child list and invokes the `diff` algorithm. This comparison produces a deterministic set of actions—**INSERT**, **UPDATE**, **MOVE**, or **REMOVE**—which are stored on each child fiber. The diff algorithm optimizes for minimal DOM manipulation by leveraging key-based reconciliation similar to other modern virtual DOM implementations.

### Commit Phase

When a fiber completes processing (the `bubble` phase), side effects from hooks such as `useEffect` execute. Finally, the `commit` function applies the queued DOM actions to the real browser document, ensuring the UI reflects the virtual tree's current state. Once the work queue empties, `flush` stops scheduling frames and the application enters an idle state until the next `render()` call or state update.

## Practical Usage Examples

### Basic Component Mounting

```tsx
import { h, render } from 'fre';

function App() {
  return h('div', null,
    h('h1', null, 'Hello, Fre!'),
    h('p', null, 'Fast, lightweight UI.')
  );
}

const container = document.getElementById('root');
render(h(App, null), container);

```

### Concurrent Features with Suspense

```tsx
import { h, render, useState, Suspense, lazy } from 'fre';

const LazyComponent = lazy(() => import('./Heavy'));

function Counter() {
  const [count, setCount] = useState(0);
  return h('button', { 
    onClick: () => setCount(c => c + 1) 
  }, `Clicked ${count}`);
}

function App() {
  return h(Suspense, { fallback: h('div', null, 'Loading…') },
    h(LazyComponent, null),
    h(Counter, null)
  );
}

render(h(App, null), document.getElementById('root'));

```

### Server-Side Hydration

```tsx
import { h, render } from 'fre';

// Container contains HTML from server rendering
const container = document.getElementById('root');

// Fre automatically hydrates using existing DOM nodes
render(h(App, null), container);

```

## Summary

- **`render()`** in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) acts as the thin public wrapper that initializes the Fiber architecture and triggers the scheduler.
- The function stores `node.firstChild` as `currentDom` to enable **SSR hydration** and efficient DOM reuse.
- **Root fiber creation** packages the virtual tree into a work unit compatible with the reconciliation loop.
- **Cooperative scheduling** via [`src/schedule.ts`](https://github.com/frejs/fre/blob/main/src/schedule.ts) ensures time-sliced rendering that yields to the browser.
- The **capture phase** distinguishes components (processed by `updateHook`) from host elements (processed by `updateHost`).
- **Diff and commit phases** translate virtual changes into minimal, batched DOM updates.

## Frequently Asked Questions

### Where is Fre's render function defined?

Fre's `render()` function is defined in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) at lines 21-30. This module also contains the core reconciliation logic including `update()`, `capture()`, and the diffing algorithms.

### How does Fre's render function support server-side rendering?

The `render()` function accepts a DOM container that may already contain HTML markup. It stores `node.firstChild` as `currentDom`, which `updateHost()` uses during the reconciliation process to hydrate existing server-rendered elements rather than creating new DOM nodes from scratch.

### What happens immediately after calling render()?

After `render()` constructs the root fiber, it synchronously calls `update(rootFiber)` to mark the fiber dirty and enqueue the first task. Control then returns to the browser until the scheduler's `flush` function begins processing the work loop asynchronously.

### Why does Fre use a Fiber-based architecture for rendering?

The Fiber architecture enables **incremental rendering** by breaking work into small units that can be prioritized, paused, and resumed. This supports concurrent features like Suspense and Error Boundaries while maintaining 60fps performance by yielding to the browser's main thread between time slices.