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.
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")
})
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:
pages/index.js, no routing)The solution is quite limiting and won’t work for us in the long term.
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?
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
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_We might consider this for the next iteration of rewrites (after the first iteration is landed on stable).
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.
in our exploration we ended up handling this at the nginx level with a whitelist of patterns.
I don't get it. Do I need express for local development to proxy for example all /api/* requests to http://target:port?
I wasn't able to test it properly, but does this also support websocket rewrite, like for example for graphql subscriptions?
Could you provide an example that proxies to a different port?
Most helpful comment
I wasn't able to test it properly, but does this also support websocket rewrite, like for example for graphql subscriptions?