T
TanStack2mo ago
like-gold

Set HTTP status code from loaders

Happy to build a full example repo to explain this further, but hopefully should be straightforward. I am calling some external APIs in my loader, and sometimes they fail. Right now what happens is the errorComponent renders fine, but the HTTP status code is 200, and there's noway to change that. Tried setResponseStatus but that just gets ignored. Right now the only way I can get around this is a funky way of setting a custom header, then checking for that header in a custom Start handler on the server, then setting the response status code from there. Seems a bit weird, I'm sure there's a good reason, but would like some insight on this if possible. I know it doesn't really matter, but a lot of scenarios like sites behind Cloudflare, those 200s get cached and the page is then cached with an error component on it even if the API ends up working later on.
5 Replies
multiple-amethyst
multiple-amethyst2mo ago
which HTTP status are we talking about? the SSR response? a reproducer would be helpful
like-gold
like-goldOP2mo ago
export const Route = createFileRoute('/demo/start/ssr/full-ssr')({
component: RouteComponent,
loader: async () => {
throw new Error('Not implemented')
},
})
export const Route = createFileRoute('/demo/start/ssr/full-ssr')({
component: RouteComponent,
loader: async () => {
throw new Error('Not implemented')
},
})
Something as simple as this. That throws an Error, it gets reflected on the frontend fine in my errorComponent, but you look at DevTools, the status code for that page is still a 200 even though the loader returned an error A lot of cases where returning something other than 200 is pretty important. The extremely dumb hacky way of I'm doing this right now is
if (import.meta.env.SSR) {
const { setResponseHeader } = await import('@tanstack/react-start/server');
setResponseHeader('x-ssr-error-status', '500');
}
if (import.meta.env.SSR) {
const { setResponseHeader } = await import('@tanstack/react-start/server');
setResponseHeader('x-ssr-error-status', '500');
}
In the loader and then
if (import.meta.env.SSR) {
const { getResponseHeaders } = await import('@tanstack/react-start/server');
initialHeaders = getResponseHeaders() as Headers;
}
const additionalSsrHeaders = {};

if (initialHeaders && initialHeaders.has('x-ssr-error-status')) {
additionalSsrHeaders['x-ssr-error-status'] = initialHeaders.get('x-ssr-error-status')!;
}
if (import.meta.env.SSR) {
const { getResponseHeaders } = await import('@tanstack/react-start/server');
initialHeaders = getResponseHeaders() as Headers;
}
const additionalSsrHeaders = {};

if (initialHeaders && initialHeaders.has('x-ssr-error-status')) {
additionalSsrHeaders['x-ssr-error-status'] = initialHeaders.get('x-ssr-error-status')!;
}
In the headers() method of the file, and THEN another
const ssrErrorStatus = res.headers.get('x-ssr-error-status');

if (ssrErrorStatus) {
const headers = new Headers(res.headers);

headers.delete('x-ssr-error-status');
headers.set('Cache-Control', 's-maxage=0, no-store, no-cache, must-revalidate, max-age=0');

return new Response(res.body, {
status: Number(ssrErrorStatus),
headers,
});
}
const ssrErrorStatus = res.headers.get('x-ssr-error-status');

if (ssrErrorStatus) {
const headers = new Headers(res.headers);

headers.delete('x-ssr-error-status');
headers.set('Cache-Control', 's-maxage=0, no-store, no-cache, must-revalidate, max-age=0');

return new Response(res.body, {
status: Number(ssrErrorStatus),
headers,
});
}
In my server entry handler -- so this seems like a lot for something like this lmk if you still need a repro, but it's genuinely just that top bit of throwing an error in the loader. The rest of the code is just my somewhat dumb way of handling it, so feel free to ignore
multiple-amethyst
multiple-amethyst2mo ago
so this is only about the SSR response then? from a superficial glance we should already set status 500 if any match is in status error can you please create a minimal reproducer that shows that status code is not 500 even when the loader throws? and then create a GitHub issue for this
like-gold
like-goldOP2mo ago
https://github.com/TanStack/router/issues/5535 Here ya go. Happy to look into this myself, but would appreciate if you can at least point me in the right direction. We're facing a bunch of problems due to this cause Cloudflare keeps caching all those pages for our users, so have no problem doing the digging myself, just wasn't sure if this was a bug or an expected behavior due to stuff like prerendering or whatever.
GitHub
SSR status code is always 200 even when error throws in loader · I...
Which project does this relate to? Start Describe the bug Added a very basic example, but essentially if the loader throws an Error, the response of the SSR is 200 regardless. Would be great to be ...
like-gold
like-goldOP2mo ago
this.hasNotFoundMatch = () => {
return this.__store.state.matches.some(
(d) => d.status === "notFound" || d.globalNotFound
);
};
this.hasErrorMatch = () => {
return this.__store.state.matches.some(
(d) => d.status === "error"
);
};
this.hasNotFoundMatch = () => {
return this.__store.state.matches.some(
(d) => d.status === "notFound" || d.globalNotFound
);
};
this.hasErrorMatch = () => {
return this.__store.state.matches.some(
(d) => d.status === "error"
);
};
--
if (this.hasNotFoundMatch()) {
this.__store.setState((s) => ({
...s,
statusCode: 404
}));
}
if (this.hasErrorMatch()) {
this.__store.setState((s) => ({
...s,
statusCode: 500
}));
}
if (this.hasNotFoundMatch()) {
this.__store.setState((s) => ({
...s,
statusCode: 404
}));
}
if (this.hasErrorMatch()) {
this.__store.setState((s) => ({
...s,
statusCode: 500
}));
}
So that hasNotFoundMatch already exists. I kind of duplicated the logic with a new hasErrorMatch and that did the trick from what I can see. this is in router-core/router.js -- i might be missing something but does seem to do what I want it to do? I think?

Did you find this page helpful?