{"record":{"id":"42b1ccbbcee78b27","repo":"remix-run/remix","slug":"cliententry-requires-an-entry-id","errorCode":null,"errorMessage":"clientEntry() requires an entry ID","messagePattern":"clientEntry\\(\\) requires an entry ID","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/src/runtime/client-entries.ts","lineNumber":93,"sourceCode":" *           }),\n *         ]}\n *       >\n *         {handle.props.label} {count}\n *       </button>\n *     )\n *   }\n * )\n * ```\n */\nexport function clientEntry<props extends SerializableProps = {}, context = NoContext>(\n  entryId: string,\n  component: (handle: Handle<props, context>) => RenderFn,\n): EntryComponent<props, context>\n\n// Implementation\nexport function clientEntry(entryId: string, component: any): any {\n  if (!entryId) {\n    throw new Error('clientEntry() requires an entry ID')\n  }\n\n  // Augment the component with entry metadata\n  component.$entry = true\n  component.$entryId = entryId\n\n  return component\n}\n\n/**\n * Type guard to check if a component is an entry component\n *\n * @param component The component to check\n * @returns True if the component has entry metadata\n */\nexport function isEntry(component: unknown): component is EntryComponent {\n  return Boolean(component && typeof component === 'function' && (component as any).$entry === true)\n}","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/ui/src/runtime/client-entries.ts#L75-L111","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nexport const Counter = clientEntry(componentId ?? '', CounterComponent)\n\n// after\nexport const Counter = clientEntry(componentId ?? '/js/counter.js#Counter', CounterComponent)","handlingStrategy":"validation","validationCode":"const id = computeEntryId() // may be ''\nif (!id || typeof id !== 'string') {\n  throw new Error('entry ID could not be computed')\n}\nexport const Counter = clientEntry(id, CounterComponent)","typeGuard":"function isValidEntryId(id: unknown): id is string {\n  return typeof id === 'string' && id.trim().length > 0 && id.includes('#')\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["client-entry","hydration","remix-ui","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}