49

Before, when I made apps with create-react-app, I would have a setupProxy.js file that would route API requests similar to this

const proxy = require('http-proxy-middleware');
module.exports = function(app) {
    app.use('/api',
        proxy({
            target: 'http://localhost:8000',
            changeOrigin: true,
        })
    );
};

But that doesn't seem to work with next.js. When I do the same thing, I get various errors.

Googling a solution, a lot say to use a custom server of some kind. But how would I accomplish a proxy like above using the default nextjs dev server? (Equivalent of npm run dev when dev in my package.json is next dev.

cclloyd
  • 8,171
  • 16
  • 57
  • 104

5 Answers5

105

There is now an official feature for this in the config: Rewrites

Besides normal path rewrites, they can also proxy requests to another webserver

next.config.js:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8000/:path*' // Proxy to Backend
      }
    ]
  }
}

See Next.js Docs Rewrites

abernier
  • 27,030
  • 20
  • 83
  • 114
Birkenstab
  • 1,200
  • 1
  • 8
  • 6
  • 2
    this one is not working when i want WebSocket proxy. it's handshake is failing. – nemish zalavadiya neel Feb 18 '21 at 07:12
  • 1
    @nemishzalavadiyaneel socket.io doesn't work for me either using rewrites... Were you able to find a solution that does not involve creating a custom server? – Marnix.hoh Mar 17 '21 at 13:08
  • I still do not. I am creating `const` object and storing hardcoded URL there. share it if you find any solution. – nemish zalavadiya neel Mar 18 '21 at 16:53
  • 1
    I believe `Rewrites` just basic proxy setup. More proxy configuration need to setup custom server – Alan Yong May 27 '21 at 15:11
  • 1
    Incase anyone is handling this in their nginx config in production here is how to make config options development only. https://nextjs.org/docs/api-reference/next.config.js/introduction – Daniel Sep 19 '21 at 22:28
  • 1
    Though, there is issue with `https` -- `Error: self signed certificate` https://github.com/vercel/next.js/issues/21537 – abernier May 19 '22 at 20:28
  • 1
    Rewrites look fantastic, until you realise that environment variables are baked in at build-time which is a deal-breaker for multi-environment deployments https://github.com/vercel/next.js/issues/21888 – Laurence Jul 13 '22 at 14:47
  • Works. However, the example should probably have: `destination: "http://localhost:8000/api/:path*"`. Note the `/api/` part. – defraggled Aug 17 '22 at 05:01
  • does it work when you trying to map external domain? – Andy Dec 19 '22 at 14:38
  • I run into the problem that getStaticProps can't fetch the data when I run `next build` and `next start` because the frontend (and therefore the proxy) is offline. Am I doing something wrong? – Florian Walther Mar 07 '23 at 16:34
  • this seems to do an 308 redirect, not really a proxy. – Garr Godfrey Mar 16 '23 at 04:17
7

My server.js set up, hope it helps:

import express from 'express';
import next from 'next';
import proxy from 'http-proxy-middleware';

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
    const server = express();

    server.use(
        '/api',
        proxy({
            target: process.env.API_HOST,
            changeOrigin: true,
        }),
    );
 
    server.all('*', (req, res) => handle(req, res));

    server.listen(port, err => {
        if (err) throw err;
        console.log(`> Ready on http://localhost:${port}`);
    });
});

package.json:

"scripts": {
    "dev": "NODE_ENV=development node -r esm server.js",
    "build": "NODE_ENV=production next build",
    "start": "NODE_ENV=production node -r esm server.js",
},
abernier
  • 27,030
  • 20
  • 83
  • 114
iamhuynq
  • 5,357
  • 1
  • 13
  • 36
5

Another solution with catch-all routes + http-proxy-middleware:

// pages/api/proxy/[...slug].js

import { createProxyMiddleware } from "http-proxy-middleware"; // @2.0.6

const proxy = createProxyMiddleware({
  target: process.env.BACKEND_URL,
  secure: false,
  pathRewrite: { "^/api/proxy": "" }, // remove `/api/proxy` prefix
});

export const config = {
  api: {
    externalResolver: true,
    bodyParser: false,
  },
}

export default function handler(req, res) {
  proxy(req, res, (err) => {
    if (err) {
      throw err;
    }

    throw new Error(
      `Request '${req.url}' is not proxied! We should never reach here!`
    );
  });
}

see: https://stackoverflow.com/a/72310680

Piran
  • 7,180
  • 1
  • 24
  • 37
abernier
  • 27,030
  • 20
  • 83
  • 114
  • 3
    This works, but you'll also need to set the API Route config options to `{"externalResolver": true, "bodyParser": false}`. These respectively tell Next.js that the response is being handled by the proxy middleware, and to pass the raw request body to the proxy (otherwise non-GET requests will likely fail.) – soren121 Jun 18 '22 at 19:58
  • Thx @soren121 just for confirmation, are these options of `createProxyMiddleware`? – abernier Jun 19 '22 at 06:50
  • 3
    No, these are options of Next.js API Routes, as documented here: https://nextjs.org/docs/api-routes/api-middlewares#custom-config – soren121 Jun 20 '22 at 07:31
2

Rewrites didn't work for me, and neither did using axios config.proxy.

I've resorted to a good old constant:

const SERVER_URL = 
  process.env.NODE_ENV === 'production' ? 'https://produrl.com : 'http://localhost:8000';

export async function getStaticProps() {
  const data = axios.get(`${SERVER_URL}/api/my-route`)
  // ...
}

I would much rather proxy requests and keep my requests cleaner, but I don't have a day to spend wrestling with this.

Maybe this very simple setup will help others.

alexcs
  • 480
  • 5
  • 16
0

I have written a reverse-proxy NPM package, called reverse_proxy_pathmapper that can be used to set up reverse proxy between frontend and backend.

Here is the Usage:

import {ReverseProxyPathMapper} from "reverse_proxy_path_mapper";

const pathMapper = {
  "/api/?(.*)": "http://localhost:8000", // backend-api
  "/?(.*)": "http://localhost:3000" // frontend
                                    // ?(.*) -> Regex to match any
}

new ReverseProxyPathMapper({}, pathMapper).serve(9000);

Now access your frontend and backend on port 9000. URL starting with /api will be sent to backend and others will be sent to frontend.

Bipin Maharjan
  • 423
  • 6
  • 13