An admin with one event hit the structure manager and got a picker with exactly one option in it. An admin with no events got an empty structure manager and a banner telling them so. Both pages ran the full structure query first, pulling competitions and folders and items out of the database, and then threw all of it away because there was nothing selected to show. The page was doing expensive work to render a dead end.
“Open the structure manager” sounds like one screen. It is really three. Depending on how many events the org owns and whether an admin has already picked one, hitting that page should be a redirect, an auto-select, or a build-it-empty prompt. All three can be decided before a single database query for structure data runs.
We added a feature to a multi-tenant SaaS platform we run that lets an org admin drill into a child event and edit its structure. The org is the parent account, and each event hangs off it as a child. The structure manager operates on whichever event is selected, carried in a ?div=<event> query param. The moment we shipped that, “go to the structure manager” stopped having an obvious meaning. The page now has a prerequisite: it needs a selected event before it can do anything useful, and there are three different ways “which event” can resolve.
In earlier versions we loaded the page first and sorted out the selection later. We rendered the chrome, kicked off the expensive structure load, then discovered there was no event to show and bolted a picker on top. That version pulled data it was about to throw away, and its empty state was an afterthought instead of a designed path. The shape that works resolves the selection on the way in, branches explicitly on the outcome, and only then decides whether expensive work is warranted.
The decision tree
Here is what an org admin gets when they hit the structure manager with no ?div= in the URL. The page walks the org’s child nodes, skips the shared pool node and anything that isn’t a real event, and builds a list. Then it branches on the count:
- Zero events. There is nothing to manage. Redirect to the event-creation screen. Don’t render an empty structure manager with a “you have no events” banner. Send them to the page that actually moves them forward.
- One event. There is no real choice to make, so the admin doesn’t make one. Auto-select it by redirecting with
&div=<the one event>appended. The admin lands directly in the thing they came for. - Two or more, none chosen. Now a choice exists and the user has to make it. Enter “select event mode”: render a picker and skip the expensive structure load. There is no selected event, so there is nothing to fetch.
- An event already selected (the URL has
?div=, or they got here via auto-select). Normal load. Do the real work.
That is none, one, many, plus the already-resolved case. Each gets its own branch, and the many case is the only one that needs the user, so it is the only one that renders a picker. Conflating any two of these gets you something worse. A picker with a single option is a speed bump, and a picker over zero events is a picker over nothing.
Resolve before you fetch
The performance payoff lives in the “many, none chosen” branch. The normal page load calls an init routine that hits the database to pull the structure: competitions, folders, items, the works. In select-event mode there is no selected event, so that query would return nothing useful. Instead of calling the init routine and getting an empty result, the code hand-builds a minimal empty structure literal in JavaScript. It holds empty maps for competitions, event folders, and items, an empty root list, and zero participant groups. The rest of the page renders against that placeholder, the picker shows, and the database is never touched for structure data.
That is the part I’d underline. The cheap win is noticing that when no event is selected, the query has no reason to run at all, and structuring the page so the expensive call sits behind the selection rather than in front of it. Caching the query or making it faster is not the point. “No event selected” and “nothing to fetch” are the same fact, and the code should treat them as the same fact.
This holds for any page with a selection prerequisite. The expensive work is almost always downstream of the selection. Load the page first and resolve the selection second, and you have inverted the dependency. You will either fetch data you discard or special-case your way back out of it. Resolve first, and the expensive branch is the only branch that fetches, and it only runs when there is something to fetch.
How the selection actually resolves
The page keys off whether the account it is operating on differs from the account in the URL. The logged-in org is one global, and the event whose data you are editing is another. When you select a child event, a helper swaps the operating account to point at the child while the URL account stays the parent. So “is an event selected?” reduces to “does the operating account differ from the logged-in account?” If they are equal, nothing is selected and you are in one of the none/one/many branches. If they differ, an event is selected and you do the normal load.
This equality check is downstream of a design choice we wouldn’t repeat: a mutable global that a hundred call sites read, swapped mid-request based on a query param. That swap is what made parent != child possible, and it caused a string of save bugs where writes targeted the parent account instead of the selected event, because so much code assumed the two were always the same object. That is a separate story. It matters here because the selection-resolution logic is the one place that is supposed to be deliberate about the difference. Everywhere else in the app that distinction is a landmine, and on the way into this page it is the explicit thing the routing branches on. If you are going to have an ambient “current context,” at least make the page entrypoint the spot where you nail down what it is.
The empty-folder state that only shows up in production
One more branch, because it only shows up in production. The event switcher normally renders inside a filter bar. But the filter bar only renders if the selected event has folders. So if an admin selects an event that has no folders yet, the filter bar doesn’t render, which means the switcher inside it doesn’t render either, and they are stuck with no way to switch back out. Brand-new event, no folders, no escape hatch.
The fix is a defensive check. When the page goes to show the structure manager, it looks for the filter-bar row, and if that row is missing it prepends the switcher on its own. The switcher no longer depends on the filter bar existing. This is the unglamorous half of selection routing. The happy paths are easy to reason about, and then there is the empty-folder, freshly created, first-time-through state where a component you assumed was always present isn’t. The entrypoint has to keep the navigation control alive in the empty case, or users will find the one path where they can get in but not out.
What to check on a page like this
When a page needs a selection before it can do anything useful, check whether it resolves that selection before it runs its expensive queries. Zero options should send the user to the page that moves them forward, one option should be picked for them, and only several options should show a picker. Then look at what happens when a freshly created item is empty, since a control that depends on content can disappear and leave users with no way out.