Fetching from builder serverless function, within an edge function

Hi there! I’m really enjoying playing around with Edge Functions. So much potential!

I’m trying to work with a rate-limited API, which only allows around 500 requests per month on the free plan. It’s a sports stats API that I’m using to get the next game when Carlos Alcaraz is playing.

Because edge function responses can’t be cached with a TTL, I was wondering if this approach is possible:

  • create a regular serverless function to get data from the rate-limited API with on-demand builders, with a TTL of 3600. (eg. when-is-alcaraz-playing.netlify.app/next-game-data.json)
  • create an edge function that fetches from the URL above. Most of the time it’ll be fetching a cached response.

If this is possible, I’ll be able to customise the edge function response based on the user’s timezone. I’ll also be able to relative times (eg. the game started 1 hour ago). I can’t do that if I just use the cached serverless function response by itself.

I’m keen to know if there are limitations about fetching from one edge function to a serverless function within a site, or if it behaves just like a regular fetch.

Thanks!

Hey! This is a great pattern, and one we recommend. It also has the benefit of caching the response in the CDN, local to the edge function.

Awesome, thank you for confirming. Will keep experimenting - the next challenge will be working out the user’s timezone based on the geo information!

Sadly, it does not work with edge functions (I use this strategy to cache data with regular serverless functions + on-demand builder) due to Deno limitations. When you fetch data, it fails because the on-demand builder and the edge functions are on the domain. We get the following error: Recursive requests to the same deployment cannot be processed.
Any ideas?

It sounds like your Edge Function and the On Demand Builder are working on the same path. Could you share reproduction steps?

Thanks for your quick answer. I try to play with remix and deploy with netlify edge functions.
In my example, I have the following remix loader:

const getData = async (url: string) => {
  const response = await fetch(
    url + "/api/my-builder/"
  );
  const data = await response.json();
  return data.title;
};

export const loader: LoaderFunction = async ({ context }) => {
  const url = new URL(context.url).origin;
  console.log(url);

  const results = await Promise.all([
    getData(url),
    getData(url),
    getData(url),
    getData(url),
  ]);
  return json(
    {
      title: results.join(", "),
    },
    {
      status: 200,
    }
  );
};

In my site, I have also defined an on demand builder with the following redirect:

[[redirects]]
  from = "/api/my-builder/*"
  to = "/.netlify/builders/builder"
  status = 200

This code works perfectly with the standard remix / netlify template (with serverless functions).
But I struggle when I want to use (and test) the template with netlify edge functions (GitHub - netlify/remix-edge-template: Deploy your Remix site to Netlify Edge Functions)

If you want, I can publish an example on github.

Could you deploy this as a site so we can see the error happening?

This is the route of my deployment that fails: https://remix-playground-nico.netlify.app/new
It tries to fetch data from this on-demand builder: https://remix-playground-nico.netlify.app/api/my-builder/

I’m seeing this message in our logs:

Recursive requests to the same deployment cannot be processed.

When I checked your deploy, I can see the Remix-generated Edge Function running on all paths - which includes your /api path as well as /.netlify/builders path. So, when you are running a fetch call, you’re calling the same Edge Function over and over and never actually hitting your On Demand Builder.

What I can suggest for now is that, try moving the On-Demand Builder to a different site and fetch from that URL to see if it works.

OR

You can create a custom Edge Function, and you should be able to use context.next() to do some transformations:

Note that, your custom Edge Function will be executed before Remix generated Edge Function - so you should probably only call it on your /api path.

Following your answer, I did some experiments.
As expected, the first solution works (deploying the on-demand builder on another site).
But this approach is not very practical. This API is part of my application. So, deploying two sites for a single application looks like a hack.
With the second solution, I did not manage to get any success. I keep on having the “Recursive requests to the same deployment cannot be processed.” error. And it looks so complicated for just fetching an API.
Sadly, I cannot move the code of the on-demand builder to the edge function due to the lack of the TTL feature.

I wonder if we can exclude paths from the netlify.toml config. It would be great to define: /* except /api/*. I think it’s not possible. I found nothing in the documentation.

Yeah, that’s not possible at the moment. But we can share that as a suggestion to the devs so that they could consider adding it as Edge Functions get out of beta.

Regarding solution 2, could you share the setup or an example repository of how you’ve linked everything? I thought it should work, so maybe we can make some changes to that setup to get it working?

It would be a very nice improvement.

For the second approach:
The custom edge function (for the /api path) is called first. Ok. But there is no way I can call the on-demand builder because context.next() will be the remix edge function. I cannot break the edge function chain (as I understand).
So when I try to fetch the API from the loader (/new path), I keep having the recursive error.
I’m not sure if it’s what you have in mind.

Yeah, I kinda felt that could be an issue. I think, as long as Remix provides a way to access the Netlify-specific context inside the server routes, this should be possible. I tried looking in Remix docs and didn’t find anything about it. Do you happen to know if that’s possible?

Yes, I can access the context in a Remix loader.
FYI, you can add this line in the server.js getLoadContext: (_, context) => context,
Now, I can access the context in the loader.
And I have something that works:
First, I need to redirect my on-demand builder to the same path as the remix path. (for example, /experiment if I want to visit the page /experiment). With context.next(), I get the data from the on-demand builder in my loader without any errors https://remix-playground-nico.netlify.app/experiment)
Interesting but not easy to guess when we just want to fetch an API.
However, I can see a limitation. In a remix, for a route, many loaders can be called (nested routes). So, I cannot call different APIs because context.next() is linked to a single API…
Other thought: In the loader, I cannot call the API (with context.next()) multiple times with different parameters.
Clearly, there are some limitations. I guess it will improve when it’s released. (Netlify edge functions are in beta and the remix-netlify-edge is still experimental).
Thanks for your help :slight_smile:

Hey @nicolaserny,

We had some feedback from the devs and it looks like, maybe you can use context.rewrite() to achieve what you need? That would also skip the Edge Functions in that chain. So you can set a specific path instead of relying on context.next().

I’ve just tried to play with context.rewrite without any success.
I declared a edge function to break the edge function chain on API (on-demand builder) calls:

export default async function (_request: Request, context: Context) {
  console.log(_request.url);
  return context.rewrite(_request.url);
}

This edge function is executed when my API is called:

[[edge_functions]]
  path = "/api/*"
  function = "test"

Now, in my remix loader, I fetch the data with await fetch(“…/api/my-builder”).
It ends up with the same error: “Recursive requests to the same deployment cannot be processed.”
Am I missing anything about context.rewrite?
I wonder if there are some limitations with the Deno runtime (used by the edge functions): Recursive requests and `loop detected` · Issue #187 · denoland/deploy_feedback · GitHub

Thanks for testing this @nicolaserny. Could you share the URL of this attempt? I tried visiting here: https://remix-playground-nico.netlify.app/api/, which did not work.

https://remix-playground-nico.netlify.app/withrewrite
(I defined different routes in Remix to test different use cases)

Thanks! I’ve relayed that back to the devs.

1 Like

Hey there,

I just want to close the communication loop here. Our dev team has researched various workarounds for this, and at the moment bypassing an edge function is not possible. I do not see this changing in the short term, as it is an upstream limitation we have confirmed with our vendors. I am sorry I don’t have better news for you!

1 Like