T
TanStack5mo ago
rival-black

How do I pass context to the router in TanStack Start?

Hey ya'll! Quick question about TanStack Start - Is it possible to pass context to the router? With TanStack Router, I can use RouterProvider to pass an AuthContext, for example, but in TanStack Start I have to use StartClient which doesn't give me a way to "provide" any context. It feels like I'm missing some key understanding about how the full stack version of TanStack differs from the client-first version...
12 Replies
rival-black
rival-blackOP5mo ago
secure-lavender
secure-lavender5mo ago
In createRouter you can add things to the router context example if you need to pass things from react-land eg a context to the router is not possible. Can you share more info about what you need to achieve?
GitHub
react-tanstarter/src/router.tsx at main · dotnize/react-tanstarter
minimal TanStack Start template with React 19, Better Auth, Drizzle ORM, shadcn/ui - dotnize/react-tanstarter
conventional-tan
conventional-tan5mo ago
Would’ve been nice to be able to inject async values in the context there. Ex: fetch user as early as possible in middleware, pass null or user into context
future-harlequin
future-harlequin5mo ago
can you give me some pseudocode what you think this could look like?
conventional-tan
conventional-tan5mo ago
@Manuel Schiller yeah for sure! and correct me if I am wrong! the context when you invoke createTanstackRouter seems like the perfect place to do dependency injection (in this case I passed queryClient, a random string value. This is nice as it gives you the typesafety in each routes loader and serverFns
import { QueryClient } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { routeTree } from "./routeTree.gen";
import { getCloudflareCtx } from "./utils/cloudflare";

const DefaultCatchBoundary = () => <div>DefaultCatchBoundary</div>;
const DefaultNotFound = () => <div>DefaultNotFound</div>;

export const testFn = () => "testVal";

export const getUser = async () => {
//Potentially calling db?
await new Promise((res) => setTimeout(res, 200));
return { user: "randomemail@gmail.com" };
};

export function createRouter() {
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1 * 1000,
},
},
});

return routerWithQueryClient(
createTanStackRouter({
routeTree,
defaultPreload: "intent",
context: { queryClient, testVal: testFn(), user: getUser() },
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: DefaultNotFound,
}),
queryClient,
);
}

declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
import { QueryClient } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routerWithQueryClient } from "@tanstack/react-router-with-query";
import { routeTree } from "./routeTree.gen";
import { getCloudflareCtx } from "./utils/cloudflare";

const DefaultCatchBoundary = () => <div>DefaultCatchBoundary</div>;
const DefaultNotFound = () => <div>DefaultNotFound</div>;

export const testFn = () => "testVal";

export const getUser = async () => {
//Potentially calling db?
await new Promise((res) => setTimeout(res, 200));
return { user: "randomemail@gmail.com" };
};

export function createRouter() {
const queryClient: QueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1 * 1000,
},
},
});

return routerWithQueryClient(
createTanStackRouter({
routeTree,
defaultPreload: "intent",
context: { queryClient, testVal: testFn(), user: getUser() },
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: DefaultNotFound,
}),
queryClient,
);
}

declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
then with createRootRoute you can do with context
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
testVal: string;
user: Promise<{
user: string;
}>;
}>()({
head: () => ({
meta: [
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
testVal: string;
user: Promise<{
user: string;
}>;
}>()({
head: () => ({
meta: [
and then you get the typesafety in loader/serverFn etc
export const Route = createFileRoute("/")({
component: Home,
loader: async ({ context, params }) => {
console.log(context.testVal);
console.log(context.user);
},
});
export const Route = createFileRoute("/")({
component: Home,
loader: async ({ context, params }) => {
console.log(context.testVal);
console.log(context.user);
},
});
I guess the question is wanting to inject the context as early on as possible (through global middleware maybe?) before you hit the route, and run a dbCheck for the user for example and passing that in context, so now the route can access it via loaders,serverFns etc
conventional-tan
conventional-tan5mo ago
No description
conventional-tan
conventional-tan5mo ago
No description
conventional-tan
conventional-tan5mo ago
whilst this works running it on node locally it will throw a top level await error on cloudflare. Hope that makes sense, I'll be thinkering around with it more 👍 Looks like passing it in the rotuers context might not be a good idea anyways since it will run on the client, so no bueno
future-harlequin
future-harlequin5mo ago
you would need to run a different impl on the client but where would it get the value from? probably needs to be dehydrated on the server and hydrated on the client
conventional-tan
conventional-tan5mo ago
yeah I'll play around with it @Manuel Schiller 👍 just to be clear does beforeLoad and the loader function run isomorphic?
future-harlequin
future-harlequin5mo ago
yes
conventional-tan
conventional-tan5mo ago
in rr7 the loader is server only, and I think they havea new client loader gotcha, okay that clarifies a lot ❤️

Did you find this page helpful?