feat(plugin): named page types as the unit for per-template config + metrics (v0.34.0) - #70
feat(plugin): named page types as the unit for per-template config + metrics (v0.34.0)#70harper-joseph wants to merge 1 commit into
Conversation
…metrics; v0.34.0
A route says WHETHER a path is prerendered. It could not say WHAT KIND of page it
is, so everything template-shaped was keyed on the matched route's path — and a
template reachable by two URL shapes became two unrelated things.
Adds a top-level `pageTypes` list of named templates (`home`, `category`, `pdp`)
that `ingress.routes` entries point at with `pageType`. Several routes may share
one name; that is the point.
metrics route_serve/route_page_age -> pagetype_serve/pagetype_age, labelled by
template rather than route path. Two category routes now report as one
series instead of two a reader had to know to add together.
cost render_time gains the template in its previously-unused third dimension.
Render cost is the fleet capacity input but was recorded as one
undifferentiated distribution, so "PDPs are expensive to settle" was
believable and not showable. It now joins delivered freshness on one key.
cadence resolveRenderInterval precedence becomes route > pageType > stored >
default. A cadence shared by several routes lives once, so the copies
cannot drift with only the first match observed.
job the claim payload carries the DECLARED name (never the label's fallback),
so browser-side render rules can scope by template instead of restating
the same URL shapes as regexes in the render fleet's own config.
explain /prerender_admin reports the template, whether it is declared, and the
cadence that follows from it.
config a pageTypes entry no route references is an `info` finding — that state
is a spelling mismatch whose only other symptom is silence.
Adoption is incremental: with no `pageType` on any route the label falls back to the
route path and then the route class, which is exactly what shipped before. Label
cardinality stays bounded by construction — no arm derives a label from the request.
Renaming route_serve/route_page_age is a clean break rather than a silent change of
meaning; they shipped in v0.33.0 and no dashboard consumes them yet.
There was a problem hiding this comment.
Code Review
This pull request introduces "Page types (templates)" to group multiple routes under a single template name (such as home, category, or pdp) for unified metrics and configuration. It updates the configuration schema, ingress classification, render queue, admin UI, and analytics tracking to use page types instead of individual route paths. Feedback is provided regarding a performance optimization in packages/plugin/src/config.js where calling routePageTypes() inside a filter loop results in O(N * M) complexity, which can be optimized to O(N + M) by extracting the call outside the loop.
| // its symptom is silence: the type's settings simply never apply and its name never appears | ||
| // in metrics, which reads exactly like "this template gets no traffic". Info, not warn — the | ||
| // same list is legitimately shared across deployments whose route sets differ. | ||
| const unusedPageTypes = declaredPageTypes().filter((name) => !routePageTypes().has(name)); |
There was a problem hiding this comment.
Calling routePageTypes() inside the filter callback causes the route list to be iterated and a new Set to be constructed for every single declared page type. This results in an routePageTypes() once outside the filter and storing the resulting Set in a local variable.
| const unusedPageTypes = declaredPageTypes().filter((name) => !routePageTypes().has(name)); | |
| const referenced = routePageTypes(); const unusedPageTypes = declaredPageTypes().filter((name) => !referenced.has(name)); |
The problem
A route says whether a path is prerendered. Nothing said what kind of page it is, so every template-shaped concern was keyed on the matched route's path:
route_serve/route_page_age(shipped v0.33.0) labelled byroute.path. A site whose category pages are reachable by two URL shapes — e.g.prefix /catalog/andcontains /category/— got two unrelated series that only a reader holding the route list could add back together.render_timehad no template dimension at all (its thirdrecordAnalyticsslot was unused). Render cost is the fleet capacity input, but it was one undifferentiated distribution — so "PDPs are expensive to settle" was a thing you could believe and not a thing you could show.renderIntervalattached to a pattern, so two routes sharing a template had to repeat it, and the copies could drift with only the first match ever observed.waitForrules by regexpathPatternin its own config — a second pattern language for the same routing fact, in another repo, with nothing keeping the two in step.The change
A top-level
pageTypeslist of named templates thatingress.routesentries point at withpageType. Several routes may share one name; that is the point.route_serve/route_page_age→pagetype_serve/pagetype_age, labelled by templaterender_timegains the template in its free third dimensionpageTypesentry no route references is aninfofindingDesign notes
pageTypevspageTypeLabelare deliberately separate.pageTypeis the declared name ornull;pageTypeLabelis always a string (name → route path → route class). Metrics take the label because every request must produce one or the series develops holes that read as lost traffic. The queue job sends the declared name only — were it to send the fallback, a browser-side rule scoped to a template would fire on a route never declared to be one.The label is computed inside the classification, not offered as a
pageTypeLabel(x)helper. A helper would have to agree with each caller about what its argument is called (the read path saysroute, the classifier saysentry) — exactly the driftrouteClass.jsexists to prevent.render_timebillsresult.id, notcacheKey— the latter may already have been re-pointed at a redirect destination, which would bill the render to the template it landed on rather than the one that paid for it.Config shape deviates from a keyed map.
mergeIntowalks the defaults shape, so an open-keyedpageTypes: {home: {...}}map would be silently dropped key by key. An array of named entries (same pattern asingress.routes) gives identical declare-once/reference-by-name semantics with no surgery on the shared merge layer.Compatibility
Adoption is incremental. With no
pageTypeon any route the label falls back to the route path, then the route class — exactly the values emitted before. Name one route at a time; nothing resets.The metric rename is a clean break, not a silent change of meaning.
route_serve/route_page_ageshipped days ago in v0.33.0 and the rollout dashboard reads onlybot_request/bot_serve/page_age, which are untouched. Renaming avoids a metric whose name says "route" while its label is a template.Label cardinality stays bounded by construction: every arm resolves to a configured name, a configured path, or one of three class constants. No arm derives a label from the request.
Testing
408 tests pass; lint and format clean. New coverage in
routeClass.test.js(two routes → one label, the fallback chain, the four-level cadence precedence, malformed input dropping the field not the route, prefix-mode parity, duplicate names) andbotServe.test.js(dimension order, verbatim label pass-through).Follow-ups (this is PR 1 of 3)
packages/browser—WaitForRule.pageTypes, honoringjob.pageType.render-service— swappathPattern: '^/product/'forpageTypes: ['pdp'], collapsing the two pattern languages into one vocabulary owned here.🤖 Generated with Claude Code