-
Notifications
You must be signed in to change notification settings - Fork 29.3k
Open
Labels
Server ActionsRelated to Server Actions.Related to Server Actions.
Description
Link to the code that reproduces this issue
https://github.com/mcrovero/reproduce-nextjs-effect-server-action
To Reproduce
- Start the application in development (next dev)
- open the browser (http://localhost:3000)
- look at server logs
Current vs. Expected behavior
Current behavior
The compiler only accepts Server Actions that are declared with the async keyword directly.
Exports that return a Promise but are produced via higher-order functions (e.g. runEffectAction) or other async functions not directly exported fail with:
Server Actions must be async functions.
This makes it impossible to export Server Actions ergonomically from libraries such as Effect.
Expected behavior
Any exported binding that is an async function or otherwise returns a Promise should be accepted as a Server Action.
Exported functions should work as long as the final export conforms to the async function contract.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:49 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6000
Available memory (MB): 16384
Available CPU cores: 8
Binaries:
Node: 20.19.1
npm: 10.8.2
Yarn: N/A
pnpm: 10.12.4
Relevant Packages:
next: 15.5.1-canary.30 // Latest available version is detected (15.5.1-canary.30).
eslint-config-next: N/A
react: 19.1.1
react-dom: 19.1.1
typescript: 5.9.2
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Server Actions
Which stage(s) are affected? (Select all that apply)
next dev (local)
Additional context
Examples of possible usecases:
"use server";
async function getData(input: number) {
return { data: "done " + input };
}
// This is not a valid Server Action
export const doSomethingPromise = (input: number) => getData(input);
// Workaround
export const doSomethingPromise = async (input: number) => await getData(input);
// --- Effect uscases ---
import { Effect } from "effect";
// Simple execution not allowed as a Server Action
export const doSomethingNoWrapper = (input: number) =>
Effect.promise(() => getData(input)).pipe(Effect.runPromise);
// we can workaround it by adding the async keyword
export const doSomethingNoWrapper = async (input: number) =>
await Effect.promise(() => getData(input)).pipe(Effect.runPromise);
// Most of the time we would run effects using a wrapper function
// There is no developer friendly way to run the wrapper with the async workaround
function runEffectAction<I, O>(
effectFn: (args: I) => Effect.Effect<O, never, never>
) {
return async (args: I): Promise<O> => {
return await Effect.runPromise(effectFn(args));
};
}
export const doSomethingEffect = runEffectAction((input: number) =>
Effect.promise(() => getData(input))
);
// We'd need to write something like
export const doSomethingEffect = async (input: number) => await runEffectAction((input: number) =>
Effect.promise(() => getData(input))
)(input);
Metadata
Metadata
Assignees
Labels
Server ActionsRelated to Server Actions.Related to Server Actions.