Setup of Laravel APP 12.x on Vercel
require_once __DIR__ . '/../public/index.php ' ;
File api/404.php (optional)
require_once __DIR__ . '/../public/index.php ' ;
if (!headers_sent ()) {
header ('HTTP/1.1 404 Not Found ' );
http_response_code (404 );
}
{
"framework" : null ,
"outputDirectory" : " public" ,
"installCommand" : " echo 1 || true" ,
"buildCommand" : " echo 1 || true" ,
"functions" : {
"api/*.php" : {
"runtime" : " [email protected] " ,
"memory" : 1024 ,
"maxDuration" : 10
}
},
"routes" : [
{
"src" : " /build/(.*)" ,
"dest" : " /build/$1"
},
{
"src" : " /favicon.ico" ,
"dest" : " /favicon.ico"
},
{
"src" : " /storage/(.*)" ,
"dest" : " /storage/$1"
},
{
"src" : " /(.*)" ,
"dest" : " /api/index.php"
}
],
"env" : {
"APP_NAME" : " Laravel 12 on Vercel" ,
"APP_ENV" : " production" ,
"APP_DEBUG" : " true" ,
"APP_URL" : " https://app-laravel-on-vercel.vercel.app" ,
"ON_SERVERLESS" : " true" ,
"VERCEL_DEMO_MODE" : " true" ,
"CACHE_DRIVER" : " array" ,
"LOG_CHANNEL" : " stderr" ,
"SESSION_DRIVER" : " array" ,
"FILESYSTEM_DISK" : " serverless_public" ,
"LARAVEL_STORAGE_PATH" : " /tmp/storage" ,
"VIEW_COMPILED_PATH" : " /tmp/views" ,
"APP_CONFIG_CACHE" : " /tmp/config.php" ,
"APP_EVENTS_CACHE" : " /tmp/events.php" ,
"APP_PACKAGES_CACHE" : " /tmp/packages.php" ,
"APP_ROUTES_CACHE" : " /tmp/routes.php" ,
"APP_SERVICES_CACHE" : " /tmp/services.php" ,
"CUSTOM_BOOTSTRAP_PATH" : " /tmp" ,
"SSR_TEMP_PATH" : " /tmp/ssr" ,
"NODE_PATH" : " node"
}
}
$ isOnLambda =
str_starts_with (env ('LAMBDA_TASK_ROOT ' , '' ), '/var/task ' )
|| env ('AWS_LAMBDA_FUNCTION_VERSION ' )
|| env ('AWS_LAMBDA_EXEC_WRAPPER ' )
|| env ('AWS_LAMBDA_RUNTIME_API ' )
|| env ('AWS_LAMBDA_FUNCTION_NAME ' )
|| false ;
return [
// ...
'on_serverless ' => (bool ) env ('ON_SERVERLESS ' , $ isOnLambda ),
'is_on_lambda ' => (bool ) $ isOnLambda ,
// ...
];
File bootstrap/app.php (Laravel 12)
use Illuminate \Foundation \Application ;
$ isOnLambda =
str_starts_with (env ('LAMBDA_TASK_ROOT ' , '' ), '/var/task ' )
|| env ('AWS_LAMBDA_FUNCTION_VERSION ' )
|| env ('AWS_LAMBDA_EXEC_WRAPPER ' )
|| env ('AWS_LAMBDA_RUNTIME_API ' )
|| env ('AWS_LAMBDA_FUNCTION_NAME ' )
|| false ;
if ($ isOnLambda ) {
$ _SERVER ['CUSTOM_BOOTSTRAP_PATH ' ] ??= $ _ENV ['CUSTOM_BOOTSTRAP_PATH ' ] ?? '/tmp ' ;
$ _ENV ['CUSTOM_BOOTSTRAP_PATH ' ] ??= $ _SERVER ['CUSTOM_BOOTSTRAP_PATH ' ] ?? '/tmp ' ;
}
$ customBootstrap =
getenv ('CUSTOM_BOOTSTRAP_PATH ' )
?: $ _SERVER ['CUSTOM_BOOTSTRAP_PATH ' ]
?? $ _ENV ['CUSTOM_BOOTSTRAP_PATH ' ]
?? null ;
$ app = Application::configure (
basePath: $ _ENV ['APP_BASE_PATH ' ] ?? dirname (__DIR__ )
)
->withRouting (
web: __DIR__ . '/../routes/web.php ' ,
// api: __DIR__ . '/../routes/api.php',
// commands: __DIR__ . '/../routes/console.php',
health: '/up ' ,
)
->create ();
if ($ customBootstrap ) {
$ app ->useBootstrapPath ($ customBootstrap );
}
return $ app ;
File config/filesystems.php
$ pathsToLink = [];
if (env ('ON_SERVERLESS ' )) {
$ pathsToLink [public_path ('storage ' )] = sys_get_temp_dir () . '/app/public ' ;
}
if (!env ('ON_SERVERLESS ' )) {
$ pathsToLink [public_path ('storage ' )] = storage_path ('app/public ' );
}
return [
'default ' => env ('FILESYSTEM_DISK ' , 'local ' ),
'disks ' => [
'local ' => [
'driver ' => 'local ' ,
'root ' => env ('ON_SERVERLESS ' )
? sys_get_temp_dir () . '/app '
: storage_path ('app ' ),
'throw ' => false ,
],
'public ' => [
'driver ' => 'local ' ,
'root ' => env ('ON_SERVERLESS ' )
? sys_get_temp_dir () . '/app/public '
: storage_path ('app/public ' ),
'url ' => env ('APP_URL ' ) . '/storage ' ,
'visibility ' => 'public ' ,
'throw ' => false ,
],
'serverless_public ' => [
'driver ' => 'local ' ,
'root ' => sys_get_temp_dir () . '/app/public ' ,
'url ' => env ('APP_URL ' ) . '/storage ' ,
'visibility ' => 'public ' ,
'throw ' => false ,
],
],
'links ' => [
...$ pathsToLink ,
],
];
Test routes routes/web.php
use Illuminate \Support \Facades \DB ;
use Illuminate \Support \Facades \Route ;
Route::get ('/ ' , fn () => [
'message ' => 'Hello, World! ' ,
'now ' => now (),
]);
Route::get ('/api ' , fn () => [
'message ' => 'Hello, World! ' ,
'now ' => now (),
]);
Route::get ('/view ' , fn () => view ('welcome ' ));
Route::get ('/db-test ' , fn () => [
'database_connection ' => config ('database.default ' ),
'database_hostname ' => config ('database.connections. ' . config ('database.default ' ) . '.host ' ),
'database_name ' => config ('database.connections. ' . config ('database.default ' ) . '.database ' ),
'test_conn_pg ' => DB ::select ('SELECT 1 AS result ' ),
]);
Route::get ('/libs ' , fn () => []);
Route::get ('/dev/vars ' , function () {
$ request = request ();
$ config = $ request ->input ('config ' );
return [
'server ' => $ request ->boolean ('server ' ) ? ($ _SERVER ?? []) : [],
'config ' => is_string ($ config )
? ($ config === 'all ' ? config ()->all () : config (trim ($ config )))
: [],
'envs ' => $ request ->boolean ('envs ' )
? array_merge ($ _ENV ?? [], (array ) getenv ())
: [],
];
});
Observações finais (importantes)
✔️ Laravel 12 exige Application::configure()
✔️ functions substitui builds
✔️ bootstrap path só pode ser mudado no bootstrap
✔️ /tmp é obrigatório em serverless
❌ Não usar config para path
❌ Não misturar builds + functions
Se quiser, no próximo passo posso:
simplificar isso para mínimo funcional
adaptar para Vite SSR
explicar o que dá para remover sem quebrar nada
Esse setup está infra-level correto .