Skip to content

Instrumentation Regression in Next.js 15.x Compared to 14.x #78044

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

Closed
Lisenish opened this issue Apr 11, 2025 · 3 comments · Fixed by #78454
Closed

Instrumentation Regression in Next.js 15.x Compared to 14.x #78044

Lisenish opened this issue Apr 11, 2025 · 3 comments · Fixed by #78454
Labels
Instrumentation Related to Next.js Instrumentation. linear: next Confirmed issue that is tracked by the Next.js team.

Comments

@Lisenish
Copy link

Link to the code that reproduces this issue

https://github.com/Lisenish/next-15-instrumentation-issue-mre

To Reproduce

  1. Clone the repository and run npm install.
  2. Execute npm run build
  3. Execute next start.
  4. Observe the logs and outputs in the console and observe the behavior in the browser
  5. Switch git branch to next-14-version
  6. Do steps 2,3,4 for this branch and compare the behavior

Current vs. Expected behavior

Next.js 15.x (Regression)

In Next.js 15.x, side effect files appear to execute before instrumentation file complete its execution. This causes environment variables or state setup in the instrumentation file to be unavailable during the side effect execution.

You can see in logs that we have the log line Running global side effect file. while instrumentation file didn't finish the execution

Logs:

next start

   ▲ Next.js 15.3.1-canary.4
   - Local:        http://localhost:3000
   - Network:      http://10.100.10.23:3000

 ✓ Starting...
Loading environment variables
 ✓ Ready in 245ms
Running global side effect file.
process.env.SOME_GSM_SECRET outside: undefined
Environment variables are loaded
process.env.SOME_GSM_SECRET in instrumentation: mocked secret value
Running in function to get secret value dynamically.
process.env.SOME_GSM_SECRET inside function: mocked secret value

On the app side it results in static value in global var not being set correctly, and it shows "no set value"

Image

Next.js 14.x (Expected Behavior)

Instrumentation files should fully run before other files (e.g., global side effect files) dependent on their side effects. This was the behavior in Next.js 14.x and is the expected behavior.

Logs shows it runs as expected (instrumentation load finishes before any attempts to execute side effects in other files)

> next start

 ▲ Next.js 14.2.27
 - Local:        http://localhost:3000

 ✓ Starting...
Loading environment variables
 ✓ Ready in 156ms
Environment variables are loaded
process.env.SOME_GSM_SECRET in instrumentation: mocked secret value

--- I opened the page in browser ---

Running global side effect file.
process.env.SOME_GSM_SECRET outside: mocked secret value
Running in function to get secret value dynamically.
process.env.SOME_GSM_SECRET inside function: mocked secret value

On the app side both values are present:

Image

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.3.0: Thu Jan  2 20:24:23 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6031
  Available memory (MB): 36864
  Available CPU cores: 14
Binaries:
  Node: 23.11.0
  npm: 10.9.2
  Yarn: N/A
  pnpm: 10.8.0
Relevant Packages:
  next: 15.3.1-canary.4 // Latest available version is detected (15.3.1-canary.4).
  eslint-config-next: N/A
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.8.3
Next.js Config:
  output: N/A

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

Instrumentation

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

next start (local), Other (Deployed)

Additional context

No response

@github-actions github-actions bot added Instrumentation Related to Next.js Instrumentation. linear: next Confirmed issue that is tracked by the Next.js team. labels Apr 11, 2025
@huozhi
Copy link
Member

huozhi commented Apr 23, 2025

@Lisenish It's due to Next.js started preloading the entries since v15 (related PR: #65289). We can fix this behavior on our side. But I'd like to learn what's the real-world case you're relying on this behavior? Cause the global side effects in page could execute in a lot of places either in static build or now in entry preloading. It sounds risky to me to rely on the contract that. Curious what are you doing with relying on the execution order of instrumentation and the page.

@Lisenish
Copy link
Author

Lisenish commented Apr 23, 2025

Hello @huozhi, thanks for looking into this! (and I see you even have a draft PR for this!)

Yes, I suspected this was due to the preloading. I also understand your point, and I agree global side-effects are dangerous and probably not the best practice it's better to avoid them, agree completely 👍

With this issue I mainly wanted to highlight that IMHO Next 15 has silently introduced a breaking change with this new behavior, and ideally it should be either fixed or we need to update the docs (even though it was only experimental in Next 14).

Why is it a breaking change? Please correct me if I'm wrong but I think it was always assumed that instrumentation.ts runs before everything else, because that is actually the requirement of many instrumentation tools to run before anything else in the app. Also, I think currently even the official docs suggest this as a place to work with side effects (see https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation#importing-files-with-side-effects).

I think you understand if I change the register in my MRE repo to something like:

await loadSecrets();
await import("./global-side-effect-file");

it still won't change the behavior, the global side effect will run before instrumentation's loadSecrets in Next 15 (let me know if you want me to create an actual branch in MRE).

If Next team thinks the current behavior is the correct one and won't fix then IMHO we should add a note to Next 15 changelog and update the above docs.


Now let me still describe our use case here at the end if you're still curious (IMHO we shouldn't focus on it here since there could be other cases among other users).

Our Use Case

It's actually somewhat similar to what I did in MRE:

  • We have our own dotenv wrapper which allows users to specify GCP Secret Manager URL as environment variable value (something like MY_SECRET=sm://my_secret#version1) in env files without exposing the secrets.
  • We call this wrapper in instrumentation.ts to actually replace the above values with actual secrets from GSM, so actually the rest of the app can still work with env vars as usual without worrying about fetching and caching the secrets (just in case: yes these are server side only secrets)
  • There were other unrelated files in the app with side effects which used the above environment variables with secrets
  • Due to this issue in Next 15 instead of actual values we caught a sneaky bug when these files started to use raw sm://my_secret#version1 values instead of actual secrets since side effects executed before the instrumentation and cached the raw value before secrets were initialized in instrumentation

We of course, refactored the side-effect part to get rid of it, since we wanted to upgrade to 15 without waiting for the official patch, but it actually required some effort since we have many web apps and we needed to do the same fix across all of them.

@Lisenish
Copy link
Author

Lisenish commented Apr 28, 2025

@huozhi Thank you for the quick fix! 🙇 (just in case also confirmed it works now in MRE with the latest canary)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Instrumentation Related to Next.js Instrumentation. linear: next Confirmed issue that is tracked by the Next.js team.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants