I have a Spartacus/SAP Composable Storefront app that uses Angular SSR (express). What I am essentially trying to do is to perform a 301 redirect with the SSR where the replacement URL is meant to be assembled dynamically by calling an external API to retrieve necessary pieces for the replacement URL.
In a service class, I've injected the SSR's Response object with @Inject(RESPONSE)
. Also, within the same service, the URL redirect logic is done in the subscribe block of the Observable returned by the HTTP client call to the API mentioned earlier. In the subscribe block, I would set the SSR's response object's HTTP status to 301 and also its location with the newly assembled replacement URL.
Once the code has reached the res.render
's callback block in the 'server.ts' snippet as shown below, res.redirect
will be invoked.
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
providers: [{provide: APP_BASE_HREF, useValue: req.baseUrl}],
}, (err, html) => {
if (res.statusCode === 301 || res.statusCode === 302) {
res.redirect(res.statusCode, (res.header as any).REDIRECT_URL)
}
//more logic here
});
});
All of this is working fine when there aren't much traffics. However, when there are a lot of concurrent requests to our Spartacus app, I realized that in the 'server.ts', the 'req' can sometimes get paired to a completely irrelevant 'res' from another request made to the same application. Hence, resulting in wrong 301 redirects.
At first I was suspecting that it could be an async issue(race condition) whereby I need to block the SSR render before the mentioned Observable is resolved. Thus, I have tried to implement an Angular resolver and placed the external HTTP call in the resolve
method. Then, I have implemented the update of the SSR Response object's status and location in the ngOnInit
method of the respective component by subscribing to the ActivatedRoute
's data to access the data that is required to assemble the replacement URL. However, I am still seeing the same issue whereby the 'res' and 'req' would intermittently get mismatched..
I am wondering what could possibly cause this issue? It could either still be an async(race condition) issue as I might not have implemented the Angular resolver correctly (I am pretty new to Angular and FE development), or that this is a concurrency issue whereby the response from another request is interfering with the request from another thread/session.
I do appreciate some help and direction on this issue as I've been stuck for quite awhile..
Turns out, I am better of making the external API call directly in the server.ts instead of relying on Angular's service. Honestly, I am not able to pinpoint the exact issue as to why are these race conditions happening.
From this very issue, it proves that it is not a good idea to write into the SSR Response or Request objects within Angular as it is prone to race conditions of such (I could be wrong on this statement due to the lack of experience at Angular and FE development).
As for the solution below, I have moved the res.redirect
out of the res.render
's callback. In this case, it makes sense because I do not want the page to get rendered at all if the initial request URL is not right in the first place.
Though the downside here is that for every request that my SSR route intercepts, the SSR server will have to perform an external API call to decide if it should get redirected or proceed to render the page as usual.
const axios = require('axios')
server.get('/web/nonCanonicalItemName/:itemCode', async(req, res) => {
let itemCode = req.params.itemCode;
try {
const response = await axios.get(`https://api.example/items/${itemCode}`, {
headers: {
'someHeader': 'foo'
}
})
const item = response.data;
let replacementURL = `/web/${item.canonicalItemName}/p/${itemCode}`;
if (req.url !== replacementURL) {
res.redirect(301, replacementURL);
} else {
res.render(indexHtml, {
req,
providers: [{
provide: APP_BASE_HREF,
useValue: req.baseUrl
}],
}, (err, html) => {
//some more custom logic here
res.send(html);
});
}
} catch (error) {
res.status(500).json({
error: 'An error has occurred'
});
}
})