remix-run/remix · error · Error

clientEntry() requires an entry ID

Error message

clientEntry() requires an entry ID

What it means

`clientEntry(entryId, component)` registers a UI component as a client hydration entry by stamping `$entry`/`$entryId` metadata onto it. The entry ID (a stable string like '/js/counter.js#Counter') is what the server uses to reference the component during SSR and what the client uses to match hydrated instances, so an empty/undefined ID is rejected immediately.

Source

Thrown at packages/ui/src/runtime/client-entries.ts:93

 *           }),
 *         ]}
 *       >
 *         {handle.props.label} {count}
 *       </button>
 *     )
 *   }
 * )
 * ```
 */
export function clientEntry<props extends SerializableProps = {}, context = NoContext>(
  entryId: string,
  component: (handle: Handle<props, context>) => RenderFn,
): EntryComponent<props, context>

// Implementation
export function clientEntry(entryId: string, component: any): any {
  if (!entryId) {
    throw new Error('clientEntry() requires an entry ID')
  }

  // Augment the component with entry metadata
  component.$entry = true
  component.$entryId = entryId

  return component
}

/**
 * Type guard to check if a component is an entry component
 *
 * @param component The component to check
 * @returns True if the component has entry metadata
 */
export function isEntry(component: unknown): component is EntryComponent {
  return Boolean(component && typeof component === 'function' && (component as any).$entry === true)
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Pass a non-empty stable entry ID as the first argument: `clientEntry('/js/counter.js#Counter', Counter)`
  2. If the ID is computed, assert it is a non-empty string before calling clientEntry
  3. Check argument order — the ID is first, the component second

Example fix

// before
export const Counter = clientEntry(componentId ?? '', CounterComponent)

// after
export const Counter = clientEntry(componentId ?? '/js/counter.js#Counter', CounterComponent)
Defensive patterns

Strategy: validation

Validate before calling

const id = computeEntryId() // may be ''
if (!id || typeof id !== 'string') {
  throw new Error('entry ID could not be computed')
}
export const Counter = clientEntry(id, CounterComponent)

Type guard

function isValidEntryId(id: unknown): id is string {
  return typeof id === 'string' && id.trim().length > 0 && id.includes('#')
}

Prevention

When it happens

Trigger: Calling `clientEntry('', Component)`, `clientEntry(undefined as any, Component)`, or passing a computed ID variable that evaluates to a falsy value (empty template string, undefined import). Typically happens at module top-level in a client entry file, so it throws during module evaluation/build.

Common situations: Typos or missing string literals when migrating components to client entries; building the ID from a variable (file path hash, build constant) that is empty in some environments; refactoring that drops the first argument or swaps arguments so the component function lands in the entryId slot.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/42b1ccbbcee78b27. Report an issue: GitHub.