Error Codes Reference

Errors follow the format BF + 3-digit code with source location and fix suggestions.

#Format

error[BF001]: 'use client' directive required for components with createSignal

  --> src/components/Counter.tsx:3:1
   |
 3 | import { createSignal } from '@barefootjs/client'
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: Add 'use client' at the top of the file

#Directive Errors (BF001–BF003)

#BF001 — Missing `"use client"` Directive

Trigger: Reactive APIs used without "use client".

// ❌ BF001
import { createSignal } from '@barefootjs/client'
export function Counter() {
  const [count, setCount] = createSignal(0)
  return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
}

Fix:

// ✅ Fixed
"use client"
import { createSignal } from '@barefootjs/client'
export function Counter() { ... }

#BF003 — Client Component Importing Server Component

Trigger: Client component imports from a file without "use client".

Fix: Add "use client" to the imported file, or import only types/constants.


#Signal Errors (BF011)

#BF011 — Module-Level Reactive Declaration

Trigger: A createSignal or createMemo call at module scope without a leading /* @client */ directive.

'use client'
import { createSignal } from '@barefootjs/client'
// ❌ BF011 — module-level signal without opt-in
const [count, setCount] = createSignal(0)
export function Counter() {
  return <button onClick={() => setCount(count() + 1)}>{count()}</button>
}

Fix (option A): Move the declaration inside the component function so each mount gets its own state.

'use client'
import { createSignal } from '@barefootjs/client'

export function Counter() {
  const [count, setCount] = createSignal(0)
  return <button onClick={() => setCount(count() + 1)}>{count()}</button>
}

Fix (option B): Prefix the declaration with /* @client */ to opt into client-only module-scope state. The signal is emitted at module scope in the client bundle and SSR renders a placeholder for any reference. Intended for "global signal" / "store" patterns shared across components.

'use client'
import { createSignal } from '@barefootjs/client'

/* @client */
const [count, setCount] = createSignal(0)

export function Counter() {
  return <button onClick={() => setCount(count() + 1)}>{count()}</button>
}

#JSX Errors (BF021–BF023)

#BF021 — Unsupported JSX Pattern

Trigger: Array method chain before .map() cannot compile to SSR template.

#SSR-Compatible Chains

  • .filter().map()
  • .sort().map() / .toSorted().map()
  • .filter().sort().map()
  • .sort().filter().map()

Other chains (.reduce(), .slice(), .flatMap()) fall back to client-side evaluation.

#filter: Supported Predicates

  • Property access: t.done, t.price
  • Literals: 'active', 5, true
  • Comparison: ===, !==, >, <, >=, <=
  • Arithmetic: +, -, *, /, %
  • Logical: &&, ||, !
  • Ternary: cond ? a : b
// ✅ SSR-compilable
{items().filter(t => !t.done).map(t => <li>{t.name}</li>)}
{items().filter(t => t.price > 100 && t.active).map(t => <li>{t.name}</li>)}

// ❌ BF021 — typeof, function calls, nested higher-order methods are not supported
{items().filter(t => typeof t === 'string').map(...)}
{items().filter(t => customFn(t)).map(...)}
{items().filter(t => t.tags.some(tag => tag.featured)).map(...)}

#sort: Supported Comparators

Simple subtraction: (a, b) => a.field - b.field:

// ✅ SSR-compilable
{items().sort((a, b) => a.price - b.price).map(...)}     // ascending
{items().toSorted((a, b) => b.date - a.date).map(...)}   // descending
{items().sort((a, b) => { return a.price - b.price }).map(...)}     // single-return block body
{items().sort((a, b) => a.name.localeCompare(b.name)).map(...)}     // zero-arg localeCompare

// A bare identifier reference to a same-file const/function comparator
// resolves one hop and compiles like the inline arrow above (#2090):
const byPrice = (a, b) => a.price - b.price
{items().sort(byPrice).map(...)}

// ❌ BF021 — locale/options localeCompare, and an unresolved comparator
// (imported, a prop, or an alias chain) are not supported
{items().sort((a, b) => a.name.localeCompare(b.name, 'ja', { numeric: true })).map(...)}
{items().sort(importedCmp).map(...)}

#Workaround

{/* @client */ todos().filter(t => t.items.some(i => i.done)).map(t => (
  <li>{t.name}</li>
))}

#Host rich-typed prop method calls (#2273)

Trigger: A method call on a prop provably typed as a built-in host rich type — Date, Map, Set, WeakMap, WeakSet, URL, URLSearchParams, RegExp, Promise, Error, Symbol, BigInt, Function — with no catalogued lowering.

// ❌ BF021 — Date.prototype.toISOString has no catalogued lowering
function Post({ createdAt }: { createdAt: Date }) {
  return <div>{createdAt.toISOString()}</div>
}

The receiver's type must be provable from the component's declared props (destructured prop, props.x member chain, loop item field, Date | null union) — an untyped, generic, or call-result receiver (d().toISOString() where d is a signal) has no evidence and is not flagged.

#Workaround

// ✅ Format in the backend and pass a string prop
function Post({ createdAt }: { createdAt: string }) {
  return <div>{createdAt}</div>
}

// ✅ Or defer to the client — but revive the receiver first
{/* @client */ new Date(createdAt).toISOString()}

The string-prop variant moves the formatting to where full language power already exists — the backend that populates the template data (Go handler, Rails controller, …) formats the Date and passes the finished string. Note that a component-body local (const iso = createdAt.toISOString()) is NOT a workaround: it lowers to a template variable whose value the template backend cannot compute, and dies at render time the same way.

The /* @client */ block must wrap the receiver in new Date(...) — a BARE {/* @client */ createdAt.toISOString()} compiles clean but crashes at real hydrate with a TypeError. Props cross the hydration boundary as JSON with no type-aware revival, so createdAt arrives at hydrate as its toJSON() ISO string, not a Date instance; wrapping it in new Date(...) revives it first, since Date's toJSON() output round-trips through its own constructor (#2636). This revival trick only works for Date and URL — every other host rich type (Map, Set, …) has no safe /* @client */ escape at all; pre-compute server-side instead.

#BF023 — Missing Key in List

Trigger: .map() loop without key prop.

// ❌ BF023
{items().map(item => <li>{item.name}</li>)}

Fix:

// ✅ Add key
{items().map(item => <li key={item.id}>{item.name}</li>)}

#Template Adapter Errors (BF101)

#BF101 — No Template-Language Lowering

Trigger: An expression that a JS-runtime adapter (Hono, CSR) executes verbatim has no lowering on a non-JS template adapter (Go, Mojo, Xslate, Twig, ERB, Blade, Jinja, MiniJinja). Two shapes are permanent known limitations rather than subset widenings:

A nested .some() / .find() inside a filter predicate (#2320) — find-family methods return an element, not a boolean, so degrading them to their receiver would silently change predicate semantics:

// ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja
{items().filter(t => picked().some(p => p.id === t.id)).map(t => <li key={t.id}>{t.name}</li>)}

A .map() loop array bound to a component-scope const with a computed initializer (#2321) — no template adapter binds an arbitrary computed local, only a prop/param it passes straight through:

// ❌ BF101 on Go/Mojo/Xslate/Twig/ERB/Blade/Jinja/MiniJinja
function ReactionBar(props: { reactions: Record<string, string[]> }) {
  const entries = Object.entries(props.reactions).filter(([, users]) => users.length > 0)
  return <div>{entries.map(([emoji, users]) => <span key={emoji}>{emoji}</span>)}</div>
}

Escapes — each verified by a conformance twin that compiles clean on the refusing adapter, listed best-SSR-first:

  • Pass the computed result as a prop (prop-precompute) — available for the loop-source shape, wherever the array is already computable server-side. Full server render: the rendered result is present in the server HTML.
  • /* @client */ (client-directive) — available for both shapes, and compiles clean on every adapter. Client-render: the region is empty in server HTML until hydration. That trade is the cost of the escape, not a bug — the twin fixtures pin the empty region in their own committed expectedHtml.
// ✅ Best for the loop-source shape: pass the computed array as a prop
function ReactionBar({ entries }: { entries: [string, string[]][] }) {
  return <div>{entries.map(([emoji, users]) => <span key={emoji}>{emoji}</span>)}</div>
}

// ✅ Either shape: defer to the client
{/* @client */ items().filter(t => picked().some(p => p.id === t.id)).map(t => (
  <li key={t.id}>{t.name}</li>
))}

See JSX Compatibility for the full worked examples.


#Component Errors (BF043–BF049)

#BF043 — Props Destructuring (Warning)

Trigger: Props destructured in function parameter.

// ⚠️ BF043
function Child({ count }: Props) {
  return <span>{count}</span>  // count is captured once
}
warning[BF043]: Destructuring props in function parameters captures values once.
   = help: Use `props.count` for reactive access, or suppress with // @bf-ignore props-destructuring

Fix options:

  1. Use direct props access:
function Child(props: Props) {
  return <span>{props.count}</span>  // Reactive
}
  1. Suppress if intentional (static initial value):
// @bf-ignore props-destructuring
function Child({ initialCount }: Props) {
  const [count, setCount] = createSignal(initialCount)
  return <span>{count()}</span>
}

#BF044 — Signal/Memo Getter Not Called

Trigger: Signal/memo getter passed without calling it.

// ❌ BF044
<Child count={count} />  // Passing getter function, not the value

Fix:

// ✅ Fixed
<Child count={count()} />

#BF049 — Rich-Typed Prop Not Hydratable

Trigger: A prop typed as a JSON-unsafe host rich type — Map, Set, WeakMap, WeakSet, URLSearchParams, RegExp, Promise, Error, Symbol, BigInt, Function — is used anywhere in this component's own client code (an event handler, an effect), regardless of whether a method is called on it. This is the sibling of BF021's host-rich-type refusal for a different shape: BF021 only walks expression positions reachable through template lowering (JSX text/attribute positions rendered at SSR); a handler or effect body is a different code path BF021 never analyzes, so even a method call there (like data.get(...) below) is just as invisible to it as a bare read. Either way the prop crosses the bf-p hydration boundary as JSON, where a Map/Set arrives de-riched ({}, every entry silently dropped) and a BigInt fails to serialize at all (TypeError at SSR render, failing the whole page).

// ❌ BF049 — a Map prop used by client code cannot survive hydration
'use client'
export function Foo({ data }: { data: Map<string, number> }) {
  return <button onClick={() => console.log(data.get('x'))}>go</button>
}

Fix: Pre-compute a JSON-serializable value server-side and rebuild the rich value client-side where it's actually needed.

// ✅ Fixed
'use client'
export function Foo({ entries }: { entries: [string, number][] }) {
  return <button onClick={() => console.log(new Map(entries).get('x'))}>go</button>
}

Date and URL props are exempt — their toJSON() output round-trips through their own constructor, so they're not JSON-unsafe (see BF021's host-rich-type section above).

This is a compile-time check: it only fires when the prop's type is provable from the component's own props type (same evidence checkRichTypeMethodCalls uses). An imported/aliased type alias, or a prop typed too loosely to resolve statically, isn't caught here — on the Hono adapter, an unsound value reaching hydration serialization throws a clear runtime error naming the prop and this code instead of failing silently or with an opaque JSON.stringify error.

#BF054 — Built-in `` / `` Used Without Import

Trigger: A bare <Async> or <Region> tag is used without importing it from @barefootjs/client, and no other binding with that name is in scope. These compiler built-ins are recognised by their import (not by tag name), so an unimported tag is treated as an undeclared component.

// ❌ BF054
export function Page() {
  return <Async fallback={<p>Loading…</p>}><Body /></Async>
}

Fix: Import the built-in from @barefootjs/client.

// ✅ Fixed
import { Async } from '@barefootjs/client'

export function Page() {
  return <Async fallback={<p>Loading…</p>}><Body /></Async>
}

A component of your own named Async / Region does not trip BF054 as long as it is imported or declared — the built-in only applies to the @barefootjs/client import.


#Suppressing Warnings

Suppress with @bf-ignore:

// @bf-ignore props-destructuring
function Component({ checked }: Props) {
  // Warning suppressed
}

Available rules:

Rule ID Error Code Description
props-destructuring BF043 Props destructuring in function parameters

#Error Code Quick Reference

Code Severity Description
BF001 Error Missing "use client" directive
BF003 Error Client component importing server component
BF011 Error Module-level reactive declaration without /* @client */
BF021 Error Unsupported JSX pattern for SSR
BF023 Error Missing key in list
BF043 Warning Props destructuring breaks reactivity
BF044 Error Signal/memo getter passed without calling it
BF049 Error Rich-typed prop read by client code cannot survive hydration
BF054 Error Built-in <Async> / <Region> used without @barefootjs/client import