Reputation: 775
I've been looking all over for an answer to this but can't find anything.
With NextJs 13 you can use server components. You can also create route.ts files in your app directory to make api end points.
What's the difference (or benefit) of creating a route.ts instead of just calling the same fetch directly within your server component? They're both on the server so it's not like the routes are only on the server but the fetch in the component is on the client. I can't tell if i should be setting up a route.ts for all of my api calls or just calling directly in the RSC components?
Thanks
Upvotes: 5
Views: 3857
Reputation: 1478
As per Vercel's Youtube video 10 common mistakes with the Next.js App Router , calling route handler in your React components is not the best way to fetch data. One of the downsides of doing this is it adds extra network calls.
export default async function Order() {
const data = await fetch('http://localhost:3000/api/orders'); // <-- You also need to specify the full address
const orders = await data.json();
return <h1>{JSON.stringify(orders)}</h1>;
}
Instead, it is recommended to fetch the data directly from the data source.
export default async function Order() {
const orders = await db.select().from(orderTable);
return <h1>{orders}</h1>;
}
Route handler is still useful if you need to access your data from an external app (e.g. mobile app) or if you need to provide a webhook to third-party services (e.g. Stripe)
If you need to use the data in multiple places (e.g. view, route handler, server actions), you can keep the data fetching logic inside a data access layer
or services
layer, and have the consumers import the same function. This is also a practice recommended by Vercel.
lib/services/orders.ts
export async function getOrders() {
const orders = await db.select().from(orderTable);
return orders ;
}
app/orders/page.tsx
import { getOrders } from 'lib/services/orders'
export default async function Order() {
const orders = getOrders();
return <h1>{orders}</h1>;
}
app/api/orders/route.ts
import { getOrders } from 'lib/services/orders'
export async function GET() {
const orders = getOrders();
return Response.json(order);
}
Upvotes: 6
Reputation: 2853
One of the big advantages of server actions is that you do not directly call any business logic inside your client components, this is especially useful if you use server-only sessions with something like iron-session. While creating route handlers is still a completely valid approach it does increase the amount of boilerplate code written substantially.
interface Props {
params: { id: string },
}
export default async function ListPage({ params }: Props) {
const { id } = params;
const endReached = async (start: number): Promise<User[]> => {
"use server";
const headers = await getAuthHeaders();
const response = await fetch(`https://api.io/users/${id}?start=${start}`, {
headers,
});
return await response.json();
};
const initialData = await endReached(0);
return <MyClientList initialData={initialData} endReached={endReached} />;
}
I the above example, from the perspective of your client component there is no business logic that is directly called inside of it. This not only ensures simplicity and maintainability but als reusability for your components.
"use client";
interface Props {
endReached: () => Promise<User[]>;
initialData: User[];
}
export default function MyClientList(props: Props) {
const { initialData, endReached } = props;
const [users, setUsers] = useState(initialData);
const onClick = useCallback(() => {
const nextUsers = await endReached(users.length);
setUsers((prev) => [...prev, ...nextUsers]);
}, [users.length, endReached]);
return (
<div>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<button onClick={onClick}>Load more</button>
</div>
);
}
Not only does your client component start with initial data and does not need to show any loading state whatsoever, but you could switch out the callback with any other function that returns the right data and it would still work.
Upvotes: -1