skip to content

In-App HTTP Endpoints

Non-document routes living in the same project, build and deployment as the pages, answering with JSON, a file or a stream. Interviewers probe when a route needs one at all.

on this pageshow

questions

5

In a meta-framework's file-based router, what is a non-document route, and how does it differ from a page route?

level: juniorimportance: must knowfreq 68%

answer

  1. same router tree, different output
  2. returns a response, not a view
  3. no layout, no head tags, no hydration payload
  4. shares the build, adapter and limits

basics

~20 s

A non-document route is a file in the same router tree that answers an HTTP request with JSON, a file, a stream or a redirect instead of an HTML page, sharing the pages' project, build and deployment.

solid answer

~40 s

A **non-document route** sits in the same router tree as the pages, but its module exports a handler that returns an HTTP response directly - JSON, a generated file, a redirect, or a body written in chunks - instead of describing a view. The router decides which pipeline a route takes before your code runs, so that response skips everything the document pipeline adds: layout wrapping, head management, and the `<script>` tags and embedded data the browser needs to bring markup to life. Everything else is shared - one repository, one build, one deployment and adapter, the same per-request interception in front, the same server-side modules to import. That sharing is the point of the surface, and also its constraint, because the handler inherits the pages' runtime and limits rather than choosing its own.

go deeper

for a junior

Recall that the same route tree can answer with something other than HTML, and that such a route lives inside the app's own project rather than in a separate service.

for a middle

Explain how the router decides which pipeline a route takes, and name precisely what a non-document response does not get: layout wrapping, head management and the client bundle's script tags.

for a senior

Be ready to say what the endpoint inherits from the deployment it ships in - runtime, timeouts, size ceilings - and when that inheritance is the reason to move the work somewhere else.

for a principal

Frame it as a contract question: every handler publishes a URL someone may come to depend on, so a team should be able to say why each one exists rather than adding them by habit.

A **file-based router** turns files in a directory tree into URLs. Most of those files describe a **document**: the framework runs the route's server code, renders a component tree to HTML, sends that HTML, and the browser then downloads the client bundle and brings the markup to life. A **non-document route** lives in the same tree and is matched by the same router, but instead of describing a view it exports a **handler** — a function that receives the request and returns an HTTP response the framework passes through largely untouched. ## How the router tells the two apart Meta-frameworks signal the distinction differently. Some use a filename convention inside the route folder; some look at the shape of what the module exports (one export per HTTP method rather than a component); some reserve a directory that sits outside the pages tree entirely. The mechanism underneath is the same in all of them: **before your code runs**, the router decides whether this route's result goes through the document pipeline or straight out as a response. That single decision is what the whole difference reduces to. A document route's output is *wrapped*: - the layouts above it in the tree render around it; - head management injects `<title>`, `<meta>` and `<link>` tags; - `<script>` and `<style>` tags for the client bundle are added so the markup can be brought to life in the browser; - the serialised data the client runtime needs is embedded into the HTML. A non-document route gets none of that. The handler's return value **is** the response: nothing wraps it, nothing is injected into it, and no client-side JavaScript is attached to it. ## What the two share, and what they do not | | Page route | Non-document route | |---|---|---| | Matched by the same router | yes | yes | | Same repository and same build | yes | yes | | Same deployment and adapter | yes | yes | | Per-request interception in front of it | usually | usually | | Can import the project's server-side modules | yes | yes | | Wrapped by layouts and head management | yes | no | | Ships a client bundle and a hydration payload | yes | no | | Response body | an HTML document | whatever the handler builds | The "shared" rows are the reason this surface exists at all: one repository, one build, one deploy, one set of shared modules and types, one place to configure the runtime. The "not shared" rows are the reason it is a different kind of object: it is an **HTTP interface**, not a view. ## What a handler can answer with - **JSON** — the common case, with `Content-Type: application/json`, consumed by a script, another service, or the app's own client code. - **A generated file** — a report, an export, a feed, an image — with its own content type and often a `Content-Disposition` header so the browser saves it instead of displaying it. - **A redirect** — a `3xx` status and a `Location` header, useful for short links and for handing a caller onward after some server-side bookkeeping. - **A bare status** — `204` when a request succeeded and there is nothing to say, `304` when the caller's cached copy is still good, `4xx`/`5xx` with a machine-readable error body. - **A stream** — a body written in chunks rather than buffered, so the first bytes leave before the last ones exist; in some deployments such a response can be held open for a long time, in others it cannot. ## Where the handler runs It runs wherever the pages' server code runs, because it is the same deployment artifact. Depending on how the app is built and hosted, that is a long-running server process, a per-request function, or a constrained edge runtime. The handler does not get to pick: it inherits the environment the pages were configured for, along with that environment's timeouts, request and response size ceilings, and the subset of server APIs the runtime exposes. Most frameworks allow a **per-route exception** to that, but the exception is something you deliberately declare, not the default. ## Traps worth naming 1. **Assuming the layouts still apply.** A common first surprise is a handler whose response comes back without the surrounding chrome the developer expected. It never had it; only documents go through that pipeline. 2. **Assuming it is private because no page links to it.** It is a URL served by a public deployment. Nothing about living in the same project makes it internal. 3. **Calling your own endpoint from your own server render.** The route's server code can import and call the same module the handler calls. Routing that call back out through HTTP adds a network round trip, a serialisation step and a failure mode, and buys nothing. 4. **Forgetting that a handler is a contract.** A component can be renamed freely; a published URL, method and payload shape cannot, once something you do not deploy depends on it.

  • Does a non-document route still go through the app's per-request interception step?
    Normally yes - interception matches by path, so unless the matcher excludes it, the step that runs in front of pages runs in front of the handler. That matters in both directions: shared request-level behaviour applies automatically, and an over-broad exclusion can quietly leave the endpoint unguarded.
  • Can a handler's code end up in the client bundle?
    The handler itself does not - it is server-side and the build keeps it out of the browser's module graph. What can leak is a module the handler shares with client code: if a file imported by a component also pulls in server-only logic, the bundle takes it along. The boundary is enforced by what the client graph imports, not by which folder the code sits in.
  • Why does a redirect from a handler differ from a client-side navigation?
    A handler's redirect is an HTTP response - a `3xx` status with a `Location` header - so the browser or any other client re-requests the new URL over the network. A client-side navigation happens inside an already-running application and is invisible to callers that are not executing your client code. Only the HTTP form works for them.

A shop's front door and its goods-in door are in the same building on the same lease, with the same opening hours - but only one of them is decorated for customers.

saying these in an interview costs you the question

  • Calls it a separate service that happens to live in the same repository.
  • Expects the surrounding layouts to wrap a JSON response.
  • Thinks a client bundle or hydration payload is attached to a handler's response.
  • Assumes the handler can pick its own runtime independently of the pages.
  • Believes only JSON can be returned, never a file, redirect or stream.
open as a page

When does an app genuinely need its own HTTP endpoint instead of doing the work inside the server-rendered route?

level: middleimportance: must knowfreq 57%

basics

~20 s

Only when a caller you do not control must reach it by URL - a third-party callback, a native client, a script - or when the response is not a document. Otherwise the route's own server code is the cheaper surface.

open as a page

You added a non-document route for your own pages to call, and outside callers found it. Why was it reachable, and how do you harden it?

level: seniorimportance: should knowfreq 51%

basics

~20 s

Because an endpoint is just a URL - the one surface of the deployment an outside caller reaches directly, with no interface in front of it. Harden it in the handler: authorise the caller, validate every input, limit the rate.

open as a page

A non-document route ships in the same build and deployment as the pages. What does it inherit, and which handlers does that rule out?

level: seniorimportance: should knowfreq 46%

basics

~20 s

It inherits the pages' adapter, runtime and per-request ceilings - execution time, body and response size, available server APIs, scaling and release cadence. Handlers needing a long-held connection, long compute or native APIs are what that rules out.

open as a page

Your team keeps adding in-app endpoints that the pages' own server code could call directly. How do you decide which deserve to exist?

level: principalimportance: nice to knowfreq 37%

basics

~20 s

Make each endpoint name an outside caller or a non-document response; everything else stays a server-side module the render calls directly. Keep the rule in that module so an endpoint is only ever a thin edge over it.

open as a page