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

Client cannot recover from version skew #75541

Open
knpwrs opened this issue Jan 31, 2025 · 11 comments
Open

Client cannot recover from version skew #75541

knpwrs opened this issue Jan 31, 2025 · 11 comments
Labels
Server Actions Related to Server Actions.

Comments

@knpwrs
Copy link

knpwrs commented Jan 31, 2025

Link to the code that reproduces this issue

https://github.com/knpwrs/nextjs-skew-recovery-bug

To Reproduce

  1. Run npm run build
  2. Run npm start
  3. Open http://localhost:3000 and make sure the browser development tools are open.
  4. Press the Server Action button.
  5. Observe logs on the server indicating the function was called.
  6. The network response has a 200 response code indicating no errors and a text/x-component mime type.
  7. Shutdown the server, leave the app running in the web browser.
  8. Rename the logServer function in actions.ts and update the import and usage in components.tsx to match (for instance, logServer can be renamed to logServer2).
  9. Run npm run build
  10. Run npm start
  11. Go to the already running app
  12. Press the Server Action button.
  13. Observe an error on the server: [Error: Failed to find Server Action "006c3c7b08402d18959b82a9692db1011f32bcc8fd". This request might be from an older or newer deployment. Original error: Cannot read properties of undefined (reading 'workers')]
  14. There are no errors on the client. Error boundaries do not trigger. There are no uncaught errors in the console. There is no way for the client to know that the function call failed and no way for the client to recover.
  15. The network response has a 200 response code indicating no errors and a text/html mime type.
  16. Press the Throw Error button. Observe an uncaught error in the console.

Note that I couldn't get error.tsx or global-error.tsx to work for either the failed function call or the thrown client-side error.

Current vs. Expected behavior

Currently the client is not able to recover from version skew when a server action cannot be called. Everything appears normal to the client.

I would expect the error boundary to catch an error so the client can refresh and recover.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.2.0: Fri Dec  6 19:01:59 PST 2024; root:xnu-11215.61.5~2/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 23.6.0
  npm: 10.9.2
  Yarn: 1.22.19
  pnpm: 9.12.2
Relevant Packages:
  next: 15.2.0-canary.33 // Latest available version is detected (15.2.0-canary.33).
  eslint-config-next: N/A
  react: 19.0.0
  react-dom: 19.0.0
  typescript: 5.7.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Server Actions, Error Handling

Which stage(s) are affected? (Select all that apply)

next start (local), Other (Deployed), Vercel (Deployed)

Additional context

This is particularly problematic given the following quote from this blog post:

Secure action IDs: Next.js now creates unguessable, non-deterministic IDs to allow the client to reference and call the Server Action. These IDs are periodically recalculated between builds for enhanced security.

I couldn't find any documentation about this. It appears that action IDs can change at any time and clients which haven't refreshed yet won't have any way to deal with this.

@github-actions github-actions bot added Error Handling Related to handling errors (e.g., error.tsx, global-error.tsx). Server Actions Related to Server Actions. labels Jan 31, 2025
@knpwrs
Copy link
Author

knpwrs commented Feb 1, 2025

Thank you for the link, @leerob. It's not entirely clear from the documentation that the encryption key affects the non-deterministic action IDs. If it does, that doesn't fully address this issue.

Even if the encryption key is kept the same across builds it is still possible to get a different action ID and the client still has no way to recover when that happens.

I couldn't find any documentation anywhere on how action IDs are generated. Is it some sort of hash involving the file name, function name, and the NEXT_SERVER_ACTIONS_ENCRYPTION_KEY? In this case renaming the file, renaming the function, moving anything, or even building on a new machine can change the action ID (say, if the action ID is generated with a full absolute path to the file).

@knpwrs
Copy link
Author

knpwrs commented Feb 3, 2025

After some more experimentation the following code also does not catch any client-side errors, even though the server output indicates that the action cannot be found:

"use client";

import { logServer2 } from "./actions";

export function CallServerActionButton() {
  return (
    <button
      onClick={async () => {
        try {
          await logServer2();
        } catch (e) {
          // This never catches anything, even if the action is not found server-side
          console.error("Error in CallServerActionButton:", e);
        }
      }}
    >
      Server Action
    </button>
  );
}

@andresg747
Copy link

Having the same issue on a Next.js ^15.0.2 app deployed on AWS with SST v2.
I agree with @knpwrs. The documentation isn’t clear enough on:

  • How this helps resolve different action IDs across builds
  • How to generate the custom encryption key

This has been really tough to troubleshoot since there are no client-side errors, no way to identify which action is causing it, and no way to track it down. We also can’t determine the user’s experience. We assume the action isn’t executed, leaving the app broken without any way to handle or provide feedback to the user.

@mbranch
Copy link

mbranch commented Feb 5, 2025

I tried to open an issue to get clarity around this in docs and it was immediately closed by a bot.
#75448

@knpwrs
Copy link
Author

knpwrs commented Feb 5, 2025

@mbranch I tried the same thing before opening this issue and it was also closed by the bot: #75492

It seems like the bot just closes all documentation issues because there isn't a field in the template for a reproduction.

@samcx samcx removed the Error Handling Related to handling errors (e.g., error.tsx, global-error.tsx). label Feb 7, 2025
@samcx
Copy link
Member

samcx commented Feb 7, 2025

@mbranch @knpwrs Looks like there's an issue with the GitHub actions closing these Documentation template issues, taking a look 👁

@mbranch
Copy link

mbranch commented Feb 7, 2025

Thanks for looking into this @samcx. Not directly related to this issue, but I similarly tried to open an issue about issues getting closed too quickly (it also got closed 😂): #75449

@samcx
Copy link
Member

samcx commented Feb 8, 2025

@mbranch For that it's working as expected because we need a GitHub repo link (you provided a link to an issue instead)—the bot should not run when you run the Documentation issue template.

@samcx
Copy link
Member

samcx commented Feb 8, 2025

Even if the encryption key is kept the same across builds it is still possible to get a different action ID and the client still has no way to recover when that happens.

@knpwrs Did you confirm this with your reproduction? I am not seeing the Environment Variable in in your reproduction.

I do agree we could improve our Documentation here, so taking a look at that as well—

@knpwrs
Copy link
Author

knpwrs commented Feb 8, 2025

@samcx the reproduction is if you rename a function or do a similar refactoring such as moving a function. Clients which have not refreshed between deployments will attempt to call non-existing actions and the client has no way to recover —- no errors are thrown, and even if one were to install a service worker to intercept fetch calls the response code is 200, even though something like 404 would probably be more appropriate (though given that the network call is abstracted away this doesn’t matter as much as just making some sort of error the client can recover from).

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

No branches or pull requests

5 participants