{"record":{"id":"45c8423c4b07e271","repo":"sveltejs/kit","slug":"keypath-cannot-start-or-end-with-see-https","errorCode":null,"errorMessage":"${keypath} cannot start or end with '/'. See https://svelte.dev/docs/kit/configuration","messagePattern":"(.+?) cannot start or end with '/'\\. See https://svelte\\.dev/docs/kit/configuration","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/kit/src/core/config/options.js","lineNumber":76,"sourceCode":"\t\t\t\tthrow new Error(`${keypath} should be an object`);\n\t\t\t}\n\n\t\t\tfor (const key in input) {\n\t\t\t\tassert_string(input[key], `${keypath}.${key}`);\n\t\t\t}\n\n\t\t\treturn input;\n\t\t}),\n\t\t(keypath) =>\n\t\t\t`The \\`${keypath}\\` option is deprecated, and will be removed in a future version of SvelteKit. Use subpath imports instead: https://svelte.dev/docs/kit/$lib`\n\t),\n\n\tappDir: validate('_app', (input, keypath) => {\n\t\tassert_string(input, keypath);\n\n\t\tif (input) {\n\t\t\tif (input.startsWith('/') || input.endsWith('/')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${keypath} cannot start or end with '/'. See https://svelte.dev/docs/kit/configuration`\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error(`${keypath} cannot be empty`);\n\t\t}\n\n\t\treturn input;\n\t}),\n\n\tcompilerOptions: any(),\n\n\tcsp: object({\n\t\tmode: list(['auto', 'hash', 'nonce']),\n\t\tdirectives,\n\t\treportOnly: directives\n\t}),\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/sveltejs/kit/blob/03f1687fe612ce3d2d9131139b5b188d9cf90c64/packages/kit/src/core/config/options.js#L58-L94","documentation":"`kit.appDir` is the directory name under the build output where SvelteKit places immutable app assets, and it becomes part of URLs. It must be a non-empty relative path segment, so validate throws when it starts or ends with '/' (an empty string throws the sibling 'cannot be empty' error).","triggerScenarios":"validate_options validates config.kit.appDir; assert_string passes (it is a string) but `input.startsWith('/') || input.endsWith('/')` is true, e.g. '/_app' or '_app/'.","commonSituations":"Users add a leading slash assuming URL-path semantics; copying an absolute path from a CDN/base-path config; trailing slash left from a copy-paste of a URL.","solutions":["Remove leading/trailing slashes: use `appDir: '_app'` (the default).","If you intended to change where assets are served from, configure `paths.base`/`paths.relative` instead of appDir.","Keep appDir as a single relative segment without slashes on either end."],"exampleFix":"// before\nkit: { appDir: '/_app/' }\n// after\nkit: { appDir: '_app' }","handlingStrategy":"validation","validationCode":"const appDir = config.kit?.appDir;\nif (typeof appDir === 'string' && (appDir.startsWith('/') || appDir.endsWith('/') || appDir === '')) {\n  throw new Error('kit.appDir must be a relative segment without leading/trailing slashes');\n}","typeGuard":"/** @returns {boolean} */\nfunction isValidAppDir(v) {\n  return typeof v === 'string' && v.length > 0 && !v.startsWith('/') && !v.endsWith('/');\n}","tryCatchPattern":"try {\n  await viteBuild();\n} catch (e) {\n  if (e.message.includes(\"cannot start or end with '/'\")) {\n    console.error('kit.appDir is relative — strip leading/trailing slashes');\n  }\n  throw e;\n}","preventionTips":["Leave appDir at its default '_app' unless you have a concrete reason","Use paths.base/paths.relative for URL prefixing needs, not appDir","Never copy URL paths (with slashes) into appDir","Validate config with the official schema before builds"],"tags":["config","validation","appdir","path-format"],"backgroundTag":"invalid-config-path-format","analyzedSha":"03f1687fe612ce3d2d9131139b5b188d9cf90c64","analyzedAt":"2026-09-02T02:01:50.504Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}