S
SolidJS•4d ago
nerotide

Redirect does not happen during load if there is an awaited call before it.

I have this piece of code in Solid Start in my route:
const getProject = cache(async function ({ id }: { id: string }) {
"use server";
await new Promise((resolve) => setTimeout(resolve, 3000));
throw redirect("/");
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};
const getProject = cache(async function ({ id }: { id: string }) {
"use server";
await new Promise((resolve) => setTimeout(resolve, 3000));
throw redirect("/");
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};
My original intention was to redirect a user if a record is not found. Or perhaps showing a specific 404 page. However, I stumbled upon this behavior that I don't understand. Is it a bug? If I take a look at what is being returned from the server, it still returns the page and the redirect happens on the client after the resource is loaded. However, I can't figure out how to force the redirect to always happen on the server. I noticed that it is possible only if there are no await calls before the throw of the redirect. So when you adjust the code like this:
const getProject = cache(async function ({ id }: { id: string }) {
"use server";
throw redirect("/"); // <-- moved this above the awaited promise
await new Promise((resolve) => setTimeout(resolve, 3000));
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};
const getProject = cache(async function ({ id }: { id: string }) {
"use server";
throw redirect("/"); // <-- moved this above the awaited promise
await new Promise((resolve) => setTimeout(resolve, 3000));
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};
It redirects. But that is not what I want. I need to make an API call before that. Am I just doing the whole thing wrong? I've been struggling with this for hours. I would appreciate any help. Also, the cache part is little documented. I would like to avoid leaking private resources behind protected API endpoints. Am I supposed to pass the user's unique API key as a function parameter to the cached function so if I had 2 users call the cached function with the same parameters, like a slug for example, so they wouldn't accidentally get someone else's cached result? Also is there a way to not use the "cache" and redirect on the server? I feel like I will certainly mess something up with it 😄 Thank you in advance for your answers. 💙
4 Replies
nerotide
nerotide•4d ago
Ahh sorry, forgot to mention... These are the versions of solid related packages that I am using. (currently the latest)
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.6",
"@solidjs/start": "^1.0.2",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.6",
"@solidjs/start": "^1.0.2",
Madaxen86
Madaxen86•4d ago
Instead of redirecting, you could just throw an error. And add an ErrorBoundary to your component/page. The fallback of the ErrorBoundary can be component showing a 404.
nerotide
nerotide•4d ago
Umm I tried it like this but it seems like it is not working. It just logs the error into the console and the error boundary does not even get hit. I tried to wrap the entire app into an error boundary (in app.tsx) and it still had no effect.
import { Title } from "@solidjs/meta";
import { cache, RouteSectionProps } from "@solidjs/router";
import { ErrorBoundary } from "solid-js";

const getProject = cache(async function ({ id }: { id: string }) {
"use server";
throw new Error("Error");
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};

export default function Editor(props: RouteSectionProps) {
return (
<main>
<Title>Editor</Title>
<ErrorBoundary fallback={() => <div>Error</div>}>
Editor page
</ErrorBoundary>
</main>
);
}
import { Title } from "@solidjs/meta";
import { cache, RouteSectionProps } from "@solidjs/router";
import { ErrorBoundary } from "solid-js";

const getProject = cache(async function ({ id }: { id: string }) {
"use server";
throw new Error("Error");
}, "project");

export const route = {
load: (ctx: any) => getProject({ id: ctx.params?.id }),
};

export default function Editor(props: RouteSectionProps) {
return (
<main>
<Title>Editor</Title>
<ErrorBoundary fallback={() => <div>Error</div>}>
Editor page
</ErrorBoundary>
</main>
);
}
peerreynders
peerreynders•4d ago
If I take a look at what is being returned from the server, it still returns the page and the redirect happens on the client after the resource is loaded.
Redirects always happen on the client even with MPAs. Given that Start is an SPA and cache() points are managed by actions, conceptually the redirect response is held by the cache() point and happens the moment something tries to consume it.
how to force the redirect to always happen on the server.
It happens on the server if the redirect happens during SSR, i.e. during initial load. Past that redirects always happen on the client.
I noticed that it is possible only if there are no await calls before the throw of the redirect.
You are not consuming the cache() point through a createAsync(). Start streams responses during SSR by default. If you consume the cache() point via createAsync(…,{ deferStream: true }) the page won't start streaming until the cache() point has settled.