router
router copied to clipboard
Tanstack-start on Cloudflare: ServerFn not working with API routes
Which project does this relate to?
Start
Describe the bug
This probably is an issue only related to Cloudflare Workers
- Create an API route
/api/test; - In page
/count, do the following:
- Create a route loader. In loader, call serverFn
const count = await getCountServerFn(). - In
getCountServerFn, simplyfetch(${BASE_URL}/api/test)
- In local development, everything is ok. Soft navigation to
/count, or do a hard refresh on/count, I can see the count number being rendered. - Build preview, everything is still ok.
- Deploy to Cloudflare Works as is.
- On production, no matter do a soft navigation to
/countor do a hard refresh, it will resulting an error.
Additional information(all in production):
For simplicity I used page /count for illustration. Below you can see the actual page is /user/deprecated
- Soft navigate to page:
- Hard refresh:
- API route itself seems fine. I can fetch this API point from frontend with no problem(fetch in useEffect).
Your Example Website or App
https://tanstack-start-on-workers-v0.tuxi.workers.dev/user/deprecated
Steps to Reproduce the Bug or Issue
As mentioned above
Expected behavior
I can call API routes just fine in serverFn on Cloudflare
Screenshots or Videos
As mentioned above
Platform
- OS: Mac OS Sequoia 15.4.1
- Browser: Chrome 137.0.7151.41
- Version: 1.116.1
Additional context
/routes/user/deprecated.tsx
import { createFileRoute } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { BASE_URL } from '@/utils/base-url';
const getCountServerFn = createServerFn({ method: 'GET' }).handler(async () => {
const res = await fetch(`${BASE_URL}/api/test`);
if (!res.ok) {
throw new Error(`api/test failed: ${res.status} ${res.statusText}`);
}
return res.json();
});
export const Route = createFileRoute('/user/deprecated')({
loader: async ({ context }) => {
const count = await getCountServerFn();
return { count };
},
component: RouteComponent,
errorComponent: () => <div>error</div>,
});
function RouteComponent() {
const { count } = Route.useLoaderData();
return (
<div>
<h2>
<pre>{JSON.stringify(count, null, 2)}</pre>
</h2>
</div>
);
}
/routes/api/test.ts
import { createAPIFileRoute } from '@tanstack/react-start/api';
import { Redis } from '@upstash/redis/cloudflare';
export const APIRoute = createAPIFileRoute('/api/test')({
GET: async ({ request, params, env }) => {
console.log('get test api', JSON.stringify(request));
const redis = Redis.fromEnv(
env ?? {
UPSTASH_REDIS_REST_URL: 'xxx',
UPSTASH_REDIS_REST_TOKEN: 'xxx',
}
);
const count = await redis.incr('counter');
return new Response(JSON.stringify({ count }));
},
});
This error on Cloudflare not for Start
Document: https://developers.cloudflare.com/workers/runtime-apis/fetch/
Error: Worker to Worker
This error on Cloudflare not for Start
Document: https://developers.cloudflare.com/workers/runtime-apis/fetch/
Error: Worker to Worker
Thanks for the quick response! But I'm not following, why is it a Worker to Worker issue? Start As a full stack framework, I thought I just have one Worker, which my Start project runs on. If it's a Worker to Worker issue, what is Worker A and what is Worker B?
cloudflare worker is implemented like you run javascript script once per request, they intercept all requests as callback to itself to avoid infinite loop, you can only do it when it is bound to a new worker (as per their docs that I can understand)
Worker A is your application (you can not call from A to A)
Worker B is a new copy of A, if you want to make an end point call to /api/test
For example:
function mainA() {
console.log(" this is worker A")
}
function mainB() {
console.log(" this is worker B")
}
//Error will occur
//if:
function mainA() {
console.log(" this is worker A")
mainA()
} //Infinite loop
//you can only do it by:
function mainB() {
console.log(" this is worker B")
mainA() //can only call A here
}
I'm running into this also now, how would one go about circumventing this issue @akawahuynh without deploying the same code again as that seems very counter intuitive?
I'm running into this also now, how would one go about circumventing this issue @akawahuynh without deploying the same code again as that seems very counter intuitive?
you can only call api on client, but to read data from database you can call right at createServerFn()
const getCountServerFn = createServerFn({ method: 'GET' }).handler(async () => {
const res = await db.query....
return res; });
What problem are you having, can you share it here?
I'm running into this also now, how would one go about circumventing this issue @akawahuynh without deploying the same code again as that seems very counter intuitive?
you can only call api on client, but to read data from database you can call right at createServerFn()
const getCountServerFn = createServerFn({ method: 'GET' }).handler(async () => { const res = await db.query.... return res; });What problem are you having, can you share it here?
Just a little heads up, one should not do mutation(post api, db update etc.) request in createServerFn. Cus loader may run multiple times.
@cherishh no. you can execute a server function in any place. does not have to be a loader. can be an onClick handler if you want
like @schiller-manuel said before, createServerFn() is like a built-in trpc in tanstack start so you can call it anywhere except API route and vice versa
@akawahuynh on alpha branch you can indeed call a server function from an API route
The issue I'm having is when I deploy to CF the api can't be called. It's a standard createserverfn not inside an api route. I get a "not found" when I try hitting the endpoint directly. However it does work in dev locally.
@karthikjn01 It's not a bug, it's a Cloudflare featureπ. If you run in a local environment, you're running in a node environment not CF. Have you tried building and running dev with wrangler dev? Most of the CF bug fixes are here. If there's an error, CF will definitely have the same error.
I've not yet had the chance no - having said that though - if that's the case, how is tanstack start marketed as being able to deploy on CF? I'll give it a go now, but I'm pretty set on deploying w Cloudflare, so it might just be a matter of using a different framework!
@schiller-manuel Thanks. I haven't tested with alpha yet. I'm waiting for it to be stable, and have documentation to read, it's too hard to fix bugs without understanding exactly what they are π
I've not yet had the chance no - having said that though - if that's the case, how is tanstack start marketed as being able to deploy on CF? I'll give it a go now, but I'm pretty set on deploying w Cloudflare, so it might just be a matter of using a different framework!
Yep, I still have some production apps using Start deploy to cloudflare, it's pretty stable so far, there are some bugs with the damn limit of cloudflare, if you need to render something for too long it will crash, it takes all day just to find out where the error is
For example: using better-auth, almost 503 continuously, if you want to release production saas app, you have to upgrade to paid π€¦ββοΈ
@cherishh no. you can execute a server function in any place. does not have to be a loader. can be an onClick handler if you want
Thanks for pointing that out! And yes, just as you said, I am indeed calling createServerFn inside the event handler now. Still, I kinda prefer calling a createServerFn inside a loader. I want the data to be ready, instead of showing a short loading state to users.
Btw is it NOT recommended to call an api route inside a createServerFn?
I've not yet had the chance no - having said that though - if that's the case, how is tanstack start marketed as being able to deploy on CF? I'll give it a go now, but I'm pretty set on deploying w Cloudflare, so it might just be a matter of using a different framework!
Yep, I still have some production apps using Start deploy to cloudflare, it's pretty stable so far, there are some bugs with the damn limit of cloudflare, if you need to render something for too long it will crash, it takes all day just to find out where the error is
For example: using better-auth, almost 503 continuously, if you want to release production saas app, you have to upgrade to paid π€¦ββοΈ
Could you elaborate a bit more on this issue? I'm also using Better Auth (free tier) and haven't encountered any problems so far. This 503 issue is due to CF overload, or better-auth? p.s. I'm really just testing to get everything running smoothly on CF at this stage. I don't have many users.
Could you elaborate a bit more on this issue? I'm also using Better Auth (free tier) and haven't encountered any problems so far. This 503 issue is due to CF overload, or better-auth?
CF workers have a limit of 10ms for free plans. For paid, it is 30s (+ up to 15min). Better Auth sometimes take compute ms of 2000ms and this is way over the 10ms limit forcing the request to fail returning 503 or the 1102 CF error.
Could you elaborate a bit more on this issue? I'm also using Better Auth (free tier) and haven't encountered any problems so far. This 503 issue is due to CF overload, or better-auth?
CF workers have a limit of 10ms for free plans. For paid, it is 30s (+ up to 15min). Better Auth sometimes take compute ms of 2000ms and this is way over the 10ms limit forcing the request to fail returning 503 or the 1102 CF error.
Thanks for the explanation! 10ms that's some real limitationπ Guess I'll upgrade to paid too.
I encountered a similar issue and it took a bit of time to track down, but the fix turned out to be straightforward.
Any imports from @tanstack/react-start/server or @tanstack/solid-start/server must be used within a createServerFn context. In my case, I had separated some of my data-fetching logic into standalone fetcher functions, which included calls to getCookie from the server package. These fetcher functions were then called from within createServerFn handlers defined elsewhere.
The problem was that getCookie (and similar server-bound utilities) cannot be used outside of the createServerFn scope. To resolve the issue, I simply moved the logic that depended on getCookie directly into the createServerFn itself, rather than calling it from a helper outside its scope.
Hope this helps anyone running into the same confusion!
@joeygrable94 that sounds a bit strange to me, and also maybe a separate issue. can you share a complete reproducer for this?
I was pretty clear in my response. You must us the server utilities inside of a serverFn. For example, CloudFlare bundler errors if I call the getCoookie method inside of an anonymous function in a component. You must place that getCookie method within a serverFn or the bundler gets mad. Unless, i'm missing something π€·
yes sure you must ultimately invoke the server utilities from within the server function. however it can be done in a helper method, if that helper is invoked by the server function. calling that helper from a component will end up this helper being called on the client.
@cherishh is there anything left to do here?