The empty string that crashed our dashboard

The Shield page worked from the menu and broke on reload. One empty select value, why it only failed on a cold load, and what we check now.

The new Shield page in the Atlas admin worked when you clicked your way to it. It also worked in every test. Then someone pressed reload, and got our 500 page: "Something went wrong", error id server_error. Nothing was wrong on the server. The cause was one empty string in a filter menu.

Following the error

The API logs were clean: no 5xx on any Shield endpoint. The admin is a single page app, so the 500 page was the app's own error screen, not a server answer. We reproduced it in a headless browser by loading /shield cold after signing in. The console had the real message, from the select component in our UI library:

A <SelectItem /> must have a value prop that is not an empty string.

The access log has three filters (level, action and rule), and each started with an "Any" item whose value was ''. An empty value felt natural: no filter means an empty query parameter. The component disagrees for a good reason. It reserves the empty string to mean "nothing selected, show the placeholder", so an item with that value would be impossible to tell apart from no selection. It throws instead of guessing.

Why only on reload

A throw during the very first render of the app, before the page has ever been shown, is treated as fatal and replaces the whole screen with the error page. That is the reload case. Reached from the menu, the app was already running, and the error stayed in the background as a console message while the rest of the page carried on. Same bug, two very different faces, and our tests only ever saw the quiet one.

The fix

Every "any" item now uses a real value, all, and the code that builds the request turns all back into no filter. The menus also say what they filter (All levels, All actions, All rules) instead of three identical "Any" labels.

const ALL = 'all'
const pick = (value) => value === ALL ? '' : value

The type for the filters still only accepts real levels, actions and rules, so a stray all can never reach the API by accident.

What we took away

  • Load every new page cold. Clicking through an app and opening a URL directly run different code paths. Both need a check before release.
  • A clean page is not proof. The bug was visible in the console from day one. We now read the console after every browser check, not only when something looks broken.
  • Empty is not a value. When a component asks for a value, give it a word. Turn it into an empty query at the edge, where you build the request.

//Keep reading

More from the blog

All posts →

//Run every brand from one place.

Start with one brand for free. Bring your team when you are ready.

$ npm install @atlas/client @atlas/nuxt

Free for one brandNo credit cardUpgrade anytime