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
- Pass a non-empty stable entry ID as the first argument: `clientEntry('/js/counter.js#Counter', Counter)`
- If the ID is computed, assert it is a non-empty string before calling clientEntry
- 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
- Use literal entry IDs like '/js/counter.js#Counter' instead of computed values
- Type the ID parameter as a template-literal string so '' fails typecheck where possible
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
- hmr must create a channel with a close function
- hmr must create a channel with an onFileEvents function
- hmr must create a channel with an updateWatchedFiles functio
- basePath must be a string
- fingerprint.buildId must be a string
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/42b1ccbbcee78b27.
Report an issue: GitHub.