Client Side data fetching
Unanswered
Korat posted this in #help-forum
KoratOP
My next app router application always relies on an external server made with another language (for every data fetch or mutate I communicate with that server).
While in server components I fetch data directly which is great as it enables nextjs caching mechanisms, I have some mixed thoughts when it comes to client-side fetching.
In client components I use React Query to handle mutations/queries which is also nice.
Thing is that I save my Bearer token and my refresh token in a server side cookie to protect it.
This means that I want to hide this information from client network (also hide the api endpoint too).
This brings me to a solutions which is using next server as proxy for every request going to that external server.
For server components Im safe and good, but in client, what I'm doing is passing server actions (functions marked with 'use server') to useQuery queryFn and useMutation as well.
While useMutations looks nice to accept server actions, I'm finding it hard to accept that I'm passing server actions to useQuery too because server actions are meant to be used for post requests,
This seems a bit off and I wanted to take your opinion if this is a nice thing to do, or if any of you is doing the same thing.
While in server components I fetch data directly which is great as it enables nextjs caching mechanisms, I have some mixed thoughts when it comes to client-side fetching.
In client components I use React Query to handle mutations/queries which is also nice.
Thing is that I save my Bearer token and my refresh token in a server side cookie to protect it.
This means that I want to hide this information from client network (also hide the api endpoint too).
This brings me to a solutions which is using next server as proxy for every request going to that external server.
For server components Im safe and good, but in client, what I'm doing is passing server actions (functions marked with 'use server') to useQuery queryFn and useMutation as well.
While useMutations looks nice to accept server actions, I'm finding it hard to accept that I'm passing server actions to useQuery too because server actions are meant to be used for post requests,
This seems a bit off and I wanted to take your opinion if this is a nice thing to do, or if any of you is doing the same thing.
76 Replies
interesting
afaik ive see nserver actiosn also used in usequery
honestly as long as it works
and u know what ure doing
and how things work
i think ure good!
but its noth cmopletely new from my experience
KoratOP
@Asian black bear Yeah, it works great to be honest, its just the web ethics 😆 , using post request to make a get request isn't right but I don't have any other option, i just wanted to see y'all opinions.
Also in second thoughts, this might be a good thing too because you can't do nextjs caching in client components anyways, and even better you are using react query's cache mechanisms even though you are passing a server action into it, so as long as that server action returns data, that data is going to be saved under a key in RQ and we are good to go.
By doing this, I can hide all my request endpoints and headers too and I think it's a win win
Also in second thoughts, this might be a good thing too because you can't do nextjs caching in client components anyways, and even better you are using react query's cache mechanisms even though you are passing a server action into it, so as long as that server action returns data, that data is going to be saved under a key in RQ and we are good to go.
By doing this, I can hide all my request endpoints and headers too and I think it's a win win
10x devs use POST requests to get data someone used to say 

@joulev No, don’t use server actions for data fetching. They cannot be run in parallel.
Just use ordinary route handlers.
Asian black bear
i thought he was mutating someth
durnig fetch
KoratOP
Hmmm, route handlers sounds nice but it feels like im doing the same thing as my original backend is doing ? recreating apis that are already created in backend sounds double work doesnt it?
@joulev No, don’t use server actions for data fetching. They cannot be run in parallel.
Just use ordinary route handlers.
KoratOP
can you give an example because I dont get the part where they dont run in parallel?
Also, I can’t use route handlers with react query ?
only if i turn my server action into a normal function that calls that route which then calls my original dotnet api, which is hella work to do😂
only if i turn my server action into a normal function that calls that route which then calls my original dotnet api, which is hella work to do😂
@Korat can you give an example because I dont get the part where they dont run in parallel?
Actions are queued. The next action only runs when the previous action has resolved.
So if you have two components fetching data, one of them will wait for the other before the data fetching even takes place.
So if you have two components fetching data, one of them will wait for the other before the data fetching even takes place.
@Korat Hmmm, route handlers sounds nice but it feels like im doing the same thing as my original backend is doing ? recreating apis that are already created in backend sounds double work doesnt it?
Exactly. Evaluate closely whether you need a proxy. If you need a proxy, evaluate if you need route handlers or just need to rewrite requests (see the documentation for middleware rewrites and next config rewrites).
Just don’t use server actions to fetch data.
Just don’t use server actions to fetch data.
Asian black bear
RQ can run in parallel
i love it so much man
@Asian black bear RQ can run in parallel
KoratOP
Does that mean its safe to use server actions in queryFn ? knowing the downside of server actions not being able to run in parallel ?
@joulev You can use route handlers with react query
KoratOP
by calling a simple function that makes a get request to that route handler right ?
@Asian black bear https://tigerabrodi.blog/nuances-of-server-actions-in-nextjs
KoratOP
Thanks a lot, it's great read
u can get things to work
but its not ideal
i think API routes
with RQ
seems like a better approach
i agree with @joulev
KoratOP
Yeah, thats good but as I said I need to do double work since endpoints already exists in a dotnet server, or maybe im missing something here
So instead of calling the dotnet endpoint directly in server actions, I will have to call a route handler which I have to create on my own
so -> client component -> queryFn -> normal function that uses fetch to call this internal route handler -> call our dotnet and receives data
so -> client component -> queryFn -> normal function that uses fetch to call this internal route handler -> call our dotnet and receives data
is that what you guys mean ?
Yes
KoratOP
Alright, thanks for the clarifications but this seems like a lot of work from developer's perspective sadly
@joulev Exactly. Evaluate closely whether you *need* a proxy. If you need a proxy, evaluate if you need route handlers or just need to rewrite requests (see the documentation for middleware rewrites and next config rewrites).
Just don’t use server actions to fetch data.
Yes that’s why I’m saying this. If you don’t have extra logic needed, I’d just rewrite the request or even call the backend directly
KoratOP
one more question that pops up, if I use route handlers for this case, doesn't that mean Im caching data both in react query's cache and in nextjs's cache ?
You can, though I wouldn’t. I would just add export const revalidate = 0 to the route handlers to disable all caching there, then use react-query’s cache everywhere. Using both cache can become unnecessarily confusing.
KoratOP
Okay, thanks a lot guys 🙌
I tried the rewrite methodology, ended up with this config
This does hide the api url but doesn't run in server anymore so I will have to rethink how i save my access_token and refresh_token
I tried the rewrite methodology, ended up with this config
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://url.net/api/:path*',
},
];
},This does hide the api url but doesn't run in server anymore so I will have to rethink how i save my access_token and refresh_token
@Korat Okay, thanks a lot guys 🙌
I tried the rewrite methodology, ended up with this config
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://url.net/api/:path*',
},
];
},
This does hide the api url but doesn't run in server anymore so I will have to rethink how i save my access_token and refresh_token
destination: https://url.net/api/path?key=process.env.API_KEY for example
Or https://url.net/API_KEY/… or anything, as long as you can send it to the backend server
KoratOP
When user logs in, i save the tokens in a secure server cookie
I don't think i can access cookies() with this approach
Oh, in cookies? Then forget about all those process.env nonsense, it should automagically work out of the box
Browsers will send the cookies, the rewrite will forward the cookies to the backend
KoratOP
Yeah but just one thing here you don't know, our backend accepts the token as Bearer under Authorization header so yeah, gotta discuss with them on this
Then you can use middleware, which allows custom logic for these rewrites
KoratOP
hmm, any source out there I can check out ?
if (pathname starts with /api) then
token = get from request.cookies
return NextResponse.rewrite(…, {
headers: {
authorization: token
}
})
token = get from request.cookies
return NextResponse.rewrite(…, {
headers: {
authorization: token
}
})
KoratOP
Yeah I have to set the headers from response
oh, great, im gonna try that out, does that eliminates rewrites from config file /
?
Yes
KoratOP
also I need to exclude the api from matcher object
KoratOP
the middleware thing you suggested works by the way
KoratOP
@joulev I tried this and the data seems to be available all at once and its fast too except that I see 3 POST requests in my terminal logs,
Unless I'm not understanding the under the hoods or I'm missing some failing cases here, I think this approach works.
Sorry for interrupting, I just wanted to have a disscussion for this with nextjs community.
Unless I'm not understanding the under the hoods or I'm missing some failing cases here, I think this approach works.
Sorry for interrupting, I just wanted to have a disscussion for this with nextjs community.
@Korat <@484037068239142956> I tried this and the data seems to be available all at once and its fast too except that I see 3 POST requests in my terminal logs,
Unless I'm not understanding the under the hoods or I'm missing some failing cases here, I think this approach works.
Sorry for interrupting, I just wanted to have a disscussion for this with nextjs community.
that's probably because your action runs really fast. try adding a delay in your actions, you will notice the sequential nature of actions immediately
"use server";
export async function get1() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return 1;
}
export async function get2() {
await new Promise((resolve) => setTimeout(resolve, 1000));
return 2;
}"use client";
import { useQuery } from "@tanstack/react-query";
import { get1, get2 } from "~/lib/actions";
export default function Page() {
const query1 = useQuery({ queryKey: ["1"], queryFn: () => get1() });
const query2 = useQuery({ queryKey: ["2"], queryFn: () => get2() });
return (
<div>
<div>Get 1: {query1.data ?? "loading"}</div>
<div>Get 2: {query2.data ?? "loading"}</div>
</div>
);
}actions running fast means your backend is fast, which is a really good thing. but as demonstrated here, you will get even faster performance if you used route handlers/rewrites instead, because in that case you are no longer subject to artificial queues by the client-side router
KoratOP
Oh, I didn’t think of testing with a delay, thats a great thing to learn. Thanks a lot man 🙌.
By the way, in the other hand I would like to make it easier for the developer to handle route handlers and server actions,
Tkdodo suggested me to use trpc, is that a good tool to eliminate the use of route handlers and server actions for my use case with external backend?
By the way, in the other hand I would like to make it easier for the developer to handle route handlers and server actions,
Tkdodo suggested me to use trpc, is that a good tool to eliminate the use of route handlers and server actions for my use case with external backend?
@Korat Oh, I didn’t think of testing with a delay, thats a great thing to learn. Thanks a lot man 🙌.
By the way, in the other hand I would like to make it easier for the developer to handle route handlers and server actions,
Tkdodo suggested me to use trpc, is that a good tool to eliminate the use of route handlers and server actions for my use case with external backend?
trpc is essentially a tool to make your route handler nicer. so instead of making 100 route handlers, you can make 100 js functions instead and route them all into the one single trpc route handler. yes i think it will be very useful in your use case too
KoratOP
Yeah thats what I thought too, played with it last night and It looks promising 🔥
I love how it batches queries like the example above into one request
I love how it batches queries like the example above into one request
Yep. In the end what did you choose to go with? Personally I’d use the middleware method
KoratOP
@joulev Actually, i continued experimenting with trpc and see how it goes alongside react query
@joulev Yes that’s why I’m saying this. If you don’t have extra logic needed, I’d just rewrite the request or even call the backend directly
KoratOP
@joulev Sorry for bringing this topic up once more, but I find it very helpful everytime I read the comments.
I see how TRPC helps me omit creating route handlers and instead I can create functions, but this still feels overwhelming from a developers perspective.
Let's say I end up calling backend directly.
In server components, I can directly call that endpoint and pass the Bearer token which i take from cookies() which is great.
In client components that is not the case, I can either ask my backend developers to change the logic of how they receive the access_token and tell them to take it from cookies instead and all I have to do in my side is pass credentials: true.
Correct me if I'm wrong.
Or I can continue to use nextjs as a proxy and instead of create all route handlers, I can go with the rewrite methodology you have shown me.
I see how TRPC helps me omit creating route handlers and instead I can create functions, but this still feels overwhelming from a developers perspective.
Let's say I end up calling backend directly.
In server components, I can directly call that endpoint and pass the Bearer token which i take from cookies() which is great.
In client components that is not the case, I can either ask my backend developers to change the logic of how they receive the access_token and tell them to take it from cookies instead and all I have to do in my side is pass credentials: true.
Correct me if I'm wrong.
Or I can continue to use nextjs as a proxy and instead of create all route handlers, I can go with the rewrite methodology you have shown me.
KoratOP
@joulev I was just trying to get an approvance that Im understanding this correctly,
also i've seen people do this, creating dynamic route.ts to rewrite requests like soo
I think i might go with this after all
also i've seen people do this, creating dynamic route.ts to rewrite requests like soo
I think i might go with this after all
too many options man, im overwhelmed
KoratOP
Thanks bro
