vercel / next.js

The React Framework
https://nextjs.org
MIT License
124.69k stars 26.62k forks source link

Top level await (experimental) not working with Server Actions called from Client Components #54282

Open k-allagbe opened 1 year ago

k-allagbe commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.5.0: Thu Jun  8 22:22:20 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6000
    Binaries:
      Node: 20.5.1
      npm: 9.8.0
      Yarn: 1.22.19
      pnpm: N/A
    Relevant Packages:
      next: 13.4.19
      eslint-config-next: 13.4.19
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.6
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/k-allagbe/my-app-canary.git

To Reproduce

Describe the Bug

The provided code is an example of using Top Level Await in a file containing Server Actions called from a Client Component. This is relevant because, my actions might depend on frameworks that use top level awaits.

The result shows that calling Server Actions from Client Components doesn't work when the Server Action file involves top level await.

The problem is not observed with Server Components.

Expected Behavior

I expected to be able to call my Server Action from a Client Component even if a Top Level Await is involved in the Server Action file, because I might use some frameworks that rely on Top Level Awaits.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1540

keshari-nandan commented 1 year ago

I am also facing the same issue.

shuding commented 1 year ago

Please keep in mind that top level await is currently an experimental feature of Next.js, and we will be working on making it more stable in the future 👍

mordechaim commented 10 months ago

Looking forward to getting this fixed.

azzzy commented 9 months ago

Any development on this? We need to make all our forms request a REST API, because our DB library has top level awaits. Is there any known workaround?

sinasab commented 9 months ago

@azzzy could maybe make the import async and wrap in react's cache, was a decent workaround for me here: https://github.com/vercel/next.js/issues/58321#issuecomment-1812762598

Jokcy commented 9 months ago

Any progress on this? Spend a lot of time using server action to do my job, but stuck on this none documented bug, I suppose you should not say server action is stable!

adrienbaron commented 9 months ago

For people that get stuck with this, I had the same issue using SST and Drizzle in a Server Action called client side (both have top level awaits). Fixed it by simply importing my actions file from a server component:

import { initActions } from "@/app/actions";
initActions();
export const initActions = () => {
  // This is a no-op function that is called on the server to initialize the actions module.
  // Otherwise, Next crashes on top level await when action is called from client component.
};

I'm assuming this loads the module already on the server and means the top level await doesn't happen inside the code path that's broken? Not sure if it will actually work when deployed on Lambda though 😅

adrienbaron commented 9 months ago

Ok, tried deploying on AWS using SST with this trick and it actually does work 👍, even when the lambda is cold, calling the action from the client doesn't crash, as long as the action module has been imported from a server component somewhere.

Would still feel like this should be fixed in Next.js (as in top level await should work in Server Actions called from client components out of the box)

adrienbaron commented 8 months ago

This seems to be fixed with Next.js 14.0.4 🎉!

EDIT: Spoke too fast, I can still reproduce on Next.js 14.0.4.

AdityaBorkar commented 8 months ago

I am facing the same error when working with Server Actions on Next 14.0.4 and SST. Does Next.js have a plan to support top level await in server actions in the near time?

abitbetterthanyesterday commented 8 months ago

@adrienbaron Can you elaborate?

I struggle with this.

I have a form that uses useFormState and useFormStatus. The latter needs to be run in a client component.

How can I import the actions in a server component in this case?

dest1n1s commented 8 months ago

I'm also facing this issue. I think it's quite an essential demand to use top level await for initializing databases. Hope Next.js could fix this problem sooner.

abitbetterthanyesterday commented 8 months ago

I have fixed this by doing the following:

  1. Export an empty action from the same action file
    
    // myAction.ts
    'use server'
    export async function initAction(){} // literally empty

export async function myNormalActionWithTopLevelAwait(){ // DB Initialisation stuff or any logic depending on top level await libs. }


 2. Run that empty action in a server component

// AnyServerComponentSuchAsLayout.tsx import {initAction} from 'myAction.ts'

export const AnyServerComponentSuchAsLayout(){ await initAction() // yes, this does nothing

return () }


3. You can now use the action in your client component

// myClientComponent.tsx 'use client' import {myNormalActionWithTopLevelAwait} from 'myAction.ts'

export const MyClientComponent(){

/ You can now use your action here, without the issue of top level await /

}



It's a dirty workaround, but until it gets fixed you can do that.
I think that's what @adrienbaron was suggesting.
BarrySaikSoundry commented 8 months ago

I have fixed this by doing the following:

  1. Export an empty action from the same action file
// myAction.ts
'use server'
export async function initAction(){} // literally empty

export async function myNormalActionWithTopLevelAwait(){
  // DB Initialisation stuff or any logic depending on top level await libs.
 }
  1. Run that empty action in a server component
// AnyServerComponentSuchAsLayout.tsx
import {initAction} from 'myAction.ts'

export const AnyServerComponentSuchAsLayout(){
 await initAction() // yes, this does nothing

  return (<MyPage />)
}
  1. You can now use the action in your client component
// myClientComponent.tsx 
'use client'
import {myNormalActionWithTopLevelAwait} from 'myAction.ts'

export const MyClientComponent(){

/* You can now use your action here, without the issue of top level await */

}

It's a dirty workaround, but until it gets fixed you can do that. I think that's what @adrienbaron was suggesting.

This totally worked and fixed the issue I've been spending hours working around. Thanks!

mordechaim commented 7 months ago

I'd like to point out that we could use the instrumentation API to initialize DB connections. That may solve most use cases for top level await.

adrienbaron commented 7 months ago

@mordechaim I just tried on my side and it sadly doesn't seem to work (at least locally), the instrumentation hook do get called, and I do import the drizzle orm package, but it still crashes with top level await issue on the server action for some reason 🤷‍♂️

mordechaim commented 7 months ago

You shouldn't use top level await entirely. The case for using it is to initialize DB connections, that could be done in the hook calling some init() function.

adrienbaron commented 7 months ago

@mordechaim some libraries, like Drizzle ORM or SST use top level await in their own code though, the issue occurs because of this, not top level await in my own application code (I don't have any). Top level await is a standard JS feature these days, and Next supports it in most places, so it not being supported in this case feels more like a bug 🤷‍♂️.

serkan-bayram commented 7 months ago

I have tons of server actions depends on that one top level await (db connection), so I don't think adding init functions for every one of them is not going to be easy. Is there any plans to solve this issue in soon future?

ajlborges commented 7 months ago

I have fixed this by doing the following:

  1. Export an empty action from the same action file
// myAction.ts
'use server'
export async function initAction(){} // literally empty

export async function myNormalActionWithTopLevelAwait(){
  // DB Initialisation stuff or any logic depending on top level await libs.
 }
  1. Run that empty action in a server component
// AnyServerComponentSuchAsLayout.tsx
import {initAction} from 'myAction.ts'

export const AnyServerComponentSuchAsLayout(){
 await initAction() // yes, this does nothing

  return (<MyPage />)
}
  1. You can now use the action in your client component
// myClientComponent.tsx 
'use client'
import {myNormalActionWithTopLevelAwait} from 'myAction.ts'

export const MyClientComponent(){

/* You can now use your action here, without the issue of top level await */

}

It's a dirty workaround, but until it gets fixed you can do that. I think that's what @adrienbaron was suggesting.

I'm using SST and have the same problem and this worked as well (Thank you!), but it's ugly as hell. Hope this gets fixed soon.

deki23 commented 5 months ago

I have tons of server actions depends on that one top level await (db connection), so I don't think adding init functions for every one of them is not going to be easy. Is there any plans to solve this issue in soon future?

One way to solve this, is to create one async function called eg. initActions and place every initializer inside, and only call initActions in eg. layout component. That way its easier to maintain IMO.

Here is the example code that I am using:

src/app/_initActions.ts

export const initActions = async () => {
  await initProductActions();
  await initCategoryActions();

  ...more initActions
};

and in my src/app/layout.tsx

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  await initActions();

  return (<App/>);
}
yhc44 commented 1 month ago

Is there any update on this? I use SST + nextjs with server actions running from client side and i still get the error with next@^14.2.5

MwSpaceLLC commented 1 month ago

we tried for first time server actions (and i think not developed for any, but for symple post form actions)

new type await function like db connect and other, not work property:

 ⨯ SyntaxError: await is only valid in async functions and the top level bodies of modules
    at (action-browser)/./helpers/database.mongo.js (W:\clienti\project\.next\server\app\agents\[ACCDAG]\page.js:652:1)
    at __webpack_require__ (W:\clienti\project\.next\server\webpack-runtime.js:33:43)
    at eval (./helpers/collections.js:8:81)
    at (action-browser)/./helpers/collections.js (W:\clienti\project\.next\server\app\agents\[ACCDAG]\page.js:608:1)
    at __webpack_require__ (W:\clienti\project\.next\server\webpack-runtime.js:33:43)
    at eval (./helpers/server.actions.js:10:78)
    at (action-browser)/./helpers/server.actions.js (W:\clienti\project\.next\server\app\agents\[ACCDAG]\page.js:685:1)
    at Function.__webpack_require__ (W:\clienti\project\.next\server\webpack-runtime.js:33:43)
next@^14.2.5

problem: file that importend in function file helpers:

import {MongoClient, ObjectId} from "mongodb";

/**
 *
 * @param _id
 * @return {ObjectId}
 * @private
 */
export const Id = (_id) => new ObjectId(_id)

/**
 *
 * @type {*|string}
 */
const connectionString = process.env.ATLAS_URI || "";

/**
 *
 * @type {MongoClient}
 */
const client = new MongoClient(connectionString);

/**
 *
 * @type {MongoClient}
 */
const conn = await client.connect();

/**
 *
 * @type {Db}
 */
const database = conn.db();

export default database;

file server actions example:

'use server'

import {first} from "@/helpers/database";
import {generatePassword} from "@/helpers/global";
import {agents} from "@/helpers/collections";

// https://github.com/vercel/next.js/issues/54282
export async function createAgentOnDatabase(AGCDAG) {
    // toto: perform query or other

    // return to client data
}

For us, this mongo.db helper, work in many project, 100/20 project of in many framework.

only server actions not work.

MwSpaceLLC commented 1 month ago

UPDATE | ASYNC DB COLLECTION WORK FINE |

WE HAVE WRITE NEW DATABASE HELPER, NOW WORK PERFECT FOR SERVER ACTIONS. (MONGO DB)

database.actions.js || helper only for actions


import {Db, MongoClient, ObjectId} from "mongodb";

/**

/**

/**

/**

export const collection = async (name) => {

const db = await mongo()

return db.collection(name);

}


>form.actions.js
```js
'use server'

import {collection} from "@/actions/database.actions";

/**
 *
 * @param formData
 * @returns {Promise<{success: boolean}>}
 */
export async function createPlaylist(formData) {

    const playlists = await collection('playlists')

    const name = formData.get('name')
    const tags = formData.get('tags')
    const description = formData.get('description')
    const cover = formData.get('cover')
    const png = formData.get('png')

    // EXAMPLE INSERT DOCUMENT
    await playlists.insertOne({name, tags, description, cover, png})

    return {success: true};
}

FormCreate.jsx


'use client'

import useFormValues from "@/hooks/useFormValues"; import {createPlaylist} from "@/actions/form.actions";

export default function FormCreate() {

const {formData, formValues, setFormValues} = useFormValues()

const handleSubmit = async (e) => {
    e.preventDefault();

    await createPlaylist(formData);

}

return (
     <>component code jsx</>
)


In the meantime we await a solution, I hope it helps 👌
DudeRandom21 commented 4 weeks ago

I've also been struggling with this and I figured before spending time implementing a workaround I would at least try upgrading to Next 15 RC and see if the problem still exists or if I really just need a short term workaround. The good news is the compilation seems to work just fine in my use case (SST + drizzle) :partying_face:.

Unfortunately it seems like we have some dependencies that aren't ready for next 15 / react 19 because I get some runtime errors thrown inside some libs when loading up the page. Because of this I can't confirm that there isn't some kind of runtime bug that still exists but at least the compilation seems good. If I have some time I'll test this out in a minimal example, but it seems like time is a hard thing to come by lately :sweat_smile:.

Anyway hopefully some of this will turn stable in the near future and we'll all be able to clean up our workarounds!