@@ -8,7 +8,7 @@ import { get_pathname, pattern_to_src } from './utils.js';
88import { VERSION } from '@sveltejs/kit' ;
99
1010const name = '@sveltejs/adapter-vercel' ;
11- const DEFAULT_FUNCTION_NAME = 'fn' ;
11+ const INTERNAL = '![-]' ; // this name is guaranteed not to conflict with user routes
1212
1313const get_default_runtime = ( ) => {
1414 const major = Number ( process . version . slice ( 1 ) . split ( '.' ) [ 0 ] ) ;
@@ -319,7 +319,7 @@ const plugin = function (defaults = {}) {
319319 group . config . runtime === 'edge' ? generate_edge_function : generate_serverless_function ;
320320
321321 // generate one function for the group
322- const name = singular ? DEFAULT_FUNCTION_NAME : `fn- ${ group . i } ` ;
322+ const name = singular ? ` ${ INTERNAL } /catchall` : `${ INTERNAL } / ${ group . i } ` ;
323323
324324 await generate_function (
325325 name ,
@@ -332,12 +332,27 @@ const plugin = function (defaults = {}) {
332332 }
333333 }
334334
335+ if ( ! singular ) {
336+ // we need to create a catch-all route so that 404s are handled
337+ // by SvelteKit rather than Vercel
338+
339+ const runtime = defaults . runtime ?? get_default_runtime ( ) ;
340+ const generate_function =
341+ runtime === 'edge' ? generate_edge_function : generate_serverless_function ;
342+
343+ await generate_function (
344+ `${ INTERNAL } /catchall` ,
345+ /** @type {any } */ ( { runtime, ...defaults } ) ,
346+ [ ]
347+ ) ;
348+ }
349+
335350 for ( const route of builder . routes ) {
336351 if ( is_prerendered ( route ) ) continue ;
337352
338353 const pattern = route . pattern . toString ( ) ;
339354 const src = pattern_to_src ( pattern ) ;
340- const name = functions . get ( pattern ) ?? 'fn-0' ;
355+ const name = functions . get ( pattern ) ;
341356
342357 const isr = isr_config . get ( route ) ;
343358 if ( isr ) {
@@ -370,24 +385,43 @@ const plugin = function (defaults = {}) {
370385 src : src + '/__data.json$' ,
371386 dest : `/${ isr_name } /__data.json${ q } `
372387 } ) ;
373- } else if ( ! singular ) {
374- static_config . routes . push ( { src : src + '(?:/__data.json)?$' , dest : `/${ name } ` } ) ;
375- }
376- }
388+ } else {
389+ // Create a symlink for each route to the main function for better observability
390+ // (without this, every request appears to go through `/![-]`)
377391
378- if ( ! singular ) {
379- // we need to create a catch-all route so that 404s are handled
380- // by SvelteKit rather than Vercel
392+ // Use 'index' for the root route's filesystem representation
393+ // Use an empty string ('') for the root route's destination name part in Vercel config
394+ const is_root = route . id === '/' ;
395+ const route_fs_name = is_root ? 'index' : route . id . slice ( 1 ) ;
396+ const route_dest_name = is_root ? '' : route . id . slice ( 1 ) ;
381397
382- const runtime = defaults . runtime ?? get_default_runtime ( ) ;
383- const generate_function =
384- runtime === 'edge' ? generate_edge_function : generate_serverless_function ;
398+ // Define paths using path.join for safety
399+ const base_dir = path . join ( dirs . functions , route_fs_name ) ; // e.g., .vercel/output/functions/index
400+ // The main symlink should be named based on the route, adjacent to its potential directory
401+ const main_symlink_path = `${ base_dir } .func` ; // e.g., .vercel/output/functions/index.func
402+ // The data symlink goes inside the directory
403+ const data_symlink_path = path . join ( base_dir , '__data.json.func' ) ; // e.g., .vercel/output/functions/index/__data.json.func
385404
386- await generate_function (
387- DEFAULT_FUNCTION_NAME ,
388- /** @type {any } */ ( { runtime, ...defaults } ) ,
389- [ ]
390- ) ;
405+ const target = path . join ( dirs . functions , `${ name } .func` ) ; // The actual function directory e.g., .vercel/output/functions/![-].func
406+
407+ // Ensure the directory for the data endpoint symlink exists (e.g., functions/index/)
408+ builder . mkdirp ( base_dir ) ;
409+
410+ // Calculate relative paths FROM the directory containing the symlink TO the target
411+ const relative_for_main = path . relative ( path . dirname ( main_symlink_path ) , target ) ;
412+ const relative_for_data = path . relative ( path . dirname ( data_symlink_path ) , target ) ; // This is path.relative(base_dir, target)
413+
414+ // Create symlinks
415+ fs . symlinkSync ( relative_for_main , main_symlink_path ) ; // Creates functions/index.func -> ![-].func
416+ fs . symlinkSync ( relative_for_data , data_symlink_path ) ; // Creates functions/index/__data.json.func -> ../![-].func
417+
418+ // Add route to the config
419+ static_config . routes . push ( {
420+ src : src + '(?:/__data.json)?$' , // Matches the incoming request path
421+ dest : `/${ route_dest_name } ` // Maps to the function: '/' for root, '/about' for about, etc.
422+ // Vercel uses this dest to find the corresponding .func dir/symlink
423+ } ) ;
424+ }
391425 }
392426
393427 // optional chaining to support older versions that don't have this setting yet
@@ -412,7 +446,7 @@ const plugin = function (defaults = {}) {
412446
413447 // Catch-all route must come at the end, otherwise it will swallow all other routes,
414448 // including ISR aliases if there is only one function
415- static_config . routes . push ( { src : '/.*' , dest : `/${ DEFAULT_FUNCTION_NAME } ` } ) ;
449+ static_config . routes . push ( { src : '/.*' , dest : `/${ INTERNAL } /catchall ` } ) ;
416450
417451 builder . log . minor ( 'Writing routes...' ) ;
418452
0 commit comments