Edgio

Route Features

Route features identify actions that will be applied to a request. Popular features include:
  • Caching: Customizes when and how content is cached.
  • Headers: Adds, modifies, or deletes headers from the request or response.
  • Origin: Controls how the CDN communicates with an origin server.
  • Response: Customizes the response sent to the client and determines whether we will allow prefetching instructions to be sent to the client.
  • Set Variables: Assigns a value to one or more user-defined variable(s) that are passed to your bespoke traffic processing solution.
  • URL: Redirects or rewrites requests to a different URL.
See Features Reference for a complete list of features and their behavior.

Defining Route Features

As outlined in the Route Features section of the CDN-as-Code guide:
  • Route features are defined as the second argument to the Router method being called in the routes.js file, such as .match(), .get(), .post(), etc.
  • May also be defined in conditional routes such as .if(), .elseif(), etc.
The argument is an object that supports features outlined in the Features Reference. The following example shows how to define a route feature that proxies a request to the origin host and caches it for 1 hour:
JavaScript
1router.match('/:path*', {
2 caching: {
3 max_age: '1h',
4 },
5 origin: {
6 set_origin: 'origin',
7 },
8});
Route features are often defined using object notation, but in some cases, it may be necessary to use RouteHelper methods to define features. Some functionality such as transforming requests/responses or serving static files requires the use of RouteHelper methods.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
When you’re mixing usage of object notation and RouteHelper methods, the addFeatures() function allows you to seamlessly integrate features defined in object notation directly within RouteHelper instance. The examples below illustrate the two notation styles, and then show how to combine them:
JavaScript
1// Using different notation styles
2router
3 // Define caching using object notation
4 .get('/some-path', {
5 caching: {
6 max_age: '1h',
7 },
8 })
9
10 // Serve a static file using a RouteHelper method
11 .get('/some-path', ({serveStatic}) => {
12 serveStatic('public/some-path.html');
13 });
14
15/* or */
16
17// Combining notation styles
18router.get('/some-path', ({addFeatures, serveStatic}) => {
19 // Use `addFeatures` RouteHelper method to define caching using object notation
20 addFeatures({
21 caching: {
22 max_age: '1h',
23 },
24 });
25
26 // Use `serveStatic` RouteHelper method to serve a static file
27 serveStatic('public/some-path.html');
28});
While both of these examples are functionally equivalent, the second example is more flexible because it allows you to define a single route using both notation styles for different features.

Caching

Add caching to a route using the caching feature:
JavaScript
1router.get('/some/path', {
2 caching: {
3 max_age: '1d',
4 stale_while_revalidate: '',
5 service_worker_max_age: '1h',
6 bypass_client_cache: true,
7 },
8 headers: {
9 set_response_headers: {
10 'x-sw-cache-control': 'max-age=3600',
11 },
12 },
13});

Customizing the Cache Key

A cache key is automatically generated for each request, but if your web application relies on query string parameter(s), request header(s), or cookie(s) when generating a response, then you should customize the cache key to include those elements. You can customize the cache key using the cache_key feature:
JavaScript
1router.get('/some/path', {
2 caching: {
3 max_age: '1d',
4 cache_key: {
5 // query string options are mutually exclusive; only one can be used for the cache key.
6 exclude_all_query_params: boolean,
7 include_all_query_params: boolean,
8 include_all_query_params_except: ['session_id', 'utm_source'],
9 include_query_params: ['page', 'filters'],
10
11 include_headers: ['x-my-header'],
12 include_cookies: ['x-my-cookie', 'language', 'currency'],
13 },
14 },
15});

Debug Cache Headers

The debug cache response headers provide additional information about the cache policy applied to the requested asset. Learn more.
To enable debug mode, you need to set the debug_header feature. In your routes.js file, add the following:
JavaScript
1import {Router, edgioRoutes} from '@edgio/core/router';
2
3export default new Router().use(edgioRoutes).get('/some-path', {
4 /* ... */
5});
Including edgioRoutes in your router will automatically enable the debug feature for all routes. You can also enable debug mode for a specific route by adding the debug_header feature to the route:
JavaScript
1router.match('/some-path', {
2 headers: {
3 debug_header: true,
4 },
5 /* ... */
6});
To see the debug headers in the response, you will need to specify the x-ec-debug header in your request. This request header should list the values of the debug headers you want to see in the response as defined under Requesting Debug Cache Information.
For example, you can use the edgio curl command to request the x-ec-cache and x-ec-cache-state headers:
Bash
1edgio curl https://your-site.com/some-path -H "x-ec-debug:x-ec-cache,x-ec-cache-state"
See an example of the response headers using our Simple Performance example site:
Bash
1# with edgio curl
2edgio curl https://edgio-community-examples-v7-simple-performance-live.edgio.link/ -H "x-ec-debug:x-ec-cache,x-ec-check-cacheable,x-ec-cache-key,x-ec-cache-state,x-ec-cache-remote"
3
4# with curl
5curl -I -H "x-ec-debug: x-ec-cache,x-ec-check-cacheable,x-ec-cache-key,x-ec-cache-state,x-ec-cache-remote" https://edgio-community-examples-v7-simple-performance-live.edgio.link/
In the following output, you will see the debug headers that were available in the response:
Diff
1➜ ~ edgio curl https://edgio-community-examples-v7-simple-performance-live.edgio.link/ -H "x-ec-debug:x-ec-cache,x-ec-check-cacheable,x-ec-cache-key,x-ec-cache-state,x-ec-cache-remote"
2
3URL : https://edgio-community-examples-v7-simple-performance-live.edgio.link/ 🔗
4From: 192.168.50.150:51204 🖥️
5To : 64.12.0.33:443 🌎
6
7HTTP/2 200
8Response Headers
9 accept-ranges: bytes
10 cache-control: public, max-age=0, must-revalidate
11 content-length: 417115
12 content-type: text/html; charset=UTF-8
13 date: Wed, 31 May 2023 14:50:30 GMT
14 etag: "665423646ec1a20bb8e43f8a71a132ba-ssl"
15 last-modified: Wed, 31 May 2023 14:36:48 GMT
16 server: Netlify
17 server-timing: edgio_cache;desc=TCP_EXPIRED_HIT,edgio_pop;desc=dcd,edgio_country;desc=US
18 strict-transport-security: max-age=31536000
19+ x-ec-cache: TCP_EXPIRED_HIT from ECAcc (dcd/7D26)
20+ x-ec-cache-key: //http/801B1A5C/origin/53/:/hs-4718288209145817659
21+ x-ec-cache-state: max-age=0 (0s); must-revalidate; cache-ts=1685544630 (Wed, 31 May 2023 14:50:30 GMT); cache-age=0 (0s); remaining-ttl=0 (0s); expires-delta=none
22+ x-ec-check-cacheable: YES
23 x-edg-mr: 7:3;
24 x-edg-version: 53 7 4 7.0.7 2023-05-31T14:32:16Z be36ceec-4cfd-4d56-aa41-ced08cd5352c
25 x-nf-request-id: 01H1S4KY3H6QM7BZ0MRJC2C3MQ
26
27
28 DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer
29[ 5ms | 204ms | 69ms | 87ms | 85ms ]
30 | | | | |
31 namelookup:5ms | | | |
32 connect:209ms | | |
33 pretransfer:278ms | |
34 starttransfer:365ms |
35 total:450ms
36
37Response Body
38 Disabled. To enable use EDGIO_CURL_SAVE_BODY=true or EDGIO_CURL_SHOW_BODY=true
39
40➜ ~

Proxying an Origin

Same Path

To forward a request to the same path on one of the origins listed in edgio.config.js, use the origin feature:
JavaScript
1router.get('/some-path', {
2 origin: {
3 set_origin: 'origin',
4 },
5});
The value of set_origin corresponds to the name property of an origin in edgio.config.js. For example:
JavaScript
1module.exports = {
2 // ... other configurations
3
4 origins: [
5 {
6 name: 'origin',
7 hosts: [
8 {
9 // The domain name or IP address of the origin server
10 location: 'www.my-site.com',
11 },
12 ],
13 },
14 ],
15};

Different Path

To forward the request to a different path, use the url.url_rewrite feature:
JavaScript
1router.get('/products/:productId', {
2 origin: {
3 set_origin: 'origin',
4 },
5 url: {
6 url_rewrite: [
7 {
8 source: '/products/:productId',
9 syntax: 'path-to-regexp',
10 destination: '/p/:productId',
11 },
12 ],
13 },
14});

Adding Caching

To cache proxied requests at the edge, use the caching feature:
JavaScript
1router.get('/products/:productId', {
2 caching: {
3 max_age: '1d',
4 stale_while_revalidate: '1h',
5 },
6 origin: {
7 set_origin: 'origin',
8 },
9});

Altering the Request

You can alter request headers when forwarding a request to a backend:
JavaScript
1router.get('/products/:productId', {
2 headers: {
3 set_request_headers: {
4 'header-name': 'some-value',
5 },
6 },
7 origin: {
8 set_origin: 'origin',
9 },
10});
The above example makes use of the headers.set_request_headers feature.

Altering the Response

You can also alter the response before and after the cache:
JavaScript
1router.get('/products/:productId', {
2 origin: {
3 set_origin: 'origin',
4 },
5 headers: {
6 // remove `header-name` from the origin response
7 remove_origin_response_headers: ['header-name'],
8
9 // add `header-name` to the response, appending the value to the
10 // header if it already exists
11 add_response_headers: {
12 'header-name': 'some-value',
13 },
14
15 // set/overwrite `header-name` to `some-value` in the response
16 set_response_headers: {
17 'header-name': 'some-value',
18 },
19
20 // append `another-value` to `header-name` in the response,
21 // becoming `some-value,another-value`
22 set_response_headers: {
23 '+header-name': ',another-value',
24 },
25
26 // remove `header-name` from the response by name
27 remove_response_headers: ['header-name'],
28
29 // or set with an empty value to remove `header-name` from the response
30 set_response_headers: {
31 'header-name': '',
32 },
33 },
34});
Additional information on the headers feature can be found in the Features guide.

Altering All Responses

You can also write catch-all routes that will alter all responses. One example where this is useful is injecting Content Security Policy headers.
Another example is adding response headers for debugging, which is often useful if Edgio is behind another CDN or if you are troubleshooting your router rules. For example, you could respond with the value of request %{http_x_forwarded_for} into x-debug-xff to see the value that Edgio is receiving from the CDN:
JavaScript
1router.match(
2 {
3 path: '/:path*',
4 query: {
5 my_site_debug: 'true',
6 },
7 },
8 {
9 headers: {
10 set_response_headers: {
11 'x-debug-xff': '%{http_x_forwarded_for}',
12 },
13 },
14 }
15);
The rules for interpolating the values of request and response objects can be found in the routing guide. Note that catch-all routes that alter headers, cookies, or caching can be placed at the start of your router while allowing subsequent routes to run because they alter the request or the response without actually sending a response. See route execution for more information on route execution order and sending responses.

Transforming Requests / Responses

If you need to modify a request before going to an origin, or modify the response from an origin, you may use transformRequest and transformResponse functions on the proxy handler. Transform functions will be executed within the Edgio cloud, and will not be executed on the edge. See Cloud Functions for more information.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.

transformRequest Function

You can modify the request before it is sent to the origin using the transformRequest function. This example shows how you could add a foo property to the request body before sending it to the origin:
JavaScript
1router.get('/products/:productId', ({proxy}) => {
2 proxy('origin', {
3 transformRequest: (request) => {
4 request.body = JSON.stringify({
5 ...JSON.parse(request.body),
6 foo: 'bar',
7 });
8 },
9 });
10});

transformResponse Function

Similarly, you can modify the response from the origin before it is sent to the client using the transformResponse function. This example shows how you could add an HTML script tag to the response body before sending it to the client:
JavaScript
1import responseBodyToString from '@edgio/core/utils/responseBodyToString';
2import $ from 'cheerio';
3/* ... */
4router.get('/products/:productId', ({proxy}) => {
5 proxy('origin', {
6 transformResponse: (response) => {
7 const body = responseBodyToString(response);
8 const $body = $(body).append(
9 '<script src="https://example.com/script.js"></script>'
10 );
11 response.body = $body.html();
12 },
13 });
14});

Manipulating Cookies

You can manipulate cookies before they are sent to the browser using headers.set_response_headers:
JavaScript
1router.get('/products/:productId', {
2 origin: {
3 set_origin: 'origin',
4 },
5 headers: {
6 // add cookie `foo=bar` to the response
7 add_response_headers: {
8 'set-cookie': 'foo=bar;',
9 },
10 },
11});

Adding Options to Cookies

In addition to the name and value of the cookie, you can also add attributes to each cookie. For information on possible cookie attributes, please refer to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
JavaScript
1import {serializeCookie} from '@edgio/core/utils/cookieUtils';
2
3router.get('/products/:productId', {
4 origin: {
5 set_origin: 'origin',
6 },
7 headers: {
8 // set cookie with options
9 add_response_headers: {
10 'set-cookie': 'foo=bar; Secure; HttpOnly;',
11 },
12
13 // set cookie with options using the serializeCookie helper
14 add_response_headers: {
15 'set-cookie': serializeCookie('foo', 'bar', {
16 Secure: true,
17 HttpOnly: true,
18 }),
19 },
20 },
21});

Proxying to Different Backends Based on Different Host Names

To proxy to different backends by matching the host header (e.g. different backends for different international sites):
JavaScript
1router
2 .match(
3 {
4 path: '/:path*',
5 headers: {
6 host: 'yoursite.c1',
7 },
8 },
9 {
10 origin: {
11 set_origin: 'country1-backend',
12 },
13 }
14 )
15 .match(
16 {
17 path: '/:path*',
18 headers: {
19 host: 'yoursite.c2',
20 },
21 },
22 {
23 origin: {
24 set_origin: 'country2-backend',
25 },
26 }
27 )
28 .match(
29 {
30 path: '/:path*',
31 },
32 {
33 origin: {
34 set_origin: 'everybody-else-backend',
35 },
36 }
37 );

Serving a Static File

To serve a specific file, use the serveStatic method.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router
2 // cache the favicon for 1 day
3 .get('/favicon.ico', {
4 caching: {
5 max_age: '1d',
6 client_max_age: '1h',
7 },
8 })
9
10 // serve the favicon from the `public` directory
11 .get('/favicon.ico', ({serveStatic}) => serveStatic('public/favicon.ico'));

Serving Static Files From a Directory

To serve a files from a directory, use the serveStatic method.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router
2 // cache the static assets for 1 day
3 .get('/assets/:path*', {
4 caching: {
5 max_age: '1d',
6 client_max_age: '1h',
7 },
8 })
9
10 // serve the assets from the `public` directory
11 .get('/assets/:path*', ({serveStatic}) => serveStatic('public/:path*'));

Serving the Service Worker

Similar to the above example, you can serve the service worker from its directory (e.g. /dist/service-worker.js).
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router
2 // cache the service worker for 1 day
3 .get('/service-worker.js', {
4 caching: {
5 max_age: '1d',
6 client_max_age: '1h',
7 },
8 })
9
10 // serve the service worker from the `dist` directory
11 .get('/service-worker.js', ({serveStatic}) =>
12 serveStatic('dist/service-worker.js')
13 );

Routing to Cloud Functions

Render the result of a Cloud Function within your application by using the renderWithApp method. Use this method to respond with an SSR or API result from your application.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router.get('/some/:path*', ({addFeatures, renderWithApp}) => {
2 addFeatures({
3 caching: {
4 max_age: '1d',
5 bypass_client_cache: true,
6 },
7 });
8
9 renderWithApp();
10});

Image Optimization

Edgio can dynamically transform your images to tailor your site’s design, experience, and performance needs. Image optimization can be enabled using the response.optimize_images feature on your route(s).

Optimizing Local Images

Images local to your project, such as those in your public directory, can be both served as static assets and processed by the image optimizer.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1// match all /images/* requests
2router.match(/\/images\/(.*)/, ({addFeatures, serveStatic}) => {
3 // serve the image as a static asset, referencing the capture group from the match
4 serveStatic('public/images/$1');
5
6 // add the image optimization and caching feature
7 addFeatures({
8 caching: {
9 max_age: '1d',
10 },
11 response: {
12 optimize_images: true,
13 },
14 });
15});
This example:
  • Matches all /images/* requests
  • Proxies the request to the static asset origin where the asset is hosted
  • Enables the image optimization feature
  • Caches the response for 1 day
Sample request: https://example.com/images/my-image.jpg?width=200&height=200
Rules should match using a regular expression that captures the image path and query string parameters containing the image optimization options. This is necessary to ensure optimization options are captured and passed to the image optimizer. Using simple path matching (e.g. /images/:path*) will not capture the query string parameters and optimizations will not be applied.

Optimizing Remote Images

Images hosted on a remote server can be optimized by proxying the request to the origin and adding the image optimization feature.
JavaScript
1router.match('/images/:path*', {
2 caching: {
3 max_age: '1d',
4 },
5 origin: {
6 set_origin: 'origin',
7 },
8 response: {
9 optimize_images: true,
10 },
11});
This example:
  • Matches all /images/* requests
  • Proxies the request to the origin origin where the asset is hosted
  • Enables the image optimization feature
  • Caches the response for 1 day
If you need to modify the request path before proxying to the origin, you can use the url.url_rewrite feature.
JavaScript
1router.match(/\/images\/(.*)/, {
2 caching: {
3 max_age: '1d',
4 },
5 origin: {
6 set_origin: 'media',
7 },
8 url: {
9 url_rewrite: [
10 {
11 source: '/images/(.*)',
12 syntax: 'regexp',
13 destination: '/assets/images/$1',
14 },
15 ],
16 },
17 response: {
18 optimize_images: true,
19 },
20});
This example:
  • Matches all /images/* requests
  • Proxies the request to the media origin where the asset is hosted
  • Rewrites the request path to /assets/images/* to match the path on the origin
  • Enables the image optimization feature
  • Caches the response for 1 day

Responding with a String Response Body

To respond with a simple, constant string as the response body use the response.set_response_body and response.set_done features:
JavaScript
1router.get('/some-path', {
2 caching: {
3 max_age: '1d',
4 },
5 headers: {
6 set_response_headers: {
7 'Content-Type': 'text/html',
8 },
9 },
10 response: {
11 set_done: true,
12 set_response_body: `
13 <!doctype html>
14 <html>
15 <body>Hello World</body>
16 </html>`,
17 },
18});
When using response.set_response_body to send a response, you must also set response.set_done to true. This will allow the request to continue matching any subsequent rules, but will prevent the request from being forwarded to the origin.
To compute a dynamic response, use the compute method.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router.get('/hello/:name', ({cache, setResponseHeader, compute, send}) => {
2 cache({
3 edge: {
4 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours
5 },
6 });
7 setResponseHeader('Content-Type', 'text/html');
8 compute((request, response) => {
9 send(`
10 <!doctype html>
11 <html>
12 <body>Hello ${request.params.name}</body>
13 </html>
14 `);
15 });
16});

Redirecting

To redirect the browser to a different URL, use the url.url_redirect feature, optionally specifying the HTTP status code:
JavaScript
1router.get('/p/:productId', {
2 url: {
3 url_redirect: {
4 code: 301,
5 source: '/p/:productId',
6 syntax: 'path-to-regexp',
7 destination: '/products/:productId',
8 },
9 },
10});
To compute the destination URL, use the compute method.
This feature requires a RouteHelper class method, since it cannot be implemented through standard object-based configuration. However, we strongly recommend to only use RouteHelper for documented use cases.
JavaScript
1router.get('/p/:productId', ({redirect, compute, cache}) => {
2 cache({
3 edge: {
4 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours
5 },
6 });
7 compute(async (request) => {
8 const destination = await getDestinationFromMyAPI(request.params.productId);
9 redirect(destination);
10 });
11});

Redirecting All Traffic to a Different Domain

JavaScript
1// Redirect all traffic except those with host header starting with www. to www.mydomain.com
2router.match(
3 {headers: {host: /^(?!www\.).*$/}},
4 {
5 url: {
6 url_redirect: {
7 code: 302,
8 destination: 'https://www.mydomain.com%{request_uri}',
9 },
10 },
11 }
12);
13
14// Redirect all traffic from www.domain.com to domain.com
15router.match(
16 {headers: {host: /^(www\.).*$/}},
17 {
18 url: {
19 url_redirect: {
20 code: 302,
21 destination: 'https://domain.com%{request_uri}',
22 },
23 },
24 }
25);

Redirecting HTTP to HTTPS

To redirect all HTTP traffic to HTTPS, use the url.url_redirect feature. Matching the source with (.*) will capture the entire path and query string that is then appended to the destination. Referencing %{host} in the destination will ensure that the request is redirected to the current host. See Feature Variables for more information.
JavaScript
1router.match(
2 {scheme: 'HTTP'},
3 {
4 url: {
5 url_redirect: {
6 code: 302,
7 source: '(.*)',
8 destination: 'https://%{host}$1',
9 },
10 },
11 }
12);

Blocking Unwanted Traffic

Blocking traffic from specific countries

If you need to block all traffic from a specific country or set of countries, you can do so by matching requests by the country code using the location.country match condition:
JavaScript
1import {or} from '@edgio/core';
2
3router.if(
4 or(
5 {
6 edgeControlCriteria: {
7 '===': [
8 {
9 location: 'country',
10 },
11 'XX',
12 ],
13 },
14 },
15 {
16 edgeControlCriteria: {
17 '===': [
18 {
19 location: 'country',
20 },
21 'XY',
22 ],
23 },
24 },
25 {
26 edgeControlCriteria: {
27 '===': [
28 {
29 location: 'country',
30 },
31 'XZ',
32 ],
33 },
34 }
35 ),
36 {
37 access: {
38 deny_access: true,
39 },
40 }
41);
Learn more about geolocation headers in the Request guide. For detailed information on complex rules, see Conditional Routes.

Blocking Search Engine Crawlers

If you need to block all search engine bot traffic to specific environments (such as your default or staging environment), the easiest way is to include the x-robots-tag header with the same directives you would otherwise set in a meta tag.
The search engine traffic is automatically blocked on Edgio edge links and permalinks as of Edgio v6.
If you would like to enable indexing on those links, you need to pass { indexPermalink: true } into the Router constructor in routes.js file:
JavaScript
1new Router({indexPermalink: true});
Otherwise, Edgio will match requests with the host header matching /edgio.link|edgio-perma.link/ and set a response header of x-robots-tag: noindex.
Additionally, you can customize this to block traffic to development or staging websites based on the host header of the request:
JavaScript
1router.get(
2 {
3 headers: {
4 // Regex to catch multiple hostnames
5 host: /dev.example.com|staging.example.com/,
6 },
7 },
8 {
9 headers: {
10 set_response_headers: {
11 'x-robots-tag': 'noindex',
12 },
13 },
14 }
15);
For other available directives, see Google Developer Central and Bing Webmaster Tools for lists of supported options.