Skip to content

Instantly share code, notes, and snippets.

@shakyShane
Last active April 13, 2026 17:20
Show Gist options
  • Select an option

  • Save shakyShane/087378b166713460aed0f3206d929e74 to your computer and use it in GitHub Desktop.

Select an option

Save shakyShane/087378b166713460aed0f3206d929e74 to your computer and use it in GitHub Desktop.

Next.js behind closed-by-default nginx — notes for SRE / backend

What you’re optimizing for

Traffic hits nginx first. Node/Express is not on the raw internet path in the sense that:

  • Unknown URLs stop at nginx (public/ file or 404); they never reach Express.
  • Only explicit locations forward to the app (documents, /_next/image, health/debug, etc.).
  • Heavy caching (especially /_next/static/ from disk, document proxy_cache for allow‑listed HTML/RSC paths) reduces origin load and absorbs a lot of read traffic before it touches Node.

That’s a deliberate defense‑in‑depth / blast‑radius posture, not “Next out of the box.”


What you did not give up

  • Normal App Router behavior for routes you explicitly expose: SSR, RSC Flight (GET /?_rsc=…), /_next/image, etc. can all work as long as nginx forwards them.
  • Next’s own caching (including Cache Components / 'use cache') still runs when a request reaches Node. The edge is an additional cache layer, not a replacement for Next.
  • Operational clarity: if Express logs are quiet for a URL class, nginx handled it (static, 404, or full document HIT).

Real limitations / tradeoffs from the closed‑by‑default design

These are platform constraints you accept in exchange for the nginx gate. They’re mostly routing and cache‑semantics, not “Next is broken.”

1. No accidental exposure — and no automatic discovery

Next can add routes freely in code; nginx does not know until you update config (and redeploy/reload). Anything not matched by an allow‑listed proxy path is invisible to Node from the internet.

Implication for SRE/BE: new pages, APIs, webhooks, OAuth callbacks, etc. need a checklist item: add/adjust nginx location + cache allowlist maps if applicable.

2. /_next/* is intentionally dangerous — you must whitelist carefully

You block generic /_next/* and only pass known internals (e.g. /_next/image). That’s good for security, but:

Implication: a Next upgrade that introduces a new required internal path would show up as edge404s until nginx is updated. Treat major Next bumps as a small nginx compatibility review.

3. Two caching layers (nginx + Next)

  • Default documents (/, /images, …): nginx may ignore upstream Cache-Control and use fixed edge TTL behavior.
  • Paths that honor upstream CC (e.g. /cache-demo): edge freshness can follow Next’s s-maxage / revalidation so you don’t “freeze” stale HTML for minutes.

Implication: invalidation is not one button. Deploys namespace keys via build id; time‑based freshness depends on which nginx profile that path uses and what Next emits. SREs should know per‑route edge semantics, not assume one global rule.

4. Cache key / Vary discipline

Your edge document key includes things like build id, host, path, locale cookie. Anything that changes HTML meaningfully but is not in the key risks wrong shared cache entries.

Implication: if a page varies on auth, feature flags, A/B, tenant, cookie‑gated experiments, etc., you must either:

  • Include those dimensions in the nginx cache key, or
  • Bypass document cache for those routes, or
  • Accept incorrect sharing.

This is the main “footgun” class for BE teams adding personalization.

5. RSC / ?_rsc= traffic is still “just HTTP”

Flight requests (e.g. GET /?_rsc=…) hit the same path‑based nginx rules as a normal document for that route. That’s fine, but it means:

Implication: client prefetch and soft navigation generate more origin‑shaped traffic than “one HTML per user click.” It’s usually small and highly cacheable, but monitor / (and other layouts) — not only leaf URLs.

6. POST, streaming, long‑lived connections

Your design is oriented around GET/HEAD document caching and explicit raw proxy blocks. POST/PUT, streaming, SSE, WebSockets (if you add them) need dedicated locations and must not be blindly document‑cached.

Implication: API routes and mutation endpoints belong in the “explicit allow, no document cache” bucket with clear body size / timeout settings on nginx.

7. Observability split

Some requests never reach Express → no Node access log for them. Debugging is nginx access logs + response headers (your edge_cache format) plus app logs when the request actually proxies.

Implication: on‑call runbooks should say “check nginx first” for 404s and mystery latency.


How to phrase the “limitations” honestly

  • You trade Next’s default “everything on one origin path just works” for explicit edge policy: allowlist, caching, and hiding Node from broad internet reach.
  • The limitations are mostly operational and cache‑correctness, not missing Next features: anything you route and cache correctly behaves like Next; anything you forget to route fails closed at nginx (by design).

Suggested one‑liner for stakeholders

“We terminate the public internet on nginx with deny‑by‑default routing and aggressive caching; Next/Express only sees explicitly allow‑listed HTTP. That gives strong safety and scale, but every new surface area must be wired in nginx, and we must treat cache keys and dual cache layers as a first‑class design concern.”

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment