Konrad Kowalski (rootsher)Principal Platform & Reliability Architect110011110110110101110000000000101000101010110010

From fetch() to the First Byte

date
category
Frontend
also in
Networking · Observability
reading
2 min / 313 words

We have this code:

js
const response = await fetch(
  "https://api.example.com/users/42"
);

What happens between that line and receiving a Response?

There is no single identical path for every request, but we can build the right model.

1. Fetch creates an abstract request

The application defines:

text
URL
method
headers
body
credentials mode
cache mode
redirect mode
signal

The browser validates the request against Fetch and the security model.

JavaScript does not create raw HTTP bytes.

2. The browser checks policies and local state

Depending on the request, these can come into play:

text
HTTP cache
cookies
CORS
service worker
redirect policy

For example a cross-origin request may require a preflight.

Which means that:

text
one fetch()

can mean:

text
zero origin requests   -> cache hit
one request            -> normal network fetch
multiple requests      -> preflight / redirects

3. The browser needs a suitable connection

A connection fit for reuse may already exist.

If not:

text
DNS resolution
  |
  v
transport setup
  |
  v
TLS / secure session setup
  |
  v
HTTP protocol negotiation

The exact stages depend on the HTTP version and the existing connection state.

4. The request becomes a wire representation

Semantically:

text
GET /users/42
Accept: ...
Cookie: ...

but how it is sent depends on the version.

text
HTTP/1.1
-> textual message framing

HTTP/2
-> binary frames on an HTTP/2 stream

HTTP/3
-> HTTP frames on QUIC streams

The application should not feel that difference functionally.

5. The backend starts answering

The response headers come first.

In DevTools, part of the time up to that moment is usually associated with TTFB:

text
request start
  |
  v
network + server work
  |
  v
first response byte

So TTFB is not simply:

text
backend execution time

It can also include network and connection setup costs.

6. fetch() can give a Response before the whole body arrives

js
const response = await fetch(url);

Then:

js
const data = await response.json();

Those are two different stages.

With a large or streaming response body the difference can be significant.

The model:

text
headers available
  |
  v
Response object
  |
  v
body continues arriving
  |
  v
body consumed

7. DevTools shows the effect of many layers

The Network panel can show:

text
Queueing
Stalled
DNS
Initial connection
SSL
Request sent
Waiting / TTFB
Content download

Not every request pays every cost.

If the connection is reused:

text
DNS/TCP/TLS cost ≈ already paid

If the response comes from cache, the path can be even shorter.

If the request is HTTP/2 or HTTP/3, many requests can share the lower layers of the connection.

The whole model

text
JavaScript
  |
  v
Fetch API
  |
  v
browser policy + cache + cookies
  |
  v
HTTP semantics
  |
  v
HTTP/1.1 | HTTP/2 | HTTP/3
  |
  v
TCP | QUIC
  |
  v
network
  |
  v
backend
  |
  v
response headers
  |
  v
Response
  |
  v
response body

That is the most important outcome of this series.

When the frontend is slow, the question:

"is the API slow?"

is usually too general.

Better questions are:

text
Did the request go to the network at all?
Did it wait for a connection?
Was it preceded by a preflight?
Are the requests serialized by our own code?
Which protocol was used?
When did the headers arrive?
How long did the body take to download?

Only with that model does the Network panel become a diagnostic tool rather than a list of endpoints.