The title may lead you to think that this post is another ranting post about the downside of a “Single Page Application”. It is more about shedding some light on the performance perspective to keep in mind while designing the SPA. Especially if your SPA consumes APIs from different domain services.
If you are designing an SPA which consumes the API from the same domain of the SPA, then great. You should skip this article if your SPA only pulls from the API on the same domain.
Most SPAs involve “microservices.” They consume different endpoints of services serving by different domains within the SPA. This adds resilience, fault tolerance, and an improved user experience of our product. Multiple domain requests become inevitable until and unless we strictly adhere to the same domain app API Gateway — Microservices Pattern for our SPA.
Let’s Imagine we have a
/users/report/:id served from domain
api.example.com. Our SPA is served from
:id means its a value that can change for every request.
Can you guess the issue with the above API design with respect to CORS (Cross-Origin Resource Sharing) and its impact on the performance of our SPA?
Here’s a brief Introduction of CORS from MDN:
This is due to the fact that we need to send a header that is not CORS-safelisted-request-header. An example header is
Authorization, x-corelation-id. Frequently our
Content-Type header value is
application/json. This is not an allowed value for the
Content-Type header for cors-safelisted-request-header.
api.example.com server accepts
application/json, our SPA domain
spa.example.com will first send an HTTP request by the
OPTIONS method. It is sent to the resource
/users/report/12345 on the other domain
api.example.com. To determine whether the actual request is safe to send, the option is sent preflighted. Cross-site requests are always preflighted like this, since they may have implications for user data.
It’s the job of
api.example.com server to let the other domain
spa.example.com know it’s safe to send the data. You might have done something similar to this for CORS inside your Application.
api.example.com server sends the proper response from “OPTIONS” method to other domain
spa.example.com then only the actual data with the request you were trying to make is done.
So to access a resource
api.example.com/users/report/12345two actual requests were performed.
You might say yes. We can use the
Access-Control-Max-Age header to cache the results of a preflight request. The next time we access the resource
spa.example.com there is no preflight request.
Yes, that’s true, but then remember the title — The terrible performance cost of CORS requests on the single-page application (SPA). This comes from the API that we’re are consuming and the way it’s been designed. In our example, we designed our API
:id means its a value that can change.
The way preflight cache works is per URL, not just the origin. This means that any change in the path (which includes query parameters) warrants another preflight request.
So in our case, to access resource
api.example.com/users/report/123987, it will trigger four requests from our SPA in total.
If you have a slow network, this could be a huge setback. Especially when an OPTIONS request takes 2 seconds to respond, and another 2 for the data.
Now imagine your SPA application making millions of such requests for different domains. It will have a terrible impact on your SPA’s performance. You’re doubling the latency of every request.
SPAs are great in their own domain. But for consuming different domains they come with their own cost. If the API is poorly designed, the latency issues of your SPA can hurt more than the benefits they provide.
There is no solution or technology that is wholly good or bad. Knowing its shortcoming and what it takes to make it work are what matters. This is what differentiates your Application from the others.