BLOG

How I Broke the Routing System of Our Enterprise App

12/23/2017Time to read: 2 min

Working in the UIPlatform team, I was in charge of refactoring the legacy front-end routing system. When I finished, everything behaved the same as before (for the most part, except a dozen regression issues), except when a user downloads a document from our site, navigation will stop working after that.

How does downloading break the routing system? We were all confused. I will briefly explain our routing infrastructure first.

Our app is a giant single page app that uses JQuery, backbone and a homemade routing system. We use hashes in URL for routing. For example, a typical URL would be https://domain.com/#currentpage. Changing the hash part will not make a request to the server because everything after the hash is ignored.

Each view is a Backbone component that extends a superclass that we call main_nav. main_nav listens to browser hash change event. When a hash change event happens main_nav's onHashChange function will be called. Other views that extend main_nav will override this function to do some custom rendering.

As we are moving away from hashes in URL, in favor of HTML5 History API fallback. We wanted to use History.js to ease the transition.

This is what I did: in main_nav, instead of directly listening to browser's hash change event, I changed it to listen to route change from History.js.

// main_nav
import History from 'history';

const history = History.createHistory(); 

const unlisten = history.listen(initView);

When I attach the listener, I get a function to unlisten. When should I unlisten the route change event? I ended up sticking it to window.onbeforeunload. When window unloads all the resource, we will unlisten for route changes. Make sense right? I learned from college to always clean up the resource.

But the problem is that we use window.location to trigger a download. For example:

function downloadExcel() {
    window.location = "https://domain.com/excel.xls";
}

Normally, setting window.location will navigate to the path. But if the server sets the MIME type to a nonsensical value in the response header, the browser will try to download it. The current page will stay as it is, just that window.onbeforeunload will be triggered too.

So that's how it broke. When a user downloads a file, window.onbeforeunload is called and detached my route listener. The fix is pretty simple, just remove the unlisten function. In the future, maybe we need to move away from triggering download by setting window.location.

Wow, I got lucky there
How my messed up code ended up saving me from regression
Create a Event System Using ES6 WeakMap