@@ -8,7 +8,7 @@ import { get_pathname, pattern_to_src } from './utils.js';
8
8
import { VERSION } from '@sveltejs/kit' ;
9
9
10
10
const name = '@sveltejs/adapter-vercel' ;
11
- const DEFAULT_FUNCTION_NAME = 'fn' ;
11
+ const INTERNAL = '![-]' ; // this name is guaranteed not to conflict with user routes
12
12
13
13
const get_default_runtime = ( ) => {
14
14
const major = Number ( process . version . slice ( 1 ) . split ( '.' ) [ 0 ] ) ;
@@ -319,7 +319,7 @@ const plugin = function (defaults = {}) {
319
319
group . config . runtime === 'edge' ? generate_edge_function : generate_serverless_function ;
320
320
321
321
// 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 } ` ;
323
323
324
324
await generate_function (
325
325
name ,
@@ -332,12 +332,27 @@ const plugin = function (defaults = {}) {
332
332
}
333
333
}
334
334
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
+
335
350
for ( const route of builder . routes ) {
336
351
if ( is_prerendered ( route ) ) continue ;
337
352
338
353
const pattern = route . pattern . toString ( ) ;
339
354
const src = pattern_to_src ( pattern ) ;
340
- const name = functions . get ( pattern ) ?? 'fn-0' ;
355
+ const name = functions . get ( pattern ) ;
341
356
342
357
const isr = isr_config . get ( route ) ;
343
358
if ( isr ) {
@@ -370,24 +385,43 @@ const plugin = function (defaults = {}) {
370
385
src : src + '/__data.json$' ,
371
386
dest : `/${ isr_name } /__data.json${ q } `
372
387
} ) ;
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 `/![-]`)
377
391
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 ) ;
381
397
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
385
404
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
+ }
391
425
}
392
426
393
427
// optional chaining to support older versions that don't have this setting yet
@@ -412,7 +446,7 @@ const plugin = function (defaults = {}) {
412
446
413
447
// Catch-all route must come at the end, otherwise it will swallow all other routes,
414
448
// 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 ` } ) ;
416
450
417
451
builder . log . minor ( 'Writing routes...' ) ;
418
452
0 commit comments