Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Server-Timing is a standard HTTP response header (RFC 7809 / W3C) that lets a server attach a breakdown of its own internal phases directly to the response it sends. A backend can annotate 'db=42ms' and 'auth=3ms' and 'render=7ms' — and any client, browser, curl, or monitoring agent can read those numbers without touching application logs. This is the fastest way to answer 'was this specific slow request slow in the database or in the application?' because the timing follows the request, not a log aggregator's pipeline that might batch-flush every 30 seconds. Browser DevTools Surface them automatically under the Timing tab of any request.
Server-Timing headers let a backend emit structured timing data — database query duration, cache lookup time, template rendering cost — directly to the browser in the HTTP response itself. Unlike APM agents that require a separate dashboard, Server-Timing shows up in DevTools' Timing tab alongside the network phases, giving frontend engineers and backend engineers a shared view of where wall-clock time is spent without any extra tooling.
curl -sv http://localhost:8080/api/data 2>&1 | grep -i server-timing. Hit it five times and note how db;dur= varies — this is the distribution you'd see in production.Use these three in order. Each builds on the one before.
In one paragraph, explain what the Server-Timing HTTP header does and why it is useful for debugging slow API responses.
How does the W3C Server-Timing spec define the 'dur', 'desc', and bare metric-token (no value) syntax? What happens if a browser encounters a metric it doesn't recognise?
I want to add Server-Timing to a high-traffic API that handles 50,000 requests/second. What are the performance implications of computing and attaching this header on every response, and how would I measure them?
// main.go — server emits Server-Timing; client reads it
package main
import (
"fmt"
"io"
"math/rand"
"net/http"
"time"
)
// Server — run this, then run the client in a second terminal
func startServer() {
http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
dbStart := time.Now()
time.Sleep(time.Duration(20+rand.Intn(80)) * time.Millisecond) // fake DB
dbMs := time.Since(dbStart).Milliseconds()
authMs := 3 + rand.Intn(5)
w.Header().Set("Server-Timing",
fmt.Sprintf("db;dur=%d, auth;dur=%d, miss", dbMs, authMs))
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"ok":true}`)
})
fmt.Println("Listening on :8080")
http.ListenAndServe(":8080", nil)
}
// Client — uncomment to use as the reader
func readServerTiming() {
resp, _ := http.Get("http://localhost:8080/api/data")
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
fmt.Println("Server-Timing:", resp.Header.Get("Server-Timing"))
}
func main() { startServer() }
// Swap to: func main() { readServerTiming() }go run main.go