nuxt: fetch error when upgrading from node.js 16 to 18

Environment

  • Operating System: Darwin
  • Node Version: v18.15.0
  • Nuxt Version: 3.5.3
  • Nitro Version: 2.4.1
  • Package Manager: npm@9.5.0
  • Builder: vite
  • User Config: modules, experimental
  • Runtime Modules: @nuxtjs/tailwindcss@6.7.2
  • Build Modules: -

Reproduction

  • clone this repo https://github.com/moebiusmania/atmmi-info-ui and install dependencies
  • run it with Node.js 16, you should see a populated page on localhost:3000 and no error on console
  • switch to Node.js 18 and re-run the dev server
  • on localhost:3000 you will see placeholder values and on console many 500 error coming from server side fetch

Describe the bug

Hello fellow devs, I have this small toy project built with Nuxt since early phases of v3 https://github.com/moebiusmania/atmmi-info-ui its a simple app that scrape data from a website using API routes and then some components consumes that same APIs and render the data on page.

I’ve used Node.js 16 since the beginning since there were some errors on Node.js 18, but trying again now I’m noticing this issue still lives. Since Node.js 16 will reach EOL within few months I would like to understand what’s happening, I’ve browsed some similar issues and all of the proposed solution are not working.

Its really few lines of code that works perfectly on 16 and suddenly breaks on 18 with no apparent reason or explanation, by logging the fetch error I get something like this:

  statusCode: 500,
  fatal: false,
  unhandled: false,
  statusMessage: undefined,
  data: {
    url: '/api/traffic',
    statusCode: 500,
    statusMessage: '',
    message: 'fetch failed (https://www.atm.it/it/Pagine/default.aspx)',

which doesn’t help much especially coming from the previous scenario where everything works.

Does anyone have a clear idea of what’s breaking the fetch requests between the two environments? thanks in advance

Additional context

No response

Logs

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

Update: So my problem suddenly magically disappeared. I have zero clue what made it work. It now works on both node 18 and 20. I’m using nvm with vscode on linux so don’t take my word for it. I was looking forward to actually finding the solution for everyone and I’m a little sad that I can no longer reproduce it. I think the problem was there also on my windows laptop so I’ll check if I can reproduce it there. I’ll be here in case anyone needs more info or cooperation. cheers.

Interesting, the problem magically just appeared for me despite not changing anything to my repro repo, it just started appearing

The good news is that it seems to not be related to Nuxt.

Running this file with node directly (without Nuxt)

const ENDPOINT = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async () => {
  const response = await fetch(ENDPOINT, {
    mode: "cors",
    headers: { "Content-Type": "application/json" }
  })
  return response.text()
}
console.log(getPage())
node:internal/deps/undici/undici:11522
    Error.captureStackTrace(err, this);
          ^

TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11522:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async getPage (/Users/hebilicious/GitHub/atmmi-info-ui/test.js:3:20) {
  cause: [Error: 005ED20402000000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:../deps/openssl/openssl/ssl/statem/extensions.c:922:
  ] {
    library: 'SSL routines',
    reason: 'unsafe legacy renegotiation disabled',
    code: 'ERR_SSL_UNSAFE_LEGACY_RENEGOTIATION_DISABLED'
  }
}

Doing some digging, it appears to be related to misconfigured IIS servers, and node refusing to connect to them after 18.

A node specific workaround would be to do something like this :

import { Agent } from "undici"
import crypto from "node:crypto"

const ENDPOINT = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async () => {
  const response = await fetch(ENDPOINT, {
    mode: "cors",
    dispatcher: new Agent({
      connect: {
        rejectUnauthorized: false,
        secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
      }
    })
  })
  return response.text()
}

@danielroe went to the bottom of this, we should definitely try to throw better error messages, the reason appears clearly when using node directly, but is hidden when it fails through ofetch.

@moebiusmania For your use case you should use this :

import { Agent } from "undici"
import crypto from "node:crypto"

const ENDPOINT: string = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async (): Promise<string> =>
  $fetch(ENDPOINT, {
    mode: "cors",
    dispatcher: new Agent({
      connect: {
        rejectUnauthorized: false,
        secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
      }
    })
  })

export { ENDPOINT, getPage }

Why the issue was closed? the problem is still present. Only server-side $fetch not working

FetchError: fetch failed (*)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)        
    at async $fetchRaw2 (file:///*/frontend/node_modules/ofetch/dist/shared/ofetch.d438bb6f.mjs:215:14)
    at async $fetch2 (file:///*/frontend/node_modules/ofetch/dist/shared/ofetch.d438bb6f.mjs:239:15)
    at async Object.handler (file:///*/frontend/.nuxt/dev/index.mjs:675:24)
    at async Object.handler (file:///*/frontend/node_modules/h3/dist/index.mjs:1285:19)
    at async Server.toNodeHandle (file:///*/frontend/node_modules/h3/dist/index.mjs:1360:7)

UPD. Changing localhost in the url to 127.0.0.1 worked… wtf

Update: So my problem suddenly magically disappeared. I have zero clue what made it work. It now works on both node 18 and 20. I’m using nvm with vscode on linux so don’t take my word for it. I was looking forward to actually finding the solution for everyone and I’m a little sad that I can no longer reproduce it. I think the problem was there also on my windows laptop so I’ll check if I can reproduce it there. I’ll be here in case anyone needs more info or cooperation. cheers.

Interesting, the problem magically just appeared for me despite not changing anything to my repro repo, it just started appearing

The good news is that it seems to not be related to Nuxt.

Running this file with node directly (without Nuxt)

const ENDPOINT = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async () => {
  const response = await fetch(ENDPOINT, {
    mode: "cors",
    headers: { "Content-Type": "application/json" }
  })
  return response.text()
}
console.log(getPage())
node:internal/deps/undici/undici:11522
    Error.captureStackTrace(err, this);
          ^

TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11522:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async getPage (/Users/hebilicious/GitHub/atmmi-info-ui/test.js:3:20) {
  cause: [Error: 005ED20402000000:error:0A000152:SSL routines:final_renegotiate:unsafe legacy renegotiation disabled:../deps/openssl/openssl/ssl/statem/extensions.c:922:
  ] {
    library: 'SSL routines',
    reason: 'unsafe legacy renegotiation disabled',
    code: 'ERR_SSL_UNSAFE_LEGACY_RENEGOTIATION_DISABLED'
  }
}

Doing some digging, it appears to be related to misconfigured IIS servers, and node refusing to connect to them after 18.

A node specific workaround would be to do something like this :

import { Agent } from "undici"
import crypto from "node:crypto"

const ENDPOINT = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async () => {
  const response = await fetch(ENDPOINT, {
    mode: "cors",
    dispatcher: new Agent({
      connect: {
        rejectUnauthorized: false,
        secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
      }
    })
  })
  return response.text()
}

@danielroe went to the bottom of this, we should definitely try to throw better error messages, the reason appears clearly when using node directly, but is hidden when it fails through ofetch.

@moebiusmania For your use case you should use this :

import { Agent } from "undici"
import crypto from "node:crypto"

const ENDPOINT: string = "https://www.atm.it/it/Pagine/default.aspx"
const getPage = async (): Promise<string> =>
  $fetch(ENDPOINT, {
    mode: "cors",
    dispatcher: new Agent({
      connect: {
        rejectUnauthorized: false,
        secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT
      }
    })
  })

export { ENDPOINT, getPage }

work for me

@oMoMa thanks for sharing about ofetch library, regarding your first comment: I’ve kept Node.js 16 up to now to avoid the issue, but as stated here https://github.com/nodejs/release#release-schedule on September v16 will reach EOL, so it would be better to find a solution now or give more visibility about it to Nuxt team.

regarding the following comments: yep, the other problem is that it looks like there isn’t a universal solution so far.

@pi0 it’s v1.0.1, the one that got installed when creating a Nuxt project

@Hebilicious thanks a lot, I’ve tried using vanilla fetch as you suggested (and updated the repo) but now the error seems to have moved to the undici package when using both v18.x and v20.3.x

{
"url": "[/api/status](http://localhost:3001/api/status)",
"statusCode": 500,
"statusMessage": "",
"message": "fetch failed",
"stack": "<pre><span class=\"stack internal\">at Object.fetch (node:internal/deps/undici/undici:11413:11)</span>\n<span class=\"stack internal\">at process.processTicksAndRejections (node:internal/process/task_queues:95:5)</span>\n<span class=\"stack\">at async getPage (./.nuxt/dev/index.mjs:758:20)</span>\n<span class=\"stack\">at async ./.nuxt/dev/index.mjs:819:16</span>\n<span class=\"stack internal\">at async Object.handler (./node_modules/h3/dist/index.mjs:1255:19)</span>\n<span class=\"stack internal\">at async Server.toNodeHandle (./node_modules/h3/dist/index.mjs:1330:7)</span></pre>"
}

@danielroe unfortunately this happens also on latest v20.3.x, although as I wrote above the error stack is different

thanks everybody for the support so far, but just to make clear my intention: I don’t care about my project working or not, as I said its a toy that I’m using to play and learn with Nuxt, but I’m fearing that at this point everyone that encountered this issue did just got fine with Node.js v16.x like me and didn’t brought it up. With EOL date approaching this could become quite a pain when bigger projects will migrate to newer versions of Node.js.

@Hebilicious so you’re telling me that the issue is not the fetch code (beside throwing error messages that could use more details) but on the fetched website server? 🤦 Welcome to the public services of the “most advanced” italian city 😄

well, I guess it explains some of the “magical” behaviors

Thanks a lot for your dedication and solution to this issue! this could be a nice snippet for Nuxt docs here nuxt.com/docs/api/utils/dollarfetch#fetch because honestly I wouldn’t been able to get to this by myself.

Yes, this is an aspx page, so they are using .net / IIS (ie Microsoft tech), which is notorious to have a critical security issue if you’re not using the latest versions.

And yes from the Nuxt/ofetch side the only issue is that the error message doesn’t show the actual reason (which I think we should find a way to fix)

I’m not sure if that should go in the docs, as this dispatcher fetch parameters has nothing to do with Nuxt, and it appears to be undici only (ie node), so this solution probably wouldn’t work on other runtimes (deno/bun/workerd …) ?

Update: So my problem suddenly magically disappeared. I have zero clue what made it work. It now works on both node 18 and 20. I’m using nvm with vscode on linux so don’t take my word for it. I was looking forward to actually finding the solution for everyone and I’m a little sad that I can no longer reproduce it. I think the problem was there also on my windows laptop so I’ll check if I can reproduce it there. I’ll be here in case anyone needs more info or cooperation. cheers.

It’s the ofetch library. Apparently it has some problems with node 18. According to this issue and a few others, and from personal experience, downgrading to node 16 fixes the issue. It’s sad cause basic server-side data fetching examples from the documentation also fail.

@Hebilicious Thank you so much for looking into this ❤️

@Hebilicious so you’re telling me that the issue is not the fetch code (beside throwing error messages that could use more details) but on the fetched website server? 🤦 Welcome to the public services of the “most advanced” italian city 😄

well, I guess it explains some of the “magical” behaviors

Thanks a lot for your dedication and solution to this issue! this could be a nice snippet for Nuxt docs here https://nuxt.com/docs/api/utils/dollarfetch#fetch because honestly I wouldn’t been able to get to this by myself.