skip to content

Dev Server vs Production

Routes compiled per request with caches off, server modules swapped in place on save, and config that is simply there in development. Asked because the bugs it hides surface only in the build.

on this pageshow

questions

5

In a meta-framework's development server, how is a route served differently from the way the built output serves it?

level: juniorimportance: must knowfreq 72%

answer

  1. tuned for the edit loop
  2. compiled on demand, not ahead
  3. every request re-renders
  4. cache tiers bypassed while developing

basics

~20 s

A development server compiles a route on demand and renders it again on every request, with prerendering skipped and the cache tiers switched off. The built output compiles once ahead of time and reuses whatever it is allowed to reuse.

solid answer

~40 s

In development the server keeps your source, compiles a route the first time something asks for it, and re-runs the whole render for every request. Prerendering is skipped, the cache tiers are bypassed or heavily reduced, and an edit shows up on the next request. That is deliberate: the development server is tuned for *see my edit now*, not for *behave the way the deployment will*. The built output is the opposite — routes are compiled ahead of time, prerenderable ones are rendered once during the build, and each cache tier is live, so a request may be answered from a stored document or a cached data result without your render code running at all. So freshness, staleness, reuse and a route's static-or-dynamic outcome are all things the development server cannot show you.

go deeper

for a junior

Remember the three differences: compiled on demand, re-rendered every request, caches off. That is enough to explain why an edit appears instantly and why a first visit pauses.

for a middle

Be able to say what each difference costs you in observability: no classification of routes, no staleness, no reuse, no realistic payload. Name what the development server is still authoritative about.

for a senior

Show the operational consequence — which bug classes can only be caught by running the built output, and where you put that step so it runs before a deploy rather than after one.

for a principal

Frame it as a deliberate trade the framework made on your behalf: loop speed bought with fidelity. Your job is deciding where the team pays the fidelity back.

A meta-framework gives you two ways to run the same application, and they are tuned for opposite goals. The **development server** exists to shorten the distance between typing a character and seeing the result. The **built output** exists to answer a request as cheaply as the framework can arrange. Nearly every surprise in the gap between them follows from that single divergence of purpose. ## Compiled when something asks for it In development the server holds your source and compiles only what a request actually needs. The first request for a route triggers compilation of that route and the modules it pulls in; later requests reuse the compiled result until a file it depends on changes, at which point the affected part is compiled again. Two visible consequences follow: - a large application starts almost immediately, because nothing has been compiled yet; - the **first** visit to each route pauses briefly, and that pause says nothing about how fast the route is — it is compile time, not render time. The built output never does this. Everything was compiled once, by the build, before any request existed. A request executes already-compiled code, or is answered without executing your code at all. ## Rendered again on every request The second difference is that development skips prerendering. A route that the build would render once and store as a document is, in development, rendered per request the way a fully dynamic route is. Your loader or data function runs again, your components render again, and the HTML is produced again. That is why an edit to a page that is *prerendered in production* still appears instantly while you are working on it. The cost is that the **classification** a build performs — deciding which routes can be rendered ahead of time and which must run per request — is simply not exercised in development. You cannot look at a development response and tell which side of that line a route will land on. ## The cache tiers are off The third difference is caching. A deployed meta-framework typically layers several caches: a stored prerendered document, a cached result for a data fetch, a cached rendered fragment, and a client-side store of already-visited routes. Frameworks differ in how many of these they have and what each is called, but in development they are disabled or reduced to near-nothing, precisely so that what you see is what your code currently does. The consequence is uncomfortable and worth stating plainly: **a page that shows fresh data in development is evidence of nothing.** It is fresh because caching is off, not because your revalidation is right. ## What that hides | Behaviour that matters in production | What the development server shows instead | |---|---| | Whether a route is prerendered or rendered per request | Everything rendered per request | | How stale a response may be before it is refreshed | Always current | | Whether regeneration of a stored page after a window, or on demand, actually works | Never exercised | | Which requests are answered without running your code | Your code runs every time | | Compiled size and what ships to the browser | An unoptimised development build | | First-visit latency | Compile time mixed into the measurement | ## How to read a development observation honestly Development is authoritative about one thing: **what your code does when it runs**. Logic, markup, styling, and the shape of the data are all faithfully represented. Treat everything about *when* and *how often* your code runs as untested until you run the built output. Practically, that means: 1. Debug behaviour in development — it is the fastest loop you will get. 2. Verify freshness, reuse, classification and payload size by building and running the build, not by watching the development server. 3. Do not tune a cache window against the development server; it will confirm whatever you already believe. A useful habit is to treat the development server as a **draft renderer**: correct about content, deliberately wrong about economics. Once that distinction is explicit in a team's head, the whole class of "it worked locally" surprises shrinks to the smaller set that really does need the deployed target to reproduce.

  • Why does the first visit to a route in development often pause while later visits do not?
    The server compiles that route and the modules it needs at the moment of the first request, then reuses the compiled result until a file it depends on changes. The pause is compilation, not rendering, so it is not a measurement of the route's speed.
  • If the caches are off in development, how do you see whether a response would have been reused?
    Run the built output locally and issue the same request twice. If the second response comes back without your loader or render code executing, that is the reuse the development server was hiding. Response headers and the build's own route report give you the same information from the outside.

Development is a rehearsal with the house lights on: you can stop and fix anything, but you cannot tell how the show reads from the seats.

saying these in an interview costs you the question

  • Thinks the development server serves a route the same way the deployment does
  • Treats fresh data in development as proof that revalidation is configured correctly
  • Reads the first-visit pause as the route being slow rather than being compiled
  • Believes prerendering happens in development, so static-or-dynamic is visible there
  • Tunes a cache window by watching the development server
open as a page

Why does code that works against a development server fail only when the production build runs it?

level: middleimportance: must knowfreq 62%

basics

~20 s

The build executes your code: it compiles every route and renders the prerenderable ones on the build machine, with no browser and no incoming request. Paths a development session never reached fail there for the first time.

open as a page

When a development server swaps a changed server module in place, what happens to state that module held at module scope?

level: middleimportance: should knowfreq 50%

basics

~20 s

Module-scope state is re-created: the fresh copy of the module runs its initialisation again, so any pool, cache or registry it built is new and empty, while the previous instance can survive unreferenced and still holding its resources.

open as a page

Your team keeps finding breakage only after deploying because everyone works against the development server — how would you change that?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Verify against the built output: build on every change in the pipeline so every route is compiled and prerendered, and run the build locally before anything risky ships. Keep the development server for the edit loop.

open as a page

A team proposes making the development server behave exactly like the deployment — how would you evaluate that proposal?

level: principalimportance: should knowfreq 38%

basics

~20 s

Treat the divergence as bought, not accidental: it pays for the edit loop. Buy parity as automated gates and a separate mode, keep the fast loop as the default, and refuse half-measures that lie in a new way.

open as a page