Slow API routes performance on NextJS

Hey everyone,

I’m running NextJS 13 with the Pages router on a private app. We use its app routes functionality (Routing: API Routes | Next.js) to proxy requests to our backend and to protect them using nextjs-auth0’s withApiAuthRequired method. We use the standard ‘@netlify/plugin-nextjs’ dependency to set this up Netlify side.

This works fine, but what we’ve noticed is that it’s just consistently running slowly. A request which would normally take 300ms if you were to make a request to our API directly is taking 1.5s - 2.5s when going though Netlify serverless functions via the proxy.

From what I can see, we’re not doing anything special that would explain the slowdown, so I’m just looking to get opinions on things I can try to improve this situation.

Here’s the code we’re using in NextJS for the proxy in /pages/api/[...path]. Nothing too special.

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const { accessToken } = await auth0.getAccessToken(req, res, { scopes });
    const { path, ...params } = req.query as {
      path: string[];
      [key: string]: string | string[];
    };
    const url = `${process.env.API_ROOT}${path.join('/')}`;
    const request: AxiosRequestConfig = {
      url: url,
      method: req.method as Method,
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
      params: params,
      data: req.body,
      responseType:
        req.headers['x-content-type'] === 'binary' ? 'arraybuffer' : undefined,
    };
    const response = await axios.request(request);
    const jsonResponse = await response.data;
    res.status(response.status || HttpStatusCode.OK).send(jsonResponse);
  } catch (err) {
    if (axios.isAxiosError(err)) {
      apiLogger.error(err, err.response?.status);
      res.status(err.response?.status || 500).json({
        ...err.response?.data,
      });
    } else {
      throw err;
    }
  }
};

export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    return await auth0.withApiAuthRequired(handler)(req, res);
  } catch (err) {
    const error = err as Error;
    apiLogger.error(error, undefined, 'Error thrown by handler');
    res.status(500).json(new ApiError('See logs for details', 500));
  }
};

export const config = {
  api: {
    externalResolver: true,
    bodyParser: {
      sizeLimit: '15mb',
    },
  },
};

I’ve also optimised the NextJS bundle sizes so the ‘first load js shared by all’ is in the green for the api paths.

Any ideas or pointers would be very welcome. Thanks. :slight_smile:

That might or might not be expected as it depends on several factors. When you connect directly, you’re simply making a single HTTP request to their server and getting a response. When you connect to them via Netlify, the following happens:

  • The request hits Netlify’s servers
  • We pass the request to AWS Lambda
  • AWS Lambda downloads your code bundle, prepares its systems, etc. and starts running the code
  • Once your code hits the part where you’re making the request to your backend, that’s when your backend will actually get the request
  • Backend responds to AWS Lambda
  • AWS Lambda responds back to us
  • We respond back to you

As you can see there are several more moving parts here and extra network connections being made each adding latency and other delays.

With that being said, you can still see if the request from AWS Lambda to your backend is being made within reasonable timeframe using console.time() statements. Other factors like location differences of each of these servers would also contribute to a factor.