T
TanStack4mo ago
rival-black

ensureQueryData on loader breaks page.

Getting this error on the client console: TRPCClientError: Unexpected token '<', "<!DOCTYPE html>" is not valid JSON The loader uses ensureQueryData to use it as the page title:
export const Route = createFileRoute('/_authed/_appLayout/posts/$postId')({
loader: async ({ params: { postId }, context }) => {
const post = await context.queryClient.ensureQueryData(
context.trpc.post.getPost.queryOptions({ id: postId })
);

if (!post) {
throw notFound();
}

return post;
},
head: ({ loaderData }) => ({
meta: [
{
title: loaderData.title ?? 'Untitled',
},
],
}),
export const Route = createFileRoute('/_authed/_appLayout/posts/$postId')({
loader: async ({ params: { postId }, context }) => {
const post = await context.queryClient.ensureQueryData(
context.trpc.post.getPost.queryOptions({ id: postId })
);

if (!post) {
throw notFound();
}

return post;
},
head: ({ loaderData }) => ({
meta: [
{
title: loaderData.title ?? 'Untitled',
},
],
}),
If I remove the ensureQueryData it works by fetching the same on the client:
function PostComponent() {
const params = Route.useParams();

const trpc = useTRPC();

const postQuery = useSuspenseQuery(
trpc.post.getPost.queryOptions({ id: params.postId })
);

return (
<>
<p>query data: {JSON.stringify(postQuery.data.id)}</p>
</>
);
}
function PostComponent() {
const params = Route.useParams();

const trpc = useTRPC();

const postQuery = useSuspenseQuery(
trpc.post.getPost.queryOptions({ id: params.postId })
);

return (
<>
<p>query data: {JSON.stringify(postQuery.data.id)}</p>
</>
);
}
No description
25 Replies
quickest-silver
quickest-silver4mo ago
can you please provide a minimal complete example repo?
rival-black
rival-blackOP4mo ago
GitHub
GitHub - rubenbase/tss-monorepo
Contribute to rubenbase/tss-monorepo development by creating an account on GitHub.
rival-black
rival-blackOP4mo ago
there you go, on local, works. on prod (cloudflare workers) any ensureQueryData will make the page crash: for example the index route of that repo:
loader: async ({ context: { trpc, queryClient } }) => {
await queryClient.ensureQueryData(trpc.posts.list.queryOptions());
return;
},
loader: async ({ context: { trpc, queryClient } }) => {
await queryClient.ensureQueryData(trpc.posts.list.queryOptions());
return;
},
quickest-silver
quickest-silver4mo ago
maybe TRPC is not initialized correctly in the loader? but later in the component it is
rival-black
rival-blackOP4mo ago
could be but isn't the initialization supposed to be in the createRouter and passing headers from the ssr.ts file?
quickest-silver
quickest-silver4mo ago
dont have experience with TRPC is TRPCProvider doing anything relevant here?
rival-black
rival-blackOP4mo ago
for this issue nothing relevant, only the one passed into the context
quickest-silver
quickest-silver4mo ago
so it looks like this returns HTML instead of JSON can you get the full HTML that is returned? or some server side logs
If I remove the ensureQueryData it works by fetching the same on the client:
during SSR, useSuspenseQuery should run on the server.
rival-black
rival-blackOP4mo ago
right, indeed this is a ssr issue with some config and not just ensureQueryData, removed it, added ssr and useSuspenseQuery and getting the same issue.
export const Route = createFileRoute("/_index")({
ssr: true,
errorComponent: ({ error }) => (
<ErrorDetails
title="Failed to load posts"
message={error.message}
error={error}
/>
),
// loader: async ({ context: { trpc, queryClient } }) => {
// await queryClient.ensureQueryData(trpc.posts.list.queryOptions());
// return;
// },
pendingComponent: Spinner,
component: IndexLayout,
});

function IndexLayout() {
const trpc = useTRPC();
const postsQuery = useSuspenseQuery(trpc.posts.list.queryOptions());

const posts = postsQuery.data

return <>
{posts.map((post) => {
return <div key={post.id}>{post.title}</div>
})}
</>
export const Route = createFileRoute("/_index")({
ssr: true,
errorComponent: ({ error }) => (
<ErrorDetails
title="Failed to load posts"
message={error.message}
error={error}
/>
),
// loader: async ({ context: { trpc, queryClient } }) => {
// await queryClient.ensureQueryData(trpc.posts.list.queryOptions());
// return;
// },
pendingComponent: Spinner,
component: IndexLayout,
});

function IndexLayout() {
const trpc = useTRPC();
const postsQuery = useSuspenseQuery(trpc.posts.list.queryOptions());

const posts = postsQuery.data

return <>
{posts.map((post) => {
return <div key={post.id}>{post.title}</div>
})}
</>
server logs only return the same error the UI returns, so guessing it's a 404 not found html which makes me thing of the baseUrl but that's properly configured, even hardcoded it and deployed to test
quickest-silver
quickest-silver4mo ago
cant you find out the error code by adding some logs?
rival-black
rival-blackOP4mo ago
i added sentry everywhere, ssr, client, on the error boundary, but doesn't catch this error any better than cloudflare logs
rival-black
rival-blackOP4mo ago
No description
rival-black
rival-blackOP4mo ago
might be an issue with one of the trpc packages because the endpoint https://hub.gobrand.app/api/trpc/posts.list works when going manually idk why calling it from ssr doesn't
rival-black
rival-blackOP4mo ago
Hey Manuel, this issue happens when deploying to CF only, works well on Vercel. Latest updates on the new repo: https://github.com/rubenbase/tss-monorepo
GitHub
GitHub - rubenbase/tss-monorepo
Contribute to rubenbase/tss-monorepo development by creating an account on GitHub.
rival-black
rival-blackOP4mo ago
getting a different error now though: Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'none'". Either the 'unsafe-inline' keyword, a hash ('sha256-Khza+XKCdhuiIMn7MrrkyDOW24+60Bz3MFWnKFjp4Qk='), or a nonce ('nonce-...') is required to enable inline execution.
quickest-silver
quickest-silver4mo ago
yeah we dont have nonce support yet. how did you set the CSP?
rival-black
rival-blackOP4mo ago
CF is setting it, I have default CF account, nothing changed, and default TSS app
quickest-silver
quickest-silver4mo ago
can you disable that?
rival-black
rival-blackOP4mo ago
researching how to, first time using cloudflare yeah no idea how to disable, I don't even have the Page Shield they mention on their docs that in theory injects those
rival-black
rival-blackOP4mo ago
opened an issue on nitro side https://github.com/nitrojs/nitro/issues/3356
GitHub
cloudflare-module ssr with trpc query + tanstack start not workin...
Environment app.config.ts import { defineConfig } from &#39;@tanstack/start/config&#39;; import tsConfigPaths from &#39;vite-tsconfig-paths&#39;; import { cloudflare } from &#39;unenv&#39;; import ...
quickest-silver
quickest-silver4mo ago
i am not sure this is a nitro issue
rival-black
rival-blackOP4mo ago
the csp issue went away when I get the headers on the ssr.ts file and pass them to router.ts instead of within router.ts use createServerFn to get the headers there with createIsomorphicFn so basically changing:
const getRequestHeaders = createServerFn({ method: 'GET' }).handler(
async () => {
const request = getWebRequest()!;
const headers = new Headers(request.headers);
return Object.fromEntries(headers);
}
);

export function createRouter(ssrHeaders: Record<string, string>) {
const headers = createIsomorphicFn()
.client(() => ({}))
.server(() => getRequestHeaders());

const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
headers,
}),
],
});
const getRequestHeaders = createServerFn({ method: 'GET' }).handler(
async () => {
const request = getWebRequest()!;
const headers = new Headers(request.headers);
return Object.fromEntries(headers);
}
);

export function createRouter(ssrHeaders: Record<string, string>) {
const headers = createIsomorphicFn()
.client(() => ({}))
.server(() => getRequestHeaders());

const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
headers,
}),
],
});
to this:
router.tsx

export function createRouter(ssrHeaders: Record<string, string>) {
const headers = createIsomorphicFn()
.client(() => ({}))
.server(() => ssrHeaders);

const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
headers,
}),
],
});

ssr.tsx

/// <reference types="vinxi/types/server" />
import { getRouterManifest } from '@tanstack/react-start/router-manifest';
import {
createStartHandler,
defaultStreamHandler,
getWebRequest,
} from '@tanstack/react-start/server';
import { createRouter } from './router';

export default createStartHandler({
createRouter: () => {
const request = getWebRequest()!;
const headers = new Headers(request.headers);
return createRouter(Object.fromEntries(headers));
},
getRouterManifest,
})(defaultStreamHandler);
router.tsx

export function createRouter(ssrHeaders: Record<string, string>) {
const headers = createIsomorphicFn()
.client(() => ({}))
.server(() => ssrHeaders);

const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
transformer: superjson,
url: getUrl(),
headers,
}),
],
});

ssr.tsx

/// <reference types="vinxi/types/server" />
import { getRouterManifest } from '@tanstack/react-start/router-manifest';
import {
createStartHandler,
defaultStreamHandler,
getWebRequest,
} from '@tanstack/react-start/server';
import { createRouter } from './router';

export default createStartHandler({
createRouter: () => {
const request = getWebRequest()!;
const headers = new Headers(request.headers);
return createRouter(Object.fromEntries(headers));
},
getRouterManifest,
})(defaultStreamHandler);
No description
rival-black
rival-blackOP4mo ago
Now getting stream closed, Nitro team said might be tanstack start, I'm not sure about that, it does work on Vercel so seems to me CF/Nitro related Yeah I think it's a me problem and how I'm configuring trpc. If I call it beforeLoad it works, if I call it on the loader breaks, I might need to create clients on each ssr and client files and pass to router.tsx
ratty-blush
ratty-blush4mo ago
Reginaldo (@RegiByte) on X
Important note for @tan_stack start users: please please make sure your server query client is created with the router If it's declared globally you will have issues with the server sharing state between requests which is very bad @tannerlinsley can we add this to the docs?
From An unknown user
X
ratty-blush
ratty-blush4mo ago
Create query client on the router

Did you find this page helpful?