Skip to main content

Request lifecycle

Every request to GreyScreen — a catalogue page, a profile, a signed upload — takes the same shape. Once you know these six hops, you can predict where a failure lives before you open a log.

Flow · web request

One request, from browser to Firestore and back

Every public call takes the same path. Zoom in to read the labels.

Rendering Mermaid diagram…
Zoom 100 percent
Mermaid

The same system, laid out automatically. Use the controls to zoom, open it full screen, or download it for a deck.

The six hops​

#HopWhat it doesWhat can go wrong
1Browser → gatewayTLS terminates; nginx matches the longest prefixWrong host, expired certificate, a path that no domain config knows
2Gateway → containerStrips the prefix, forwards to the upstreamA route missing from domains.json, or generate.sh not re-run
3ContainerStatic nginx (SPA) or Express (API) handles the now-rooted pathAn SPA fallback that swallows /healthz, or a route order bug
4API → FirestoreReads the live snapshot or a documentWrong Firebase project, a document that is not isReady
5Back through the gatewayJSON and cache headers returncompression() buffering a stream, CORS allowlist mismatch
6BrowserReact rendersA client that assumed a field which is absent for old documents

Why prefixes matter​

The gateway gives each container the path as if it were mounted at its own root. A browser request to /api/customer/catalog/home arrives at the customer API as /catalog/home. This is deliberate: the same image can run behind any domain without a path prefix baked into the code.

Consequences worth knowing:

  • Debug logs inside the API show the stripped path. Search for it, not for the public URL.
  • domains.json is the list of public routes. A new endpoint is reachable locally and dead publicly until a domain route exists and generate.sh runs.
  • / is not stripped. The website is the only upstream mounted at a bare origin.

Static and API responses​

Static frontends answer two things:

  • /healthz → 200 with a small body. This is what deploys and monitoring probe.
  • Everything else → index.html, so client-side routes work on a hard refresh.

APIs answer GET /health with {status, appEnv, production[, version]}. A deployed build that returns only {"status":"ok"} predates the current controller. Treat that as version drift, not a different contract — and re-probe before concluding the environment is broken.

Caching, by surface​

SurfacePolicyWhy
Catalogue and title pagesShort revalidation or no-storeA newly published title must appear promptly
/catalog/browseLongerThe ID set is rebuilt server-side, so a stale page cannot hide a deleted title
View aggregates (Top 10)5 minutesExpensive to compute, safe to be slightly old
Top-10 board membershipRebuilt from the live snapshot on every requestA deleted or unpublished title must not linger while aggregates are cached
/healthNot cachedIt is a liveness probe
Admin SSEExplicitly unbufferedProxies and compression must not sit on the stream

Identity travels in three different ways​

The same request path carries three kinds of identity, and they are not interchangeable:

  1. Bearer ID token — the website's /me* routes. Verified with verifyIdToken.
  2. userId in the path or body — the legacy /users/* routes and most native calls. Checks existence only. Do not extend this pattern; see Security notes.
  3. Admin cookie — the back office. EventSource cannot send an Authorization header, so the stream route always reads the cookie.

What a healthy request looks like​

  • One 2xx in the Network tab, with access-control-allow-origin matching your origin.
  • The API log shows the stripped path.
  • /health names the environment you expected.

If any of those three is off, fix it before reading the React code.

Next: Upload to publish — where a title comes from.