A routed page can compile, pass its authorization checks, have documentation, and still be functionally absent. If the people entitled to use it cannot discover a normal way in, the product behaves as though the capability was never built.
That is easy to miss in a modular Blazor application. One team adds pages in one project, another module owns a second page tree, and a central hub or navigation catalogue tries to present the whole product coherently. Each local change can look correct while the experience between modules quietly breaks.
The remedy is not another release checklist. It is an executable discoverability contract.
Define the invariant at the page boundary
A useful rule is simple:
Every static operational page must have one intentional entry point whose visibility matches the page's access rule.
“Static” matters. A detail page with an identifier in its route is normally reached from a list, search result, or parent screen. It does not need a global menu item. A screen that answers on two route aliases is still one page; adding two cards would create duplication rather than discoverability.
The rule should cover pages, not merely strings that resemble URLs. That keeps the test aligned with the product concept people actually use.
It should also say “intentional entry point,” not “mentioned somewhere.” A link buried inside another page may technically make a route reachable, but it does not necessarily make the capability findable. Likewise, a help article that names a screen is documentation, not navigation. Requiring a deliberate card or menu entry forces the information architecture to be explicit.
Scan every module, not only the original tree
Architecture tests often begin with a useful local assumption: all administrative pages live under one directory, namespace, or assembly. The test passes for years. Then a new module introduces another tree and the guard continues to report green because it never sees the new pages.
This is a particularly awkward failure because both the feature and the guard are “working as designed.” The design boundary is simply stale.
The test therefore needs a completeness check as well as a matching check. Enumerate every known page tree. Parse the routed components. Exclude only clearly contextual routes. Then compare the resulting pages with the navigation catalogue.
Add a sanity assertion too. If a mature tree suddenly produces zero—or far fewer—pages, the parser or directory assumption probably broke. Without that guard, a failed scan can look like perfect compliance.
When a new module owns pages, extending the scan is part of integrating that module. This turns architectural coverage into an explicit responsibility rather than inherited folklore.
Keep navigation and authorization separate
The navigation entry should use the same access decision as the destination page. If the card is broader, people see an invitation that ends in refusal. If it is narrower, people who are entitled to use the page cannot find it.
It is tempting to reuse a nearby permission because it is convenient. That can be wrong in both directions. When no existing decision expresses the page's real audience, adding a focused visibility flag is usually clearer than bending an unrelated one.
But matching visibility does not turn navigation into security. A hidden card only changes what the interface presents. A direct request, a bookmarked address, or another client can still reach the endpoint. The page or server operation must continue to enforce authorization independently.
This separation produces a healthy pair of invariants:
- navigation makes entitled capabilities discoverable;
- authorization rejects requests that are not entitled.
Testing both is stronger than asking either mechanism to do two jobs.
Prefer a precise contract over “linked somewhere”
A broad reachability scan can be a good first diagnostic. Search the codebase for links to each route and identify pages that have no inbound reference. That quickly reveals obvious orphans.
As a permanent contract, however, “linked somewhere” is weak. A tab rail is visible only after someone has already reached a page in that tab set. A contextual link may depend on data or state. A help page may mention the route without providing product navigation. Source text can prove that a string exists; it cannot prove that the intended audience has a clear entry point.
Tightening the rule to require the chosen navigation catalogue makes the standard less ambiguous. It also creates useful pressure: when a page fits nowhere, the team must decide whether the navigation model is incomplete, the page is contextual, or the feature should not be exposed yet.
Accept the maintenance cost deliberately
The stricter contract adds work. New pages need catalogue entries, accurate visibility mapping, and suitable grouping. Route conventions must be understood well enough for the test to distinguish static pages, detail pages, and aliases. Large navigation surfaces may expose sections that no longer fit their historical categories.
Those are not test problems to hide with a long exemption list. They are product and architecture decisions the test has made visible.
Keep exemptions small, named, and justified by a navigation model rather than by inconvenience. Fail when an exemption becomes stale. If a page genuinely belongs behind a list or workflow, encode that reason. If it is an operational destination, give it an intentional entry point.
The payoff is modest but durable: completed capabilities do not disappear between modules, permission drift becomes easier to see, and a new page cannot quietly land outside the product's navigation contract.
A page is not shipped merely because its route exists. It is shipped when the right person can find it—and the wrong person still cannot execute it.
Top comments (0)