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

Unable to proxy any path to a backend server instead of showing a Next.js 404 page #9338

Closed
kachkaev opened this issue Nov 7, 2019 · 10 comments

Comments

@kachkaev
Copy link
Contributor

kachkaev commented Nov 7, 2019

Is your feature request related to a problem? Please describe.

Create React App allows configuring a backend proxy in their development server, which is quite handy in certain scenarios. Any request that does not match an existing asset or an index HTML page gets forwarded to a backend server, if HOST is specified. For example:

/ # served by CRA
/whatever/autogenerated/chunk.js # served by CRA
/favicon.ico # served by the backend server if /public/favicon.ico does not exist
/any/path/to/non-existing/local/resource.json # served by the backend server

Essentially, they are just using a standard webpack-dev-server proxy feature as far as I understand.

I’ve been trying to configure our Next.js development server in a similar way but failed. We probably need to make a small tweak to the framework unless I’m missing something. Here’s roughly what I tried in the first place:

// server.js
const next = require("next")
const express = require("express")
const proxyMiddleware = require("http-proxy-middleware")

;(async () => {
  const app = next({ dev: true })
  await app.prepare()

  const expressServer = express()

  // Handle requests using Next.js
  const handle = app.getRequestHandler()
  expressServer.all("*", async (req, res) => {
    await handle(req, res)
  })

  // Handle requests using the proxy middleware
  expressServer.use(
    proxyMiddleware("/", {
      target: `https://example.com`,
      // a few more options
    }),
  )

  expressServer.listen(3000, (err) => {
    if (err) {
      throw err
    }
    logger.log("Server ready on port 3000")
  })
})()

If I place Next.js handler before the proxy middleware handler, all requests are processed by Next.js. If I swap them, no requests reach Next.js anymore. This happens because both handlers are too greedy.

I tried adding custom logic after await handle(req, res) to instruct express to ignore the just-obtained result from Next.js and proceed to the following hander. However, this did not work because response HTTP handers are already sent by then so cannot be rewritten. Even if it worked, it would be slow because for every request to proxy, Next.js would have to render a 404 page, which is then disposed.

Describe the solution you’d like

We have a co-located restful API that can start with anything and unfortunately, the routing is outside our control. There is no shared prefix or pattern I can whitelist – otherwise, I could just place the proxy middleware handler first. API path examples:

/whoami
/[USERNAME]/path/to/api/resource.json
/[anything]/[you]/[can]/[imagine]

I’d like to be able to proxy to all these paths in development just as I would do in CRA out of box. This means that I would like to somehow disable 404 page handling by Next.js.

This could look like:

// Handle requests using Next.js
const handle = app.getRequestHandler({ skip404: true })
expressServer.all("*", (req, res) => {
  return handle(req, res)
})

// Handle requests using the proxy middleware
// ...

I’m not insisting on the option name or even its existence, this is just an example of what could potentially help. The same problem does not apply to production, because we export static app files and then serve them with Nginx. When testing the production build locally, we use a custom Express server and it works:

// server.production.js
const express = require("express")
const proxyMiddleware = require("http-proxy-middleware")

const expressServer = express()
expressServer.use("/", express.static("out", { dotfiles: "ignore" }))
// ↑ not as greedy as Next.js handler, so Express proceeds to the below middleware
// if a static file in 'out' dir is not found

expressServer.use(
  proxyMiddleware("/", {
    target: `https://example.com`,
    // a few more options
  }),
)

expressServer.listen(5000, (err) => {
  if (err) {
    throw err
  }
  logger.log("Server ready on port 5000")
})

Describe alternatives you've considered

For now, I have to replace "*" with a regexp:

const handle = app.getRequestHandler({ skip404: true })
expressServer.all(/^\/(_next\/.*)?$/, (req, res) => {
  return handle(req, res)
})

This works only for apps that:

  • have one entry point (just pages/index.js, no routing)
  • do not contain anything in the public directory

The solution is quite limiting and won’t work for us in the long term.

Additional context

I know there is an RFC for plugins and am wondering if this new abstraction could solve our problem. No matter if yes or no, I’ve decided to share our team’s issue to collect some feedback and ideas. WDYT folks?

@rafaelalmeidatk
Copy link
Contributor

rafaelalmeidatk commented Nov 13, 2019

You can use a prefix on your Next app and rewrite the path to remove it:

server.use(
  proxyMiddleware('/api', {
    target: 'https://example.com',
    pathRewrite: { '^/api': '/' },
    changeOrigin: true,
  })
);

If you make a request to /api/whoami it will proxy the request to https://example.com/whoami

@kachkaev
Copy link
Contributor Author

kachkaev commented Nov 19, 2019

Thanks for your suggestion @rafaelalmeidatk! The problem with this approach is that your client logic should account for this prefix all the time. I.e. fetch(`${apiPrefix}/whatever`) instead of fetch(`/whatever`). Some requests like that are done via internal company's packages, which complicates apiPrefix injection significantly.

Besides, we not only want to proxy ajax requests, but also the login gateway. I.e:

  • / (Next.js) unauthenticated?
  • /login-gateway (proxied web page) wait for login, set cookies and bring the user back
  • / (Next.js) now authenticated

@timneutkens
Copy link
Member

timneutkens commented Nov 19, 2019

We might consider this for the next iteration of rewrites (after the first iteration is landed on stable).

@mAAdhaTTah
Copy link
Contributor

We're looking at something like this as an incremental migration path: serve whatever pages we can successfully from Next.js and send anything 404'ing on to the old app server (which we're transforming into an API server). I've been trying to make it work with a custom server but I haven't found a good way to determine whether Next will handle the URL or if there's a place to override before it responds.

@VinSpee
Copy link

VinSpee commented Jan 15, 2020

in our exploration we ended up handling this at the nginx level with a whitelist of patterns.

@trickreich
Copy link

I don't get it. Do I need express for local development to proxy for example all /api/* requests to http://target:port?

@timneutkens
Copy link
Member

Fixed by #10041

@bkniffler
Copy link

I wasn't able to test it properly, but does this also support websocket rewrite, like for example for graphql subscriptions?

@FBosler
Copy link

FBosler commented May 14, 2020

Could you provide an example that proxies to a different port?

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants