clockworklabs/SpacetimeDB · error · TypeError
HTTP handler '${exportName}' was exported more than once
Error message
HTTP handler '${exportName}' was exported more than once What it means
HTTP handlers are created via makeHttpHandlerExport and must be registered exactly once when the module's exports are processed. Each handler object is tracked in the module-level exportedHttpHandlerObjects WeakSet; if the same handler object is registered again under another export name, the SDK throws, because one handler object must map to exactly one export name and schema entry.
Source
Thrown at crates/bindings-typescript/src/server/http_handlers.ts:373
throw new TypeError(`Route conflict for \`${path}\``);
}
return new Router([...this.#routes, candidate]);
}
}
export function makeHttpHandlerExport<S extends UntypedSchemaDef>(
ctx: SchemaInner,
opts: HttpHandlerOpts | undefined,
fn: HandlerFn<S>
): HttpHandlerExport<S> {
const handlerExport: HttpHandlerExport<S> = Object.assign(
(...args: Parameters<HandlerFn<S>>) => fn(...args),
{
[httpHandlerFn]: fn,
[exportContext]: ctx,
[registerExport](ctx: SchemaInner, exportName: string) {
if (exportedHttpHandlerObjects.has(handlerExport)) {
throw new TypeError(
`HTTP handler '${exportName}' was exported more than once`
);
}
exportedHttpHandlerObjects.add(handlerExport);
registerHttpHandler(ctx, exportName, fn, opts);
ctx.httpHandlerExports.set(
handlerExport as HttpHandlerExport<UntypedSchemaDef>,
exportName
);
},
}
);
return handlerExport;
}
export function makeHttpRouterExport(
ctx: SchemaInner,
router: RouterView on GitHub (pinned to 524b4487d9)
Solutions
- Create a separate handler per export name with its own httpHandler/makeHttpHandlerExport call instead of re-exporting the same object
- Remove the redundant re-export so each handler is exported from exactly one module under exactly one name
- If hot-module-reload triggers it during development, restart the dev server so the export table is rebuilt once
Example fix
// before
// users.ts
export const getUser = makeHttpHandler(...);
// index.ts
export { getUser } from './users';
export { getUser as getUserV2 } from './users'; // second registration throws
// after: one handler object per export name
export { getUser } from './users';
export const getUserV2 = makeHttpHandler(/* own fn */); Defensive patterns
Strategy: validation
Validate before calling
// convention check at module setup: one export name per handler object
const registered = new WeakSet<object>();
function assertSingleExport(handlerObj: object, name: string): void {
if (registered.has(handlerObj)) throw new Error(`handler re-exported: ${name}`);
registered.add(handlerObj);
} Prevention
- Create one handler object per export name; never re-export or alias handler objects
- Keep barrel files from re-exporting the same handler under multiple names
- Restart dev servers after schema/export changes so registration tables are rebuilt cleanly
When it happens
Trigger: Exporting the same handler object under two names (including via barrel-file re-exports or aliases); registering one handler object in two submodules; dev-server hot reload re-running export registration on the same objects.
Common situations: index.ts doing `export * from './users'` plus `export { getUser as getUser2 } from './users'`; sharing a handler constant between submodules to avoid duplication; bundlers that instantiate module code twice.
Related errors
- Route paths must start with `/`: ${path}
- Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_H
- Cannot nest router at `${path}`; existing routes overlap wit
- Route conflict for `${path}`
- There is already a reducer, procedure, or view with the name
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/26ec7e36aa46b93a.
Report an issue: GitHub.