heygen-com/hyperframes · error

[renderToLambda] either siteHandle or projectDir must be sup

Error message

[renderToLambda] either siteHandle or projectDir must be supplied

What it means

Thrown by renderToLambda when BOTH opts.siteHandle and opts.projectDir are absent. The function needs a project to render: either a previously-deployed SiteHandle (skips the tar+PUT) or a local projectDir (deploySite runs inline). Supplying neither means there is nothing to render, and the guard prevents a downstream undefined dereference in deploySite. Passing one of the two resolves it.

Source

Thrown at packages/aws-lambda/src/sdk/renderToLambda.ts:92

  bucketName: string;
  stateMachineArn: string;
  outputS3Uri: string;
  projectS3Uri: string;
  startedAt: string;
}

// fallow-ignore-next-line complexity
export async function renderToLambda(opts: RenderToLambdaOptions): Promise<RenderHandle> {
  validateDistributedRenderConfig(opts.config);

  if (!opts.bucketName) {
    throw new Error("[renderToLambda] bucketName is required");
  }
  if (!opts.stateMachineArn) {
    throw new Error("[renderToLambda] stateMachineArn is required");
  }
  if (!opts.siteHandle && !opts.projectDir) {
    throw new Error("[renderToLambda] either siteHandle or projectDir must be supplied");
  }

  const executionName = opts.executionName ?? `hf-render-${randomUUID()}`;
  const ext = formatExtension(opts.config.format);
  const outputKey = opts.outputKey ?? `renders/${executionName}/output${ext}`;
  const planOutputS3Prefix = formatS3Uri({
    bucket: opts.bucketName,
    key: `renders/${executionName}/`,
  });
  const outputS3Uri = formatS3Uri({ bucket: opts.bucketName, key: outputKey });

  const site =
    opts.siteHandle ??
    (await deploySite({
      projectDir: opts.projectDir as string,
      bucketName: opts.bucketName,
      region: opts.region,
      s3: opts.s3,

View on GitHub (pinned to c2996c8626)

Solutions

  1. Always provide projectDir as a baseline; layer siteHandle on top when you have a cached handle.
  2. Restructure the caller so exactly one branch runs: if (siteHandle) use it, else use projectDir.
  3. Add a TS-level assertion or branded type so the 'both undefined' state is unrepresentable.
  4. Default projectDir to a known directory when no siteHandle exists.

Example fix

// before
const handle = cache.get(id); // undefined on miss
await renderToLambda({ config, bucketName, stateMachineArn, siteHandle: handle });

// after
const opts = handle
  ? { siteHandle: handle }
  : { projectDir: './dist' };
await renderToLambda({ config, bucketName, stateMachineArn, ...opts });
Defensive patterns

Strategy: validation

Validate before calling

function assertRenderSource(opts: {
  siteHandle?: unknown; projectDir?: string;
}): void {
  if (!opts.siteHandle && !opts.projectDir) {
    throw new Error('either siteHandle or projectDir must be supplied');
  }
}

Type guard

const hasRenderSource = (o: { siteHandle?: unknown; projectDir?: string }): boolean =>
  Boolean(o.siteHandle) || Boolean(o.projectDir);

Prevention

When it happens

Trigger: Caller assumes projectDir is required and siteHandle optional but passes neither; siteHandle is conditionally set and the condition evaluated false; an options builder that omits both when a feature flag is off.

Common situations: Branching logic that sets siteHandle only on cache-hit and projectDir only on cache-miss but leaves a path where neither is set; spreading a partial config object; TS optional fields left undefined at runtime despite the type.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/4a1a4d4cd8ddabf4. Report an issue: GitHub.