React’s component-based API is excellent for a variety of use cases. However, it suffers from a shortcoming when used naïvely with asynchronous data fetching. Consider the following components:

Suppose we render <Foo><Bar /></Foo>. This will lead to an unnecessary request waterfall. <Bar> will not mount until <Foo> has received its data. The call to /api/bar does not depend on the results of the call to /api/foo, but this use of component nesting prevents sending the request for /api/bar until after the request for /api/foo has completed.

We often address this problem by only fetching data in top-level components. For example, Relay and Apollo offer powerful solutions that allow top-level data fetching while still colocating data requirements with components, by using statically declared data dependencies on those components.

However, this approach is naturally contained within the boundaries of a single route. At a route boundary, a component’s children may be one of multiple options, depending on the current navigation state. Performing data loading in parallel across route boundaries would require a route component to be explicitly aware of all of its possible child route components, and would limit reusability.

These route boundaries, then, are exactly where dynamic choices on component rendering can be determined statically, so it’s natural to split out data and code dependencies on a per-route basis.

The ideal solution is to fetch all data and code in parallel after navigating to a new set of routes. For good UX, we should also provide feedback on this loading state to users.