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-partyView on GitHub (pinned to d081033d5f)
Solutions
- Pass a numeric literal: `vercel({ serverless: { maxDuration: 60 } })`.
- Coerce env-var inputs: `maxDuration: Number(process.env.MAX_DURATION)`.
- 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
- Always coerce env-var-sourced numbers with `Number()`.
- Type the config builder input strictly (no `any`).
- Add a config sanity check that logs resolved numeric options.
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
- maxDuration must be a positive number
- Vercel Image Optimization requires at least one size to be c
- CONTENT_TOO_LARGE
- Configured image service is not a local service
- Configured image service is not a local service
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/7d7c7930f46ed9c4.
Report an issue: GitHub.