openapi-ts
openapi-ts copied to clipboard
NextJS compatibility
Description
The library looks great - personally I am finding it difficult to get it working cleanly within a nextJS app that wants to use the generated API on the server side of NextJS (ie "use server";). If I am passing functions around into client components to be called, NextJS mandates that they be async and also have "use server"; either in the function itself or in the file etc...
Is there any consideration for NextJS compatibility for the services.gen.ts file or an option? Right now I will probably have to write a post-script that removes all export class (since NextJS does not allow this either) and transforms them into a list of exported functions e.g async ${serviceName}${operationName}(..... for example. And also put a "use server"; at the top of the file.
If this sounds reasonable, I could try and get around to a PR?
Yes there is, do you have Discord? Would be easier to discuss how this could work
Too late for me now unfortunately - we can hash it out on Discord perhaps later this week or the weekend.
Did you have a general plan in mind for it?
I didn't mean now! The only pointer I have for you now is https://github.com/hey-api/openapi-ts/issues/308
Related thread is https://github.com/hey-api/openapi-ts/issues/342, both features must be satisfied to consider this a good solution
@samvaughton could you not pass "SomeService.someFunc" as that would be an asynchronous function. They are static on the class. If that would work, then the solution may be to simply add "use server" to the top of the file for nextjs
I have tried a fair few configurations and NextJS is very picky about this...
When doing something like this enquiryAction={EnquiriesService.createEnquiry}
Normal services.gen.ts without modification results in:
Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
<... formMode="CREATE" clientData=... enquiryAction={function}>
^^^^^^^^^^
So I add "use server"; to the file and it points to the export class EnquiriesService as the culprit:
× Only async functions are allowed to be exported in a "use server" file.
│ ╭─▶ export class EnquiriesService {
12 │ │ /**
13 │ │ * Adds a new enquiry
14 │ │ * Adds a new enquiry
15 │ │ * @returns EnquiryResponse Successfully added a new enquiry.
16 │ │ * @throws ApiError
17 │ │ */
18 │ │ public static createEnquiry(
19 │ │ data: $OpenApiTs["/v1/enquiries"]["post"]["req"],
The only way to fix it is to remove classes and export async function .... for all of them 🤷
I didn't mean now!
@mrlubos Woops, my bad :-)
@samvaughton what if you put "use server" at the top of the static function?
For what it's worth, I don't think it's reasonable to require "use server" declaration, even if it worked. @samvaughton I'm considering exporting all operations as standalone functions anyway so I'd be in favour of that + export services as objects instead of classes
@jordanshatford tried it and unfortunately it does not work. Since passing a server function to a client component is a very specific thing, NextJS requires it to be async and have "use server"; in that file. And the moment you use "use server"; it requires every exported function in that file to be async too :( . It doesn't play nicely with static methods inside a class.
Extracting a single function out, a working file looks something like this:
"use server";
// This file is auto-generated by @hey-api/openapi-tsimport type { CancelablePromise } from "./core/CancelablePromise";
// removed imports for brevity
export async function createEnquiry(
data: $OpenApiTs["/v1/enquiries"]["post"]["req"],
): CancelablePromise<
ApiResult<$OpenApiTs["/v1/enquiries"]["post"]["res"][201]>
> {
// "use server"; does not work here....
const { requestBody } = data;
return __request(OpenAPI, {
// redacted for brevity
});
}
I think we should switch to async either way, then unlocking this functionality would be trivial
Hi! Is there any progress on NextJS support?
@abramchikd Not yet, can you list out what's required with the latest version using the new Fetch API client?
Oh, actually I tried it with the new Fetch API client. And it seems to work great. Basically I just needed to add 'use server' in the beginning of services.gen.ts.
Thank you and sorry for disturbing.
I will post an update if I find that something is missing
@abramchikd No worries. How do you add the declaration? Also, the services are not using async keyword and that was previously mentioned as a requirement – what changed?
@mrlubos Almost everything works fine with server actions.
I only had to make two changed:
- Add
'use server'inservices.gen.tsso that the file looks like this:
'use server'
// This file is auto-generated by @hey-api/openapi-ts
import {...}
import {...}
export const getSomeRequest = (options?: Options) => { return (options?.client ?? client).get<GetSomeRequestResponse, GetSomeRequestError>({
...options,
url: '/some/request'
}); };
...
It can be automated by running sed -i "1s/^/'use server'\n/" src/apiClient/services.gen.ts after openapi-ts.
- Write a wrapper for request functions, which removes
requestandresponsefields from theRequestResult, because class objects cannot be passed from Server components to Client components
'use server';
import { Options, RequestResult } from '@hey-api/client-fetch';
import { apiClient } from '@/apiClient/apiClient';
export async function apiRequest<Payload = unknown, Response, Error>(
request: (options: Options<Payload>) => RequestResult<Response, Error>,
options?: Options<Payload>,
): Promise<Response> {
const {data, error} = await request({...options, client: apiClient});
if (error) {
throw new Error(JSON.stringify(error));
}
return data!;
}
So my request invocation looks like this:
const header = await apiRequest(getHeader, { path: { headerId } })
Interesting, thank you! If you'd be able to spin up a StackBlitz example at some point, that would be very appreciated
Moving this issue to https://github.com/hey-api/openapi-ts/issues/1515!