Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Server Timing Laravel Package

fetzi/server-timing

Add Server-Timing headers to Laravel responses and expose app, database, and custom performance metrics in your browser's DevTools Performance tab. Lightweight middleware for profiling without extra tooling.

View on GitHub
Deep Wiki
Context7

Getting Started

fetzi/server-timing is a tiny, framework‑agnostic helper. It has no Laravel service provider, no config file, no facade — you construct Fetzi\ServerTiming\ServerTiming, call start() / stop() (or measure()) around code you want to profile, and read getHeader() to get the Server-Timing header value.

composer require fetzi/server-timing

Minimal wiring — bind it as a request-scoped singleton and add one middleware:

// App\Providers\AppServiceProvider::register()
$this->app->scoped(\Fetzi\ServerTiming\ServerTiming::class, fn () => new \Fetzi\ServerTiming\ServerTiming());
// App\Http\Middleware\ServerTiming
public function __construct(private \Fetzi\ServerTiming\ServerTiming $timing) {}

public function handle(Request $request, Closure $next)
{
    $this->timing->start('app');
    $response = $next($request);
    $this->timing->stop('app');

    $response->headers->set('Server-Timing', $this->timing->getHeader());
    return $response;
}

Register it globally (or as a group middleware) in bootstrap/app.php / Kernel.php.

Where to look first: the package README — that's basically the whole API. There's nothing to publish. The real "first use case" is: wrap one controller action, hit it in Chrome, open DevTools → Network → your request → Timing tab; you'll see your named metric. That feedback loop is faster than Telescope for quick "where is my 200 ms going" checks.

Implementation Patterns

Named phases around slow work. The most useful pattern is coarse named phases rather than per-query granularity:

$timing->measure('db', fn () => $repository->loadAggregates());
$timing->measure('http', fn () => Http::get('https://api.internal/...')->json());
$timing->start('render');
return view('dashboard', $data);

Instrumenting the query layer. DB::listen fires after a query, so it can't measure durations directly. Instead wrap at the repository/service boundary, or use a query-log listener that records $query->time and aggregates into a single db metric after the response is built.

Queue / jobs. Because the binding is scoped, the container gives you a fresh instance per request and per Octane request. In a job you can wrap the handle body and log getHeader() — useful for spotting slow jobs, though the header itself only matters on HTTP responses.

Blade partials. For a specific slow view section: @php app(ServerTiming::class)->start('sidebar') @endphp ... @php app(ServerTiming::class)->stop('sidebar') @endphp. Only do this for known-slow sections; it clutters templates fast.

Combine with existing telemetry. If you already use Telescope or a metrics package, feed the same timings into both rather than duplicating start/stop calls — e.g. a small TimingService that fans out to ServerTiming and your APM.

Production vs. local. Gate the middleware by environment or a feature flag so production responses don't carry internal metric names. If you want it in production for debugging specific users, key it off a header or a config flag.

DevTools-driven workflow. Add metrics, reload the page, check the Timing tab. Good candidates: DB, Redis, external HTTP, template render, and an overall app metric. That's usually 4–6 entries — enough to isolate the slow layer.

Gotchas and Tips

  • No auto-instrumentation. The package does nothing on its own. If you expect DB/Redis timings out of the box, you'll be disappointed; you write the start/stop calls.
  • Octane state. Use scoped() (or bind() + explicit reset() in middleware). A plain singleton() will leak metrics across requests under Octane/RoadRunner/Swoole — you'll see duplicate or accumulating entries in the header.
  • Set the header before the response is sent. Doing it in terminate() is too late for most SAPIs; set it in handle() after $next() returns. For streamed responses, set it before the first byte is flushed.
  • Duplicate metric names. Starting the same key twice (e.g. nested loops) may overwrite or produce multiple entries depending on version. Use unique keys (db:users, db:orders) and stop every started metric — an unstopped metric may produce a bogus/infinite duration.
  • Header size. Every metric is bytes on the wire, on every response. Long names or descriptions add up, and proxies/browsers may truncate. Keep names short (db, redis, tpl).
  • Don't leak internals. Metric names and desc values are visible to clients. Avoid table names, endpoint URLs, tenant IDs. In production, prefer generic names.
  • CORS / JS access. Cross-origin JS can only read Server-Timing if the response also carries Timing-Allow-Origin. Same-origin DevTools access works regardless.
  • Nesting is on you. The package doesn't track parent/child. If you want a tree, name metrics hierarchically (db.users, db.orders) and do the math yourself.
  • Precision source. Durations come from the monotonic clock and are in milliseconds. Sub-millisecond operations will show as ~0 and are usually not worth a metric.
  • Testing. Assert on $response->headers->get('Server-Timing') in feature tests; also assert it's absent when the feature flag is off, so you don't accidentally ship it.
  • Extension points are limited. There's no formatter/hook API — if you need custom formatting, filtering, or sampling, wrap the package in your own class rather than fighting it. For anything more ambitious, ~40 lines of hrtime() and a header setter gives you the same result with full control.
  • Reverse proxies. Nginx/Apache pass Server-Timing through by default, but any proxy that strips unknown headers (some CDN configs, WAFs) will eat it. Verify in staging via curl -I.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource