withastro/astro · error · TypeError
maxDuration must be a positive number
Error message
maxDuration must be a positive number
What it means
The Vercel adapter validates its `maxDuration` option at integration setup, before any build work starts. `maxDuration` maps to the serverless function timeout written to the build output, so Vercel requires a number strictly greater than zero. This specific throw fires when the value is a number but is zero or negative (a preceding check handles non-number values).
Solutions
- Set maxDuration to a positive integer (e.g. 10 or 60): `vercel({ maxDuration: 60 })`
- Omit maxDuration entirely to use Vercel's default function timeout if you don't need a custom limit
- If the value comes from an environment variable, parse and guard it: `const maxDuration = Number(process.env.MAX_DURATION) > 0 ? Number(process.env.MAX_DURATION) : undefined`
Example fix
// before
export default defineConfig({
adapter: vercel({ maxDuration: 0 }),
});
// after
export default defineConfig({
adapter: vercel({ maxDuration: 60 }),
}); Defensive patterns
Strategy: validation
Validate before calling
const maxDuration = Number(process.env.VERCEL_MAX_DURATION);
export default defineConfig({
adapter: vercel({
maxDuration: Number.isFinite(maxDuration) && maxDuration > 0 ? maxDuration : undefined,
}),
}); Type guard
function isValidMaxDuration(v: unknown): v is number {
return typeof v === 'number' && v > 0;
} Prevention
- Keep adapter options as literals in astro.config.mjs instead of templating from env vars
- Run `astro build` in CI to catch config validation before deploy
- Remember 0 does not mean 'unlimited' — omit maxDuration for the platform default
When it happens
Trigger: Calling `vercel({ maxDuration: 0 })` or `vercel({ maxDuration: -5 })` in astro.config.mjs. It also fires when the value comes from an env var parsed with Number() that resolves to 0 (e.g. `Number('')`), or from a computed expression that clamps to <= 0.
Common situations: Developers set `maxDuration: 0` intending "no limit" instead of omitting the option; config is copied between projects and the value is templated from CI variables that are empty; a preset/shared config object defaults maxDuration to 0.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- maxDuration is set to
- Please make sure that your plan allows for this duration…
- AdapterSupportOutputMismatch
- `Astro.session` was accessed but no session storage is…
- ClientAddressNotAvailable
AI-assisted analysis of withastro/astro@3578d45d34 (2026-08-18).
Data as JSON: /api/errors/aa475d6c3388555b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/vercel/src/index.ts:260
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 _hasServerBuild = false;
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
const middlewareSecret = crypto.randomUUID();
let _buildOutput: 'server' | 'static';
View on GitHub (pinned to 3578d45d34)