nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Nuxt 3.0.0-rc.11 with Nitro 0.5.12> /Server/api functions fail on POST requests, GET methods are OK when publishing to Cloudflare pages functions)

Open ricky11 opened this issue 2 years ago • 3 comments

Environment

Nuxt 3.0.0-rc.10-27718444.40d0907 with Nitro 0.5.2-27718085.c24dbcf
Cloudflare pages with functions

Reproduction

https://stackblitz.com/edit/github-dxg5qg?file=server/api/customer.get.js

Describe the bug

Deployment of my Nuxt 3 app that contains following folder server folder structure

/server/api/customer.post.js <- POST fail with 405 method not allowed on cloudflare pages /server/api/customer.get.js <-- GET works. /server/api/test.js <-- should response to all routes

Nitro preset : nitro: { preset: 'cloudflare_pages', },

Upon pushing to github, cloudflare initiates a page build with the following successful log entries on cf pages:


Nuxt 3.0.0-rc.11-27719556.94377b1 with Nitro 0.5.3-27719444.ff99330
--
18:54:51.303 | ℹ Client built in 2421ms
18:54:51.304 | ℹ Building server...
18:54:52.513 | ✔ Server built in 1209ms
18:54:52.572 | ✔ Generated public .output/public
18:54:52.588 | ℹ Building Nitro Server (preset: cloudflare_pages)
18:54:59.396 | ✔ Nitro server built
18:54:59.419 | └─ functions/path.js (414 kB) (132 kB gzip)
...
18:55:16.586 | Success: Assets published!
18:55:18.083 | Success: Your site was deployed!

When visiting the domain all looks good and page is published! Now time to test the /functions folder

GET https://www.domain.com/api/customer HTTP/1.1

RESULT : 200 OK

POST https://www.domain.com/api/customer HTTP/1.1

RESULT : 405 NOT ALLOWED

POST https://www.domain/api/incorrectpath HTTP/1.1

RESULT : 405 NOT ALLOWED (should return 404 NOT FOUND)

Talking to Cloudflare discord support they say

Let them know that if they want to discuss it then feel free to pop on here. Cloudflare Pages never returns 405s, so this is Nitro

  • No POST handlers work, either in the .post.js file or the .js file
  • It works in dev (so in Vite), not prod
  • The command (make sure they know the command is build not generate)

Strangely when I POST to a route that does not exist like POST https://www.domain.com/api/blah, the result is also 405 NOT ALLOWED, hence it seems all POST requests are being block before the function is actually triggered, but for some reason all GET routes are OK.

to recreate the issue

  • use the stackblitz template above : https://stackblitz.com/edit/github-dxg5qg?file=server/api/customer.get.js
  • run npm run build , not generate, as generate will not create /functions folders
  • npx wrangler pages dev .output/public OR npx wrangler pages publish .output/public
  • visit xxx.com/api/customer -- this will be a GET and it will work
  • make a postman POST request to xx.com/api/customer - it will fail with 405 (if not pls let me know)

According to the nitro docs : it should just work.

Additional context

Cf discord channel has been informed of this issue and says they would be happy to connect with nuxt core team, please contact the discord community member @isaac-mcfadyen#4321 on cloudflare-pages discord channel.

ricky11 avatar Sep 14 '22 00:09 ricky11

Workarounds to get the cf /functions folder working correctly are: -change preset to 'node-server' -change cloudflare pages build command to 'npm run generate' instead of 'npm run build' <-- this itself does not port nuxt3 /server/api functions to cf pages functions.. -recreate /functions folder in ide and write functions as per cf api : https://developers.cloudflare.com/pages/platform/functions/

  • push to github and then everything just works GET, POST etc..

Above workaround bypasses NUXT3 /server folder and replies on cloudfloare / functions.. the downside of this is portability, we are tied down to cloudflare infra and loose all nuxt /server features.

ricky11 avatar Sep 16 '22 02:09 ricky11

Upgraded to 3.0.0-rc.11 , and still have the same problem.

ricky11 avatar Sep 22 '22 09:09 ricky11

sorry hoping to get some eyes on this issue

ricky11 avatar Oct 03 '22 17:10 ricky11

Also having this issue! "nuxt": "3.0.0-rc.13"

PizzaConsole avatar Nov 12 '22 20:11 PizzaConsole

I did a bit of digging on this, the error might be somewhere here

export async function onRequest (ctx: CFRequestContext) {
  try {
    // const asset = await env.ASSETS.fetch(request, { cacheControl: assetsCacheControl })
    const asset = await ctx.next()
    if (asset.status !== 404) { // GET returns 404; Other methods return 405
      return asset
    }
  } catch (_err) {
    // Ignore
  }

The GET method will return 404 every other method will return 405 Ultimately the execution will end up in wrangler > miniflare-dist > index.mjs > generateHandler > generateResponse and it calls this code:

    if (!request.method.match(/^(get|head)$/i)) {
      return new MethodNotAllowedResponse();
    }

That is the cause of this behavior.

Not sure what the recommended fix would be... @DaniFoldi ?

PizzaConsole avatar Nov 12 '22 21:11 PizzaConsole

Any updates on this?

Cosnavel avatar Dec 21 '22 09:12 Cosnavel

Also having the same issue? Any updates on this?

Plinpod avatar Jan 03 '23 19:01 Plinpod

I did a bit of digging on this, the error might be somewhere here

export async function onRequest (ctx: CFRequestContext) {
  try {
    // const asset = await env.ASSETS.fetch(request, { cacheControl: assetsCacheControl })
    const asset = await ctx.next()
    if (asset.status !== 404) { // GET returns 404; Other methods return 405
      return asset
    }
  } catch (_err) {
    // Ignore
  }

The GET method will return 404 every other method will return 405 Ultimately the execution will end up in wrangler > miniflare-dist > index.mjs > generateHandler > generateResponse and it calls this code:

    if (!request.method.match(/^(get|head)$/i)) {
      return new MethodNotAllowedResponse();
    }

That is the cause of this behavior.

Not sure what the recommended fix would be... @DaniFoldi ?

This fix seems to work to return a 200 however the /server/api code never actually gets called

Plinpod avatar Jan 03 '23 21:01 Plinpod

I appreciate the debug efforts by folks, and there is even a PR up (#796, which I actually recommend closing, see below), and I don't know how at the time of adding the preset, I didn't notice this - I think I was testing with a simple h3 return "test" handler.

Instead of relying on error codes to check if an asset is available as static, we should take advantage of Pages' _routes.json now that support has been added. Ref: https://developers.cloudflare.com/pages/platform/functions/routing/#creating-a-_routesjson-file

Question to @danielroe (I couldn't find anything related in the repo): is there perhaps a function to get all declared static routes? In a nuxt app, I'd use something like

{
   "version": 1,
   "include": ["/*"],
   "exclude": ["/_nuxt/*"]
}

as _routes.json, which gets me close to the goal already (Cloudflare doesn't charge anything for static assets) - adding it manually should in fact be a better workaround than messing with HTTP request methods and response codes.

DaniFoldi avatar Jan 04 '23 09:01 DaniFoldi

What you're talking about is static assets. That's how you reduce the requests you specify in exclude. Of course, that part helped me.

But this issue doesn't seem to have anything to do with what you're talking about.

This issue is 405 Method not allowed when I create an API using post method on server and distribute it to cloudflare pages functions, which is a very important issue that I am experiencing the same phenomenon and may have to give up nuxt from my project because of this issue.

The bottom line is not to obscure the subject and nature of the issue. You make me more confused.

YankeeTube avatar Jan 15 '23 11:01 YankeeTube

Well, indeed I am talking about the static assets. If you understand how the preset is set up, bypassing the function for static assets means we can remove the env.ASSETS.fetch() call from the preset handler, which is causing your 405s.

It does have lot's to do with the issue you're facing.

DaniFoldi avatar Jan 15 '23 11:01 DaniFoldi

So, according to you, you're facing 405 Error because you set /_nuxt/* to exclude?

YankeeTube avatar Jan 15 '23 13:01 YankeeTube

No, please read my message carefully. The 405 is returned from the env.ASSETS.fetch(), which we could remove once we generate a valid _routes.json - which takes care of serving static assets internally, instead of nitro having to "try" every fetch as a static asset.

DaniFoldi avatar Jan 15 '23 13:01 DaniFoldi

No, please read my message carefully. The 405 is returned from the env.ASSETS.fetch(), which we could remove once we generate a valid _routes.json - which takes care of serving static assets internally, instead of nitro having to "try" every fetch as a static asset.

Yeah, I understand what you're saying. But it's not an exact solution. There's not enough examples.

Using the same _routes.json you suggested, I still face 405 errors.

So how do you want me to deal with it?

YankeeTube avatar Jan 15 '23 13:01 YankeeTube

I'm the author of #796. I disagree to close my PR. Because:

  1. Nitro is helping you to build a js server. It's hard for Nitro to tell which route is static. Maybe you can write a get handler that returns the current time. And for static assets, I think it's out of the scope of Nitro.
  2. __routes.json has a limit of 100 rules. It's hard to reach the limit, but it means there will still be an edge case that you need to use Nitro to serve static assets.

DanSnow avatar Jan 17 '23 08:01 DanSnow

So the main issue with using __routes.json I see here is that in Nuxt, paths don't actually coorospond to the path in the filesystem.

Consider all static assets which are under the path /_nuxt/*. In the filesystem itself, they're under .nuxt/dist/client, so if you used __routes.json to try and serve them without getting billed (a good idea!) then they would all 404...

However the solution isn't to stop relying on __routes.json. All Functions requests are now being billed on Cloudflare Pages, with the exception of static assets from __routes.json, so not relying on that behavior is actually making sites more expensive for users, especially compared to basically every other competitor framework (SvelteKit, SolidStart, Astro, and Remix).

isaac-mcfadyen avatar Jan 17 '23 12:01 isaac-mcfadyen

Consider all static assets under the path /_nuxt/*. In the filesystem itself, they're under .nuxt/dist/client, so if you used __routes.json to try and serve them without getting billed (a good idea!) then they would all 404...

I just tested it. If you run NITRO_PRESET=cloudflare-pages npx nuxi build you can see Nuxt puts js files under .output/public/_nuxt and ask you to deploy .output/public. So yes, paths correspond to the filesystem and it should work if you put /_nuxt/* in __routes.json

DanSnow avatar Jan 17 '23 13:01 DanSnow

So yes, paths correspond to the filesystem and it should work if you put /_nuxt/* in __routes.json

Ah, my mistake.

In that case, IMO the __routes.json is the best option for serving static assets while allowing users not to be billed. It should be able to be generated automatically.

isaac-mcfadyen avatar Jan 17 '23 13:01 isaac-mcfadyen

In that case, IMO the __routes.json is the best option for serving static assets while allowing users not to be billed. It should be able to be generated automatically.

I agree. But I think this is out of scope for Nitro, because putting assets under /_nuxt is Nuxt's behavior, not Nitro's. Nuxt should maybe add a Nitro plugin to do this.

DanSnow avatar Jan 17 '23 13:01 DanSnow

@pi0 thoughts?

HelloAlexPan avatar Jan 17 '23 13:01 HelloAlexPan

I Just encountered this bug, which only happens with the Cloudflare pages. Works well in development.

For now, we had to move to vercel. But I would really love to have a solution.

trapcodeio avatar Feb 06 '23 10:02 trapcodeio

Would love to see progress on this issue. There's an open PR that, while imperfect, is better than the status quo.

DataDrivenMD avatar Feb 07 '23 17:02 DataDrivenMD

This is blocking the adoption of Cloudflare pages for me too. Any news?

gecode-es avatar Feb 19 '23 08:02 gecode-es