Vercel & Serverless#

Running Webrick in serverless/edge environments requires a slightly different shape: single-binary boot, fast cold-start, and stateless caches. This page sketches patterns for Vercel, AWS Lambda (via API Gateway/Lambda@Edge), and Cloudflare Workers (edge JS).

TL;DR: PHP is a great fit on serverless containers (Lambda with PHP Runtime, Vercel’s PHP runtime). For edge JS-only platforms (Cloudflare Workers), place Webrick behind an edge proxy or re-implement only the thinnest endpoints at the edge.


Principles#

  • Precompute: ship a prebuilt route cache and classmap (Composer --classmap-authoritative).

  • Light boot: keep the front controller small; avoid dynamic scanning.

  • Immutable code: no writes outside /tmp (or platform temp dir).

  • Stateless: use external stores for session, response cache, and rate limits.

  • Short timeouts: streaming and long-running jobs are a poor fit; offload to queues/cron.


Vercel (Serverless Functions)#

Vercel supports PHP runtimes that execute per-request. Use the community PHP runtime (e.g., vercel-php) or a custom runtime container.

Files#

api/
└─ index.php          # your front controller (adapted)
vercel.json
composer.json
vendor/
.route-cache/     # prebuilt (checked into artifact)

vercel.json (example)#

{
  "functions": {
    "api/index.php": {
      "runtime": "[email protected]",
      "memory": 512,
      "maxDuration": 10
    }
  },
  "routes": [
    { "src": "/(.*)", "dest": "/api/index.php" }
  ]
}

Pin a runtime version; set memory and maxDuration per endpoint needs.

Front controller tweaks#

  • Do not write caches at runtime—read from .route-cache/ if present.

  • Put ephemeral files in sys_get_temp_dir() when absolutely necessary.

$kernel = RouterKernel::bootWithRegistrar(
  /* ... */
  routeCache: __DIR__ . '/../.route-cache', // shipped with artifact
  /* ... */
);

What to avoid / adapt#

  • Streaming/SSE: Vercel serverless functions buffer responses; prefer short responses or move streams behind a persistent service.

  • File downloads: write to temp and return, or stream from object storage (S3).

  • Response cache: back with Redis (Upstash) or Vercel KV; don’t use in-memory cache.


AWS Lambda (API Gateway)#

Use a PHP Lambda runtime (Bref is the de-facto standard). It maps API Gateway requests to your PHP entrypoint.

Composer & Bref#

composer require bref/bref

serverless.yml (Serverless Framework + Bref)#

service: webrick
provider:
  name: aws
  region: ap-south-1
  runtime: provided.al2
  environment:
    WEBRICK_SIGN_KEY: ${env:WEBRICK_SIGN_KEY}
    WEBRICK_COOKIE_KEY: ${env:WEBRICK_COOKIE_KEY}
plugins:
  - ./vendor/bref/bref

functions:
  http:
    handler: public/index.php
    layers:
      - ${bref:layer.php-84}
    events:
      - httpApi:
          method: ANY
          path: /{proxy+}
    timeout: 10
    memorySize: 512

Front controller: same as normal, but:

  • Ensure route cache is bundled (read-only).

  • Use /tmp for transient files.

  • Prefer external stores: DynamoDB/ElastiCache/MemoryDB for rate limits and caches.

Binary responses: Configure API Gateway binary media types if returning images/files.

Cold starts: Keep dependency list lean; pre-warm with provisioned concurrency for critical paths.


Lambda@Edge / CloudFront#

If you need signed URL verification or simple rewrites at the edge:

  • Keep Webrick in origin (Lambda or container).

  • Use Lambda@Edge (Node/Python) functions to:

    • Rewrite /old/* /new/*

    • Add/remove headers (e.g., X-Forwarded-Proto)

    • Early deny bots with cheap checks

  • Let Webrick handle app logic, signed URLs, throttling, etc., at the origin.


Cloudflare Workers (and other JS-only edges)#

Workers don’t run PHP. Two common patterns:

  1. Edge proxy → origin PHP

    • Worker handles micro-decisions (bot deny, geo-routing, A/B flags).

    • Proxies to your PHP origin (Fly.io, Render, ECS, etc.).

    • Cache public GETs at the edge using cache.put() with Vary that mirrors your app.

  2. Hybrid endpoints

    • Implement static or ultra-simple endpoints at the edge (e.g., /status, /robots.txt).

    • All dynamic routes go to origin.

Keep signatures cohesive: if generating signed URLs at the edge, share the same key via secrets and keep time skew in mind.


Externalizing state (must-do)#

  • Response Cache → Redis/Upstash/ElastiCache (include media, locale, Accept-Encoding in keys if you store encoded bytes).

  • Throttle → Redis atomic ops (INCR/LUA); do not use local memory.

  • Sessions (if you use them) → Redis or JWT + encrypted cookies.

  • File storage → S3/GCS; serve via signed URLs; Webrick endpoint only issues the link.


Observability on serverless#

  • Request ID: still emit X-Request-Id so logs correlate across cold starts.

  • Server-Timing: useful, but some platforms strip it—verify.

  • Centralize logs in CloudWatch (Lambda), Vercel logs, or a third-party collector.

  • Export lightweight metrics via embedded statsd/OTEL where possible, or infer from access logs.


Timeouts & limits (guidance)#

Platform

Cold Start

Max Duration

Notes

Vercel PHP

~50–200ms typical

10–60s (plan-dependent)

Avoid streaming; rely on object storage

AWS Lambda + Bref

100ms–1s

Up to 15m

Provisioned concurrency for critical routes

Cloudflare Workers

~0ms

30s HTTP

JS only; use as smart edge cache/proxy

Keep Compression enabled (gzip/br) unless the platform auto-compresses. Ensure ETag strategy remains recompute-strong if bytes change on the wire.


Example: S3 download via signed URL#

Instead of streaming large files from a function:

  1. Webrick endpoint authenticates request and generates a temporary S3 presigned URL.

  2. Client downloads directly from S3 (no compute time billed).

Route::get('/files/{id:int}', function ($r, int $id) {
  // check ACLs...
  $s3Url = issueS3PresignedUrl("files/{$id}.zip", ttl: 900);
  return Response::redirect($s3Url, 302);
}, ['middleware'=>['verifySignedUrl']]);

Build & ship checklist (serverless)#

  • composer install --no-dev --classmap-authoritative

  • Route cache prebuilt and shipped with artifact

  • No runtime writes except /tmp (or platform temp)

  • Externalized response cache, throttle, sessions

  • Short timeouts; avoid streaming; prefer redirects to object storage

  • Observability headers & logs wired (X-Request-Id, Server-Timing if available)

  • Secrets via platform store (Vercel env vars, AWS Secrets Manager/SSM)


Troubleshooting#

Symptom

Likely cause

Fix

Long cold starts

Heavy autoload or attribute scans

Prebuild route cache; use classmap authoritative; trim deps

502/timeout on large responses

Platform buffering/limits

Redirect to object storage; chunk on origin behind a CDN

Throttle inconsistent across instances

Local/in-memory store

Move to Redis/Upstash/Dynamo with atomic ops

Signed URLs invalid intermittently

Clock skew

Ensure platform times are NTP-synced; add small TTL buffer

“Works locally, 404 on platform”

Wrong entry mapping

Check vercel.json routes or API Gateway mapping template