vercel-community / rust

🦀 Rust runtime for ▲ Vercel Serverless Functions
https://rust-runtime.vercel.app
MIT License
816 stars 50 forks source link

Using along side nextjs in same project causes next pages/api routes to be ignored #133

Open mmckegg opened 8 months ago

mmckegg commented 8 months ago

I have a NextJS project that I have added rust api route to, however now all of the existing nextjs pages/api/** routes return a 404.

Here's what I've done:

Everything (except for next api) works perfectly when I do this, including the rust api calls.

If I remove the rust runtime from vercel.json then the nextjs api routes work as expected (but then the rust api won't work).

I continued down the rabbit hole started in #79 and discovered that the reason this is happening is that Vercel CLI proxies the nextjs commands to an inner next dev process. When no additional routes are added, this proxy process works correctly. However once an API route exists, the CLI is adding a default 404 route to /api that breaks the proxy and stops nextjs api routes from working.

https://github.com/vercel/vercel/blob/644b8a52cb2cc8f05e215e2230f95f902cdf8ae8/packages/fs-detectors/src/detect-builders.ts#L1041-L1050C8

If I disable the code referenced above, everything works correctly. I'm wondering if the nextjs config needs to have an ignoreRuntimes field added, but perhaps that is a hack. I don't really understand what its actual purpose is. Unfortunately there doesn't seem to be anything I can do to work around this issue without modifying the vercel CLI source.

So this seems to be an issue with vercel cli. Happy to raise an issue there instead, but is someone more familiar with this able to comment?

ecklf commented 8 months ago

Can you confirm (as in the issue you've referenced) that this behavior still works on vercel-cli 28.16.7? We should try bisecting the versions to see which one introduced it and report it on the respective repo.

mmckegg commented 8 months ago

@ecklf My understanding is that this has never worked. The workaround mentioned by @abcd-ca in #79 was to use the internal port for nextjs localhost calls instead of the default proxied one (which I can confirm still works in the latest CLI version). This doesn't fix the issue, it just ignores it.

To get it to work locally, there's a trick, though I've been discussing this with Vercel and a ticket has been submitted with the Vercel CLI team which I expect will streamline this:

1 . vercel dev --debug Look for a line that contains Spawning dev command: next dev --port 50434. The port number differ from this one but it shows that the NextJS API endpoints are running on a different port than the Rust API endpoints which are on port 3000 (Thanks to Vercel for this tip)

  1. View the endpoints at the resulting url:
    • Javascript lambda: http://localhost:50434/api/hellojs
    • Rust lambda: http://localhost:3000/api/hellorust

The above is true whether or not the rust api routes are active, but when they are active, port 3000 will no longer proxy the nextjs api calls to the inner process (as detailed in my first post).

Edit: I should mention that this issue only affects the vercel dev cli, not when deployed to vercel.

Andersgee commented 5 months ago

The same issue persists as of vercel cli version 33.4.1 and nextjs 14.1.0

The workaround #79 eg running vercel dev --debug to see where the nodejs (and edge runtime) serverless functions actually are available still works.

To be able to play around a bit locally I just created a proxy on 3001 that forwards everything to 3000, except those special paths created by route.ts files that it forwards to 33779 or wherever vercel dev --debug sais they are. Something like:

//dev-proxy.mjs
import http from "node:http";
import httpProxy from "http-proxy";

const specialPort = 33779;
//const specialPaths = ["/api/edge-route", "/api/nodejs-route"];
const specialPaths = getSpecialPaths();

const proxy = httpProxy.createProxyServer();
const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  if (specialPaths.includes(url.pathname)) {
    proxy.web(req, res, { target: `http://localhost:${specialPort}` });
  } else {
    //everything else works as normal?
    proxy.web(req, res, { target: `http://localhost:3000` });
  }
});

server.listen(3001);

function getSpecialPaths(base = "src/app/api", res = []) {
  const files = fs.readdirSync(base);

  for (const file of files) {
    const path = join(base, file);
    if (fs.statSync(path).isDirectory()) {
      getSpecialPaths(path, res);
    } else if (path.endsWith("/route.ts")) {
      const p = path.split("/route.ts")[0].split("src/app")[1];
      res.push(p);
    }
  }
  return res;
}

And then open browser on localhost:3001 instead of localhost:3000