As rules accumulate in a meta-framework's per-request interception step, how do you decide which belong there and which move elsewhere?
answer
- four places, not one
- envelope-only and pre-routing stays
- constant output belongs to the host
- data-dependent work belongs to the route
- a widening matcher is the first symptom
basics
~20 sKeep only rules that decide from the request envelope alone and must run before routing. Push constant behaviour down to the hosting layer, and move anything needing a resolved route or loaded data into the route and its writes.
solid answer
~50 sTreat the station as a shared budget: everything the matcher selects crosses it, so each rule added there taxes all of that traffic. For each rule, ask what it needs. If the answer is only the URL, headers or cookies, and the decision must happen before a route or a cached copy is chosen, it belongs here. If it is a constant — a fixed header, a blanket path redirect — a rule applied by the hosting target does the same thing without waking your code. If it needs the resolved route, the loaded record or the identity of a specific owner, it belongs in the route's data step and in every write, where that information exists. The symptoms of getting it wrong are a matcher that has grown to everything, per-request dependency calls at the front door, and routes that quietly stopped being served from a shared copy.
go deeper
Know that not everything cross-cutting belongs at the front door: some rules need data only the route has, and some are constant enough for the hosting layer to apply.
Classify a rule by its input. Envelope-only and needed before routing stays at the station; anything that needs the resolved route or loaded records cannot live there at all.
Show the diagnosis: a widening matcher, a dependency call in the step, and routes that quietly stopped being served from a shared copy are the three symptoms to look for.
Own it as a shared budget with an owner and an admission rule, and be explicit that the front-door check and the data-level check are different decisions rather than duplication to remove.
## Four places a rule can live Per-request interception is one station among four, and most arguments about it are really placement arguments. | Place | Sees | Costs | Good for | |---|---|---|---| | A rule the hosting target applies before your code | The request envelope, no application context | No invocation of your code at all | Constant headers, blanket path redirects, blocking obvious junk | | The interception step | Method, URL, headers, cookies | One invocation per matched request, plus its cold path | Decisions that must precede routing and the cache: gating a section, rewriting a path, choosing a variant | | The route's own data step, or a wrapper it calls | The resolved route, its parameters, the loaded records | Rides along with work already happening | Permission on a specific record, shaping what is returned | | The write | Everything the read sees, plus the intent to change state | Already the expensive path | The decision that must never be skipped | ## The questions to ask of each rule 1. **What is its input?** Envelope only, or resolved route and data? An envelope rule can live at the front; a data rule cannot be moved there no matter how convenient it would be. 2. **Must it happen before a route or a cached copy is chosen?** Redirecting a whole section, or substituting one path for another, genuinely must. Deciding what a page is allowed to show does not. 3. **Is its output constant?** If the rule produces the same bytes for every request, application code is the wrong place; a host-level rule applies it without an invocation. 4. **Does it vary the response body?** Varying pushes the route off any shared copy. That may be worth it — but it should be a decision, not a side effect of where someone put the code. 5. **Can it be walked around?** If a different URL reaches the same information, the rule is not enforceable here, and placing it here only creates the appearance of a control. 6. **How often is it wrong?** A rule that redirects one request in a thousand is taxing the other nine hundred and ninety-nine to catch it. ## Symptoms that the station has accreted too much - The matcher has widened until it effectively selects everything, usually because one rule needed one more path and nobody narrowed it afterwards. - The step now calls a dependency — a store, a permission service, a configuration fetch — and its latency has become the floor for every matched request. - Routes that used to be answered from a prerendered or shared copy are rendering per request, because something in the step started varying the body. - A cold path in the step is visible in tail latency for traffic that has nothing to do with the rules it contains. - The same policy now exists in the step and in the routes, with the two versions slightly out of step, and nobody can say which one is authoritative. - Debugging a page means reading a file that is not the page, for behaviour invisible in the route's own code. ## What must be duplicated anyway Some checks legitimately appear twice, and framing that as duplication causes bad architecture. The front-door redirect and the record-level permission check answer different questions: one decides **where to send a request**, the other decides **whether to hand over data**. Keep the second expressed once as a shared function called from the route's data step and from every write, and keep the first coarse enough that it never needs to know anything the envelope does not contain. Then the two cannot disagree, because only one of them decides permissions. ## Making the call in practice Write down, for the section in question, the list of rules and their inputs. Anything constant moves down to the hosting layer. Anything needing data moves into the route. What is left — envelope-only decisions that must precede routing — stays, and the matcher is then re-derived from exactly that list rather than inherited from whatever it was before. Re-run the exercise when the route layout changes, because a matcher written against an old layout fails silently. The organisational half matters as much as the technical half: this station is a single file that all matched traffic crosses, so it accumulates rules from every team that cannot find a better home for one. Decide who owns it, require that a new rule names its input and its matcher, and the accretion stops being automatic. Meta-frameworks differ in what they even offer here — some let similar interception be attached at levels of the route tree, which changes the answer by making a narrower home available — but the trade never changes: the earlier a rule runs, the more traffic it touches and the less it knows.
- A team wants one rule applied to literally every response. Where does it go?If the rule produces the same output for every request, a rule applied by the hosting target is the right home: it costs no invocation of your code and cannot be missed by a path the matcher overlooked. Reserve the interception step for rules whose outcome actually depends on the request.
- How do you argue against moving a data-dependent check into the step for consistency?Point out what it would need: a dependency call at a station every matched request crosses, inside a unit with tight limits, that still cannot see which record the URL concerns because routing has not run. Consistency comes from one shared permission function called by the route and the writes, not from moving the check earlier.
- What makes the interception step accumulate rules faster than other layers?It is the only place that applies to a whole section without touching each route, so it is the cheapest place to put anything cross-cutting. That convenience is real, which is why the guard has to be procedural: every new rule states its input and its matcher, and rules whose input is not the envelope are sent elsewhere.
- How would you detect that this station has become a latency floor?Compare the section's response times against a route excluded from the matcher, and watch the step's own invocation duration distribution rather than the page's. A rising floor with a heavy tail on otherwise trivial paths points at dependency calls or cold starts in the step rather than at the routes.
saying these in an interview costs you the question
- Puts every cross-cutting rule at the front because it is one file
- Adds a dependency call to the step for convenience
- Widens the matcher rather than moving the rule
- Calls the route-level permission check redundant duplication
- Ignores that varying the body costs the route its shared copy
- Leaves no owner for the file every matched request crosses