Edge function: context.rewrite results in unexpected 301 response

I am combining an angular and vue app together to run on one domain and am using an edge function to handle which files should be served from which folder.

the edge function is placed in netlify/edge-functions/app-routing.ts

export default async (request: Request, context: Context): Promise<Response | void>  => {
  const url = new URL(request.url)
  const dotIndex = url.pathname.lastIndexOf('.')
  const extension = dotIndex === -1 ? '' : url.pathname.slice(dotIndex + 1)

  if (!extension) {
    return context.rewrite(getAppPath(url.pathname))
  } else {
    const referrer = request.headers.get('referer')
    if (referrer) {
      const referrerUrl = new URL(referrer)
      return context.rewrite(`${getAppPath(referrerUrl.pathname)}${url.pathname}`)
    }
  }
}

const getAppPath = (path: string) => {
  const isVue = path.toLowerCase().startsWith('/admin')

  return isVue ? '/vue' : '/angular'
}


export const config = { path: "/*" }

On my local netlify dev setup this is working perfectly and all request rewrites work as expected. I’m using a simple config:

[build]
  publish="dist"

[dev]
  port = 8080
  publish = "dist"

Once I deploy this on netlify it results in 301 responses. so navigating to root will result in the following response:

Request URL: https://main--combined-apps-test.netlify.app/
Request Method: GET
Status Code: 301 

Headers:
...
location: /angular/
...

this then turns in to a redirect loop on /angular
Can anyone give some insight on why context.rewrite does no rewrite but redirects on the netlify server?

Hi @erikseyferth.

There is a new syntax for doing rewrites that you can read about here.

-return context.rewrite(getAppPath(url.pathname))
+return new URL(getAppPath(url.pathname))

Would you mind trying that and letting us know if that addresses your problem?

Thanks!

HI @eduardoboucas,

Thanks, but this still does not work. ON my local netlify dev it does work as expected. but after deploying to https://combined-apps-test.netlify.app/ it still redirects to the /angular or /vue url instead of doing an actual rewrite.

On my local I also noticed some unwanted behaviour.
the new way of rewrites works differently from the context.rewrite syntax. as this after returning the new URL the edge function is invoked again. I solved it now by adding if statements to skip the 2nd request from redirect again and creating a loop. But It seems to me that it is inefficient to re-evaluate after a rewrite.

so I end up with two questions:

  • Is there a way to prevent netlify from rerunning the request and behave more like context.rewrite did?
  • why is the url changing on the netlify server while it is not on netlify dev?

for reference the new version of the code

export default async (request: Request): Promise<URL | undefined>  => {
  const url = new URL(request.url)
  const dotIndex = url.pathname.lastIndexOf('.')
  const extension = dotIndex === -1 ? '' : url.pathname.slice(dotIndex + 1)

  // skip target paths
  if (url.pathname === '/angular' || url.pathname === '/vue' || url.pathname.includes('/angular/') || url.pathname.includes('/vue/')) {
    return undefined
  }

  if (!extension) {
    // main navigation
    return new URL(getAppPath(url.pathname), url.origin)
  } else {
    // assest (assign app by referrer)
    const referrer = request.headers.get('referer')
    if (referrer) {
      const referrerUrl = new URL(referrer)
      return new URL(`${getAppPath(referrerUrl.pathname)}${url.pathname}`, referrerUrl.origin)
    }
  }

  return undefined
}

Kind regards,
Erik

Sorry to hear that isn’t working for you. I’ve just tried creating a reproduction case with your function on a test site and it does seem to work as expected (i.e. I see a rewrite, not a redirect).

Could you share some details on how you’re deploying your site? Are you using Netlify Build or the Netlify CLI? If the latter, could you update to the latest version and try again?

Thanks for taking a look. I have deployed using a git trigger. but I have tried a netlify deploy from the cli as well. the result is the same.

In the version currently online (https://main--combined-apps-test.netlify.app) I notice that the rewrite for the assets is working as expected. the first request (the main navigation bit in the script) still results in a 301 redirect.


in red the redirect
in orange the new angular route (skipped in the edge function)
in green the correct rewrites for the angular assets

to clarify: expected behaviour is to serve the angular files from / and not have a redirect to angular at all.

Is there a way to debug edge functions on the netlify environment?
and could you give me some insight on why the new style of rewrites invokes the edge function again after rewrite?

Thanks,
Erik

Hi @erikseyferth — I’m sorry for the delayed response!

Do you have any redirects in your netlify.toml or _redirects file? I’m trying to understand if there is anything else about your site that may be explaining this behaviour, since I can’t reproduce it with my test case.

To your questions:

The best way to debug edge functions is to use Netlify Dev with the CLI, but it sounds like you’re already doing that and it’s not giving you the same result? That discrepancy looks like a problem that we need to get to the bottom of.

The idea is that if you’re attaching edge functions to run on a given path, those should always run, and that not running them again without making it abundantly clear to the user may lead to unexpected behaviour.

For example, imagine you have an authentication function that is protecting a given route. If another edge function that is not behind that protection does a rewrite to the protected function, not running the edge functions for the protected route might mean unintentionally exposing a page that should be gated.

Having said that, if you really want to skip running the functions, you can use a modified version of context.next — unlike context.rewrite, this will not be deprecated.

The context.next method accepts an optional Request object as a parameter. If you set it with the URL you want to rewrite to, it will return the contents of that page without invoking additional edge functions that may be configured for that path.

export default async (req, context) => {
  const newURL = new URL("/new-path", req.url);
  const newReq = new Request(newURL, req);
  const res = await context.next(newReq);

  return res;
};

Hi @eduardoboucas – Thanks for your response.

I don’t have any netlify.toml redirects (i’ve copied the contents of that in the first post) and don’t have a redirects file at all.

with regards to the rerunning of edge functions after rewrites. I get your point about the need to always run the edge functions. I expected a rewrite not to be a request but rather just serving something else. This is the reason for my confusion.

I have implemented the context.next redirects and they work well in the netlify dev environment.

But alas. after deployment to netlify it still gives me redirects instead of rewrites.
Is it possible you support to look at and test with the actual netlify env we are uysing? I suspect there must be something in the logs or settings that can point out this discrepancy in behaviour.

Kind regards,
Erik

Hi again @erikseyferth.

Could you try to change the rewrite URLs to /angular/ and /vue/ instead of /angular and /vue (i.e. with a trailing slash)?

Our redirects engine does some URL normalisation to improve our cache hit ratio, and that could be the redirect we’re seeing.

If that’s the case, you should stop seeing a 301 after the change.

Thanks!

1 Like

Hi @eduardoboucas ,

That was it! Thanks a lot for helping me out. I hope this feature can also be added to netlify dev to prevent other people from having the same ‘mysterious’ behaviour.

Cheers!

Thanks for confirming you found your solution!