next.jsnextjs14

how to make curried server-functions work reliably on nextjs app-router


I'm testing the possibility to pass a curried action from page.tsx to a ClientComponent.tsx
I did so:

page.tsx

'use server'

import { ClientComponent } from './ClientComponent'

export default async function Page() {
  const buttonAction = await curriedAction(`SOME CURRIED SERIALIZABLE VALUE FROM SERVER`)
  return <ClientComponent buttonAction={buttonAction} />
}

async function curriedAction(curriedValue: string) {
  return async function performCurriedAction(paramFromClient: string) {
    'use server'
    console.log(`curried action on curriedValue:${curriedValue} and paramFromClient:${paramFromClient}`)
  }
}

ClientComponent.tsx

'use client'

export function ClientComponent({ buttonAction }: { buttonAction(paramFromClient: string): Promise<void> }) {
  return <button onClick={() => buttonAction('SOME PARAM FROM CLIENT')}>click</button>
}

clicking the button on rendered page, I can see on server log the curried function correctly invoked with curried value:

 GET /_next/webpack-hmr 404 in 778ms
curried action on curriedValue:SOME CURRIED SERIALIZABLE VALUE FROM SERVER and paramFromClient:SOME PARAM FROM CLIENT
 POST /test-curried-function 200 in 63ms

indeed it worked well, that's a mind-blowing useful feature !

Trying to understand how it works, I noticed that the server-curried-string "SOME CURRIED SERIALIZABLE VALUE FROM SERVER" was not present anywhere in the stuff fetched from the client,

However I noticed the action POST call:

------WebKitFormBoundaryCw3dGNjpJ69USr7N
Content-Disposition: form-data; name="1"

"8ARFpkVVsFrIvcKvRbK4mZdVP8y01+1Ezoj0XUgq3uGdw1SDh0hZaiOJIHjo7lHZwwuDaY2yslgYaoJAFSm0sCYOs4dmZocXiR25igJRioFU6JvmmrJ6IhL5eh6dHeitNdyle9cWHsmXYzajC3HaWoiRmE4w2msqPso="
------WebKitFormBoundaryCw3dGNjpJ69USr7N
Content-Disposition: form-data; name="0"

["$@1","SOME PARAM FROM CLIENT"]
------WebKitFormBoundaryCw3dGNjpJ69USr7N--

there's obviously "SOME PARAM FROM CLIENT"
and i guess that hashed string encapsulates the curried value from server

So I tried to restart the server, keeping the web-page open without refreshing
This time, clicking the button makes the server throw :

✓ Compiled /test-curried-function in 4.4s (2103 modules)
 ⨯ Internal error: OperationError: The operation failed for an operation-specific reason
    at AESCipherJob.onDone (node:internal/crypto/util:445:19)
    at AESCipherJob.callbackTrampoline (node:internal/async_hooks:130:17)
digest: "3732802089"
Cause: [Error: Cipher job failed]
 POST /test-curried-function 500 in 4586ms

refreshing the page, the action works fine again.

I guess this indicates restarting the server, some hashing key changed
This would brake a scalable web-server ( and server restarts as well )

I couldn't find documentation to fix this
Is there a configurable hash-secret to keep consistent web-server deployments ?


Solution

  • I just came across a similar error in my application. I believe it was caused by the encryption key Next uses for server actions cycling for each build.

    From the docs: "To prevent sensitive data from being exposed to the client, Next.js automatically encrypts the closed-over variables. A new private key is generated for each action every time a Next.js application is built. This means actions can only be invoked for a specific build."

    The docs then go on to say that you can override the process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY variable to keep a consistent server action encryption key across builds.

    Read more here https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#security