Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Server Actions in Next.js #291

Open
Finkes opened this issue Jul 23, 2024 · 3 comments
Open

Support for Server Actions in Next.js #291

Finkes opened this issue Jul 23, 2024 · 3 comments

Comments

@Finkes
Copy link

Finkes commented Jul 23, 2024

I'm using the next-superjson-plugin to pass data from next.js server components to client components and this works like a charm.

Besides server components next.js also provides server actions, a new way of fetching data from the backend to to client by directly calling a backend function from the client side.

Server actions have the same serialization problem like passing data from server components to client components: behind the scenes data is serialized as JSON string on the server and then passed to the client. Therefore the following example doesn't work as expected, since the custom type is lost on the client side

"use server"

export async function serverAction(){
  return new Prisma.Decimal(10)
}


// client side
const response = await serverAction()
console.log(typeof response) // returns string not decimal/object!

As a workaround we can use SuperJSON.serialize() and SuperJSON.deserialize() like this:

"use server"

export async function serverAction(){
  return SuperJSON.serialize(new Prisma.Decimal(10))
}


// client side
const response = SuperJSON.deserialize(await serverAction())

However with this solution we are loosing the TypeScript type safety and we have to assign the return types manually.

Is there any way to integrate SuperJSON into next.js server actions? Maybe there is a way to provide a custom serializer to next.js?

@Finkes Finkes changed the title Support for Server Actions Support for Server Actions in Next.js Jul 23, 2024
@Skn0tt
Copy link
Collaborator

Skn0tt commented Jul 24, 2024

Interesting! I think ideally, next-superjson-plugin would be performing a compile-time transform to cover this as well. I'm not sure if that's possible though, are there any good ways of detecting a server action definition or call inside an AST?

For a more manual solution, do you think this TypeScript hack would work?

"use server"

function wrapWithSuperJSON<ServerAction extends () => Promise<any>>(serverAction: ServerAction): ServerAction {
  if (typeof window === 'undefined') return serverAction.then(SuperJSON.serialize)
  return serverAction.then(SuperJSON.deserialize)
}

export const serverAction = wrapWithSuperJSON(() => new Prisma.Decimal(10))


// client side
const response = await serverAction()

I haven't tried this out, so let me know if this doesn't work.

@Finkes
Copy link
Author

Finkes commented Jul 25, 2024

Thank you @Skn0tt for your quick support! I really appreciate that.
I agree, making next-superjson-plugin handle this automatically would be great.

are there any good ways of detecting a server action definition or call inside an AST?

I'm pretty sure there is a way, but unfortunately I don't have a deeper understanding on how things work behind the scenes, yet.

I tried your proposal, but it looks like the deserialization function isn't executed at all:

"user server"

function wrapWithSuperJSON<ServerAction extends () => Promise<any>>(
  serverAction: ServerAction,
): ServerAction {
  if (typeof window === "undefined")
    return serverAction().then(SuperJSON.serialize) as any as ServerAction;
  return serverAction().then(SuperJSON.deserialize) as any as ServerAction;
}

export const serverAction = async () =>
  wrapWithSuperJSON(async () => {
    return Promise.resolve(new Prisma.Decimal(10));
  });

// client side
const result = await serverAction();
console.log(result);

Output:
image

But thanks to your proposal I have found another workaround which involves wrappers on both sides:

// wrapper functions

/**
 * Wrap a next.js server action with SuperJSON (serialize)
 * @param serverAction
 */
export function serializeWithSuperJSON<ServerAction extends () => Promise<any>>(
  serverAction: ServerAction,
): ReturnType<ServerAction> {
  return serverAction().then(
    SuperJSON.serialize,
  ) as any as ReturnType<ServerAction>;
}

/**
 * Wrap a next.js server action with SuperJSON (deserialize)
 * @param serverAction
 */
export function deserializeWithSuperJSON<
  ServerAction extends () => Promise<any>,
>(serverAction: ServerAction) {
  return serverAction().then(SuperJSON.deserialize) as any as Promise<
    ReturnType<ServerAction>
  >;
}

// server side
"user server"

export async function serverAction() {
  return await serializeWithSuperJSON(async () => {
    return new Prisma.Decimal(10);
  });
}

// client side
const result = await deserializeWithSuperJSON(serverAction);
console.log(Prisma.Decimal.isDecimal(result));

Note that it looks like server actions require the function keyword as described by this error message.

@Skn0tt
Copy link
Collaborator

Skn0tt commented Jul 25, 2024

Good to hear you found a workaround! It's really hard to keep up with all the new shenanigans Next.js comes up with, and I have a feeling that supporting server actions in next-superjson-plugin will be close to impossible. Ideally, Next.js added something like tRPCs transformer option: https://trpc.io/docs/server/data-transformers#using-superjson

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants