Skip to content

Instantly share code, notes, and snippets.

@tiagofrancafernandes
Last active January 8, 2026 04:01
Show Gist options
  • Select an option

  • Save tiagofrancafernandes/61fa996cd5208285fa743cdacd161c28 to your computer and use it in GitHub Desktop.

Select an option

Save tiagofrancafernandes/61fa996cd5208285fa743cdacd161c28 to your computer and use it in GitHub Desktop.
dev-vercel Setup

Setup of Laravel APP 10.x on Vercel

File api/index.php

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);
}

File vercel.json

{
    "outputDirectory": "public",
    "installCommand": "echo 1 || true",
    "buildCommand": "echo 1 || true",
    "functions": {
        "api/*.php": {
            "runtime": "[email protected]"
        }
    },
    "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": "PHP/Laravel on Vercel",
        "APP_ENV": "production",
        "APP_DEBUG": "true",
        "APP_URL": "https://app-laravel-on-vercel.vercel.app",
        "VERCEL_DEMO_MODE": "true",
        "ON_SERVERLESS": "true",
        "CACHE_DRIVER": "array",
        "LOG_CHANNEL": "stderr",
        "SESSION_DRIVER": "array",
        "FILESYSTEM_DISK": "serverless_public",
        "LARAVEL_STORAGE_PATH": "/tmp/storage",
        "APP_CONFIG_CACHE": "/tmp/config.php",
        "APP_EVENTS_CACHE": "/tmp/events.php",
        "APP_PACKAGES_CACHE": "/tmp/packages.php",
        "OLD_APP_ROUTES_CACHE": "/tmp/routes.php",
        "APP_ROUTES_CACHE": "/tmp/routes-v7.php",
        "APP_SERVICES_CACHE": "/tmp/services.php",
        "VIEW_COMPILED_PATH": "/tmp/views",
        "CUSTOM_BOOTSTRAP_PATH": "/tmp",
        "SSR_TEMP_PATH": "/tmp/ssr",
        "NODE_PATH": "node"
    }
}

File config/app.php

// ...
$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

$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') ?: null;
$customBootstrap ??= $_SERVER['CUSTOM_BOOTSTRAP_PATH'] ?? $_ENV['CUSTOM_BOOTSTRAP_PATH'] ?? null;

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

if ($customBootstrap) {
    $app->useBootstrapPath(
        $customBootstrap
    );
}

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

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->input('server') ? $_SERVER ?? [] : [],
        'config' => is_string($config) ? ( $config === 'all' ? config()->all() : config(trim($config))) : [],
        'envs' => $request->input('envs') ? array_merge($_ENV ?? [], (array) getenv()) : [],
    ];
});

Setup of Laravel APP 12.x on Vercel

File api/index.php

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);
}

File vercel.json

{
  "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"
  }
}

File config/app.php

$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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment