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 ?
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