{"record":{"id":"0e0d14642a2ac036","repo":"sveltejs/kit","slug":"invalid-isr-expiration-value-json-stringify-val","errorCode":null,"errorMessage":"Invalid isr.expiration value: ${JSON.stringify(value)} (${desc}, in ${route_id})","messagePattern":"Invalid isr\\.expiration value: (.+?) \\((.+?), in (.+?)\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-vercel/utils.js","lineNumber":34,"sourceCode":"\t\tsrc = '^/?';\n\t}\n\n\treturn src;\n}\n\nconst integer = /^\\d+$/;\n\n/**\n * @param {false | string | number} value\n * @param {string} route_id\n * @returns {number | false}\n */\nexport function parse_isr_expiration(value, route_id) {\n\tif (value === false || value === 'false') return false; // 1 year\n\n\t/** @param {string} desc */\n\tconst err = (desc) => {\n\t\tthrow new Error(\n\t\t\t`Invalid isr.expiration value: ${JSON.stringify(value)} (${desc}, in ${route_id})`\n\t\t);\n\t};\n\n\tlet parsed;\n\tif (typeof value === 'string') {\n\t\tif (!integer.test(value)) {\n\t\t\terr('value was a string but could not be parsed as an integer');\n\t\t}\n\t\tparsed = Number.parseInt(value, 10);\n\t} else {\n\t\tif (!Number.isInteger(value)) {\n\t\t\terr('should be an integer');\n\t\t}\n\t\tparsed = value;\n\t}\n\n\tif (Number.isNaN(parsed)) {","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/sveltejs/kit/blob/03f1687fe612ce3d2d9131139b5b188d9cf90c64/packages/adapter-vercel/utils.js#L16-L52","documentation":"adapter-vercel validates the `isr.expiration` value from each route's config via parse_isr_expiration. Valid values are `false`/'false' (meaning 1 year) or a positive number/numeric string; anything else — negative numbers, zero, non-numeric strings, objects — throws with a description of why it is invalid.","triggerScenarios":"A route exports `config.isr.expiration` that parses to an invalid value: e.g. `expiration: 0`, `expiration: -30`, `expiration: 'soon'`, or a string that Number() cannot parse, in utils.js parse_isr_expiration.","commonSituations":"Typo like 'expiraton' swapped types; setting expiration to 0 thinking it means 'no cache'; copying an exponential-backoff-style value; passing a Date or human string like '1 hour'.","solutions":["Set `expiration` to a positive number of seconds, e.g. `isr: { expiration: 60 }`","Use `expiration: false` if you want the maximum (1 year) caching","Remove non-numeric strings or convert them to seconds yourself","Check the route named in the error message (`in ${route_id}`) and fix its exported config"],"exampleFix":"// before (+page.js)\nexport const config = { isr: { expiration: '1 hour' } };\n// after\nexport const config = { isr: { expiration: 3600 } };","handlingStrategy":"validation","validationCode":"const exp = config?.isr?.expiration;\nconst valid = exp === false || exp === 'false' || (typeof exp === 'number' && exp > 0) || (typeof exp === 'string' && Number(exp) > 0);\nif (!valid) throw new Error(`invalid isr.expiration: ${JSON.stringify(exp)}`);","typeGuard":"const isValidExpiration = (v) =>\n  v === false || v === 'false' ||\n  (typeof v === 'number' && Number.isFinite(v) && v > 0) ||\n  (typeof v === 'string' && v.trim() !== '' && Number(v) > 0);","tryCatchPattern":"try {\n  await build();\n} catch (err) {\n  if (err.message.startsWith('Invalid isr.expiration')) {\n    console.error('Set isr.expiration to a positive number of seconds or false.');\n  }\n  throw err;\n}","preventionTips":["Express expiration always in whole seconds","Use false (not 0) for maximal caching","Centralize ISR config constants to avoid one-off typos","Type-check route configs with JSDoc/TS"],"tags":["config","vercel","isr","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"03f1687fe612ce3d2d9131139b5b188d9cf90c64","analyzedAt":"2026-09-02T02:01:50.504Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}