withastro/astro · error · TypeError

maxDuration must be a number

Error message

maxDuration must be a number

What it means

Thrown by the Vercel serverless adapter when `vercel({ serverless: { maxDuration } })` is truthy but not a number type. `maxDuration` maps to Vercel's function timeout and must be numeric to be serialized into the function config.

Source

Thrown at packages/integrations/vercel/src/index.ts:254

	webAnalytics,
	includeFiles: _includeFiles = [],
	excludeFiles: _excludeFiles = [],
	imageService,
	imagesConfig,
	devImageService = 'sharp',
	middlewareMode,
	edgeMiddleware,
	maxDuration,
	isr = false,
	skewProtection = process.env.VERCEL_SKEW_PROTECTION_ENABLED === '1',
	staticHeaders = false,
}: VercelServerlessConfig = {}): AstroIntegration {
	// Resolve middleware mode with backward compatibility
	const resolvedMiddlewareMode = middlewareMode ?? (edgeMiddleware ? 'edge' : 'classic');

	if (maxDuration) {
		if (typeof maxDuration !== 'number') {
			throw new TypeError(`maxDuration must be a number`, {
				cause: maxDuration,
			});
		}
		if (maxDuration <= 0) {
			throw new TypeError(`maxDuration must be a positive number`, {
				cause: maxDuration,
			});
		}
	}

	let _config: AstroConfig;
	let _buildTempFolder: URL;
	let _serverEntry: string;
	let _middlewareEntryPoint: URL | undefined;
	let _routeToHeaders: RouteToHeaders | undefined = undefined;
	// Extra files to be merged with `includeFiles` during build
	const extraFilesToInclude: URL[] = [];
	// Secret used to verify that the caller is the astro-generated edge middleware and not a third-party

View on GitHub (pinned to d081033d5f)

Solutions

  1. Pass a numeric literal: `vercel({ serverless: { maxDuration: 60 } })`.
  2. Coerce env-var inputs: `maxDuration: Number(process.env.MAX_DURATION)`.
  3. Validate the value type before assigning it to the config object.

Example fix

// before
export default defineConfig({
  adapter: vercel({ serverless: { maxDuration: process.env.MAX_DURATION } }),
});
// after
export default defineConfig({
  adapter: vercel({
    serverless: { maxDuration: Number(process.env.MAX_DURATION) },
  }),
});
Defensive patterns

Strategy: type-guard

Validate before calling

function asMaxDuration(v: unknown): number | undefined {
  if (v == null) return undefined;
  const n = Number(v);
  if (!Number.isFinite(n)) throw new TypeError('maxDuration must be a number');
  return n;
}
// usage: vercel({ serverless: { maxDuration: asMaxDuration(process.env.MAX_DURATION) } })

Type guard

function isNumberLike(v: unknown): v is number | string {
  return typeof v === 'number' || (typeof v === 'string' && v.trim() !== '' && !isNaN(Number(v)));
}

Prevention

When it happens

Trigger: Passing `maxDuration` as a string (e.g. from an env var read without `Number()`), an object, or an array. The guard `if (maxDuration)` passes for any truthy value, then `typeof maxDuration !== 'number'` fires.

Common situations: Reading `maxDuration` from `process.env.MAX_DURATION` (always a string) without coercion. Spreading a JSON config where the value is quoted.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/7d7c7930f46ed9c4. Report an issue: GitHub.