Edgio

Edge Functions

Edge Functions enable you to execute a small piece of JavaScript code on our edge servers. This code can be used to modify requests and responses as well as make additional calls to your defined origins. The benefits of using Edge Functions include enhancing performance by reducing latency, improving user experience by personalizing content, and increasing security by managing authentication and redirection at the edge. They allow you to execute your code closer to users, reducing the need to go back to the original server and thus, provide faster services.
Key information:
  • This article assumes that you are familiar with our CDN-as-Code approach for defining rules.
  • Edge Functions requires activation. Contact your account manager or our sales department at 1 (866) 200 - 5463 to upgrade your account.

Prerequisites

Setup requires:

Install the Edgio CLI

If you have not already done so, install the Edgio CLI.
Bash
1npm i -g @edgio/cli@latest

Defining Edge Functions

An edge function is invoked when an incoming request matches a route that has an edge function assigned to it. Only a single edge function can be assigned to a route. If multiple routes match an incoming request, the edge function assigned to the last matching route is invoked.
Define an edge function by:
  • Storing your standalone JavaScript code as a file with a js file extension.
  • Setting an edge_function property within your routes.[js|ts]. Set this string property to the relative path to your edge function.
    JavaScript./routes.js
    1import {Router} from '@edgio/core/router';
    2export default new Router()
    3 .get('/', {
    4 edge_function: './edge-functions/index.js',
    5 })
    6 .match('/api/*', {
    7 edge_function: './edge-functions/api.js',
    8 });
An edge function file must export the following entry point:
JavaScript
1/**
2 * Handles an HTTP request and returns a response.
3 *
4 * @async
5 * @param {Request} request - Represents the incoming request.
6 * @param {Object} context - Provides additional information about the request and environment.
7 * @param {Object} context.client - The client's network information.
8 * @param {Object} context.device - The client's device capabilities.
9 * @param {Object} context.environmentVars - Environment variables as defined in the Developer Console.
10 * @param {Object} context.geo - The client's geo location.
11 * @param {Object} context.metrics - Provides functions for injecting metrics into your edge function.
12 * @param {Function} context.metrics.add - Adds a value to the metric with the given ID.
13 * @param {Function} context.metrics.startTimer - Starts a timer for the metric with the given ID. Only one timer can be active at a time for a given metric ID.
14 * @param {Function} context.metrics.stopTimer - Stops a timer for the metric with the given ID.
15 * @param {Object} context.origins - Origin servers as defined in the Edgio Console or the edgio.config.js file.
16 * @param {Object} context.requestVars - Information about this property including values set using Set Variables.
17 * @returns {Response | Promise<Response>}
18 */
19export async function handleHttpRequest(request, context) {
20 // ... function code ...
21}

Edge Function Initialization Script (Optional)

An edge function initialization script is a JavaScript file executed once before any edge function in a project is invoked. This script is particularly beneficial for projects with two or more edge functions, allowing for the setup of global variables, initialization of third-party libraries, and defining utility functions used across multiple edge functions. It reduces duplicate code setup and is specified within the routes.[js|ts] file. The script must adhere to specific execution and memory constraints, similar to the edge functions themselves.
To specify an edge function initialization script, add the edge_function_init_script property to the Router(...) constructor. The edge_function_init_script property accepts a string representing the path to the script.
JavaScript./routes.js
1import {Router} from '@edgio/core/router';
2
3export default new Router({
4 // Specify an edge function initialization script
5 edge_function_init_script: './edge-functions/init.js',
6}).get('/', {
7 edge_function: './edge-functions/main.js',
8});
The initialization script must export the following entry point:
JavaScript./edge-functions/init.js
1/**
2 * @async
3 * @param {Object} context - Provides additional information about the request and environment.
4 * @returns {void}
5 */
6export async function handleHttpInit(context) {
7 // ... function code ...
8}
Key information:
  • Execution Context: It’s important to note that certain parts of the context, like geo, client, and device, may not be relevant or populated during initialization as they are request-specific. The most relevant part of the context for initialization is context.environmentVars.
  • Execution Frequency and State Preservation: The initialization code runs upon the first request received for any edge function within the project/bundle. After execution, the state is saved as a memory snapshot for subsequent executions of the project’s edge functions. These snapshots are specific to each backend cache server and will be periodically evicted based on internal criteria. Upon eviction, handleHttpInit will execute again for the next request.
  • CPU/Memory Limitations: The initialization script shares the same CPU and memory limitations as the edge functions. This includes the execution time constraint (i.e., 50ms).
  • Use Cases: An initialization script is ideal for computationally expensive operations like compiling regex, which is beneficial to perform once rather than in every edge function execution. However, operations like fetch are not recommended in this phase due to potential persistence of fetched data beyond desired periods.
  • Consideration for Global State: Developers should be cautious when using the context to set up specific code or save data in the global scope during initialization, as it may persist and potentially lead to incorrect behavior across different requests.
The following sample code demonstrates how to set up a global variable within an edge function initialization script and access it within an edge function.
JavaScript./edge-functions/init.js
1export async function handleHttpInit(context) {
2 // Set up a global regular expression for matching URLs
3 global.urlRegex = new RegExp('https://example.com');
4}
JavaScript./edge-functions/main.js
1export async function handleHttpRequest(request, context) {
2 // Access the global variable
3 if (global.urlRegex.test(request.url)) {
4 // ... function code ...
5 }
6}

Edge Function Parameters

The edge function is passed two parameters: request and context.
request is an object representing the incoming request and context is a read-only object providing additional information about the request and your environment, such as access to the client’s network information, device capabilities, geo location, environment variables, origin servers, and information about this property including values set using variables. It also provides functions for injecting metrics into your edge function and for returning the response from your edge function to the downstream client.
ParameterTypeDescriptionReference
requestObjectRepresents the incoming requestRequest
contextObjectA read-only object providing additional information about the request and your environment
context.clientKey-value storeThe client’s network informationvirt_ variables in Feature Variables
context.deviceKey-value storeThe client’s device capabilitieswurfl_ variables in Feature Variables
context.environmentVarsKey-value storeEnvironment variables as defined in the Edgio Console (Property -> Environment —> Environment Variables)Environment Variables
context.geoKey-value storeThe client’s geo locationgeo_ variables in Feature Variables
context.metricsObjectProvides functions for injecting metrics into your edge functionEdge Insights - Access Logs
context.originsKey-value storeOrigin servers as defined in the Edgio Console (Property -> Environment -> Origins) or the edgio.config.js fileOrigin Configuration
context.requestVarsKey-value storeInformation about this property including values set using Set VariablesSet Variables
context.respondWith(response)Function
  • Edgio v7.2.3 or higher: Deprecated. See Responding to the Client.
  • Edgio v7.2.2 or lower: Must be called to return the response from your edge function to the downstream client.
context.respondWith(response)
context.waitUntil(promise)FunctionWaits until the given promise is fulfilledcontext.waitUntil(promise)

Metrics Functions

Inject up to 10 metrics into your edge function through context.metrics. Metric data is reported through Edge Insights when viewing log data for the Access Logs data source. View these metrics by clicking on an entry within the Logs section and then looking for Edge Function Customer Metric # log fields.
FunctionDescription
context.metrics.add(id: integer, value: integer)Adds a value to the metric with the given ID. Valid values for id are 0 - 9.
context.metrics.startTimer(id: integer)Starts a metric’s timer. Only a single timer can be active at any time for a given ID.
context.metrics.stopTimer(id: integer)Stops a metric’s timer.

Edge Function Namespace

Edge Functions global namespace provide access to the following:
Global Object/ClassDescriptionReference
console objectThe standard console object used to log messages to the console.Console Object
Headers ClassThe standard Headers class used to manipulate headers on requests and responses.Headers Class
Request ClassThe standard Request class used access the initial request on this route and to make new requests to the origin server.Request Class
Response ClassThe standard Response class used to access responses from the origin server and to create new downstream responsesResponse Class
fetch(request)A modified fetch() function used to makes requests to the origin server.Fetch API
TextDecoderPolyfill class to manage decoding text.TextDecoder
TextEncoderPolyfill class to manage encoding text.TextEncoder
URLPolyfill class to manage URLs.URL
URLSearchParamsPolyfill class to manage URL search parameters.URLSearchParams
parseURLFunction to parse URLs.parseURL

Request Class

Edge functions use a modified version of the standard Request API. See the Unsupported Methods and Properties section for more information.
Edge functions are passed a Request instance representing the incoming request. This object provides methods and properties for accessing the request’s headers, body, URL, and more.

Supported Methods and Properties

  • Headers: Access the request headers using request.headers.
  • Body: The request body can be read as:
    • ArrayBuffer: await request.arrayBuffer()
    • JSON: await request.json()
    • Text: await request.text()
  • Method: request.method to get the HTTP method of the request.
  • URL: request.url provides the full URL, and request.path gives the request path.
  • Cloning: To clone a request without its body, use request.cloneWithoutBody().

Unsupported Methods and Properties

The following properties and methods from the standard Request API are not supported:
  • request.blob()
  • request.cache
  • request.credentials
  • request.clone()
  • request.destination
  • request.formData()
  • request.integrity
  • request.mode
  • request.redirect
  • request.referrer
  • request.referrerPolicy
  • request.signal
Using an unsupported method or property will throw an error.

Response Class

Edge functions use a modified version of the standard Response API. See the Unsupported Methods and Properties section for more information.
Origin fetch requests and edge functions return a Response instance representing the response. This object provides methods and properties for accessing and setting the response’s headers, body, status code, and more. Create a response through the Response class or by calling the fetch() function. See the Edge Function Namespace section for more information.

Supported Methods and Properties

  • Headers: Access or modify the response headers using response.headers.
  • Body: The response body can be interacted with using:
    • ArrayBuffer: await response.arrayBuffer()
    • JSON: await response.json()
    • Text: await response.text()
  • Status: response.status to get the HTTP status code of the response. response.statusText provides the corresponding status text.
  • URL: response.url provides the URL of the response.
  • Redirected: response.redirected is a property that indicates whether the response is a result of a redirection.
    You may redirect a response up to 5 times before an exception is thrown.
  • Redirection: Create a redirected response using Response.redirect(url, status).
  • Cloning: To clone a response without its body, use response.cloneWithoutBody().

Unsupported Methods and Properties

The following properties and methods from the standard Response API are not supported:
  • response.blob()
  • response.clone()
  • response.formData()
  • response.type
Note: The above-mentioned unsupported methods and properties will throw an error if attempted to be used.

Responding to the Client

Edge functions must respond to the client by returning a Response object or a Promise that resolves to a Response object. The Response object can be created using the Response class or by calling the fetch() function. See the Edge Function Namespace section for more information.
JavaScript./edge-functions/example.js
1export async function handleHttpRequest(request, context) {
2 const defaultResponse = new Response('Hello World!');
3 const response = await fetch('https://your-server.com', {
4 edgio: {
5 // an origin name must be specified in the request
6 origin: 'web',
7 },
8 });
9
10 if (!response.ok) {
11 return defaultResponse;
12 }
13
14 return response;
15}
As of v7.2.3, the context.respondWith() function is deprecated. You must return a Response object or a Promise that resolves to a Response object to respond to the client.

Origin Requests Using fetch()

Before issuing a fetch request to an origin, you must define an origin configuration within the edgio.config.js file:
JavaScriptedgio.config.js
1module.exports = {
2 /* ... */
3 origins: [
4 {
5 // The name of the backend origin
6 name: 'web',
7
8 // Uncomment the following to override the host header sent from the browser when connecting to the origin
9 // override_host_header: 'example.com',
10
11 // The list of origin hosts to which to connect
12 hosts: [
13 {
14 // The domain name or IP address of the origin server
15 location: 'your-server.com',
16 },
17 ],
18
19 // Uncomment the following to configure a shield
20 // shields: { us_east: 'DCD' },
21 },
22 ],
23};
Learn more about origin configuration in our CDN-as-Code guide.
Request a resource from an origin by passing two required arguments to the fetch() function. Set the first argument to a URL or a Request object. Set the second argument to the name of the origin and any additional options compatible with the fetch() function.
JavaScript./edge-functions/example.js
1export async function handleHttpRequest(request, context) {
2 const resp = await fetch('https://your-server.com', {
3 // an Edgio origin must be specified in the request
4 edgio: {
5 origin: 'web',
6 },
7 });
8
9 // handle the response as needed
10 /* ... */
11
12 // return the response to the client
13 return resp;
14}
Create a reusable fetch() function by defining a utility function such as createFetchForOrigin(). See the Polyfills and Helpers section for more information.
JavaScript./edge-functions/example.js
1export async function handleHttpRequest(request, context) {
2 const fetch = createFetchForOrigin('web');
3
4 const resp = await fetch('https://your-server.com');
5
6 // handle the response as needed
7 /* ... */
8
9 // return the response to the client
10 return resp;
11}
Some libraries allow you to specify a fetch() function to use. For example, PlanetScale’s database driver configuration accepts a custom function to use when making requests to the API.
JavaScript./edge-functions/example.js
1import {connect} from '@planetscale/database';
2import {createFetchForOrigin} from './polyfills';
3
4const fetch = createFetchForOrigin('planetscale');
5
6export async function handleHttpRequest(request, context) {
7 const env = context.environmentVars;
8
9 const config = {
10 host: env.PLANETSCALE_HOST,
11 username: env.PLANETSCALE_USERNAME,
12 password: env.PLANETSCALE_PASSWORD,
13 fetch,
14 };
15
16 const conn = connect(config);
17
18 // make a request to the PlanetScale API
19 /* ... */
20
21 // return the response to the client
22 return resp;
23}
This approach allows for creating unique fetch() functions for each origin server. Optionally, you can override the global fetch() function if you are unable to specify a fetch() function in your library.
JavaScript./edge-functions/example.js
1import createFetchForOrigin from './polyfills';
2
3export async function handleHttpRequest(request, context) {
4 const fetch = createFetchForOrigin('api');
5
6 // override the global fetch() function
7 global.fetch = fetch;
8
9 // make a request to the your API
10 /* ... */
11
12 // return the response to the client
13 return resp;
14}

Caching fetch() Requests

See the Edge Function Caching guide for more information on caching origin fetch requests.

Compressed Responses

Edge functions do not automatically decompress responses from the origin server, specifically when those responses are encoded with compression methods like gzip or br. This characteristic becomes relevant if you need to manipulate the response body before sending it to the client, as decompression would be a necessary step in processing the data. Responses that are merely passed through from the origin to the client without modification are not impacted by this behavior.
If your edge function manipulates the response body of a fetch request, we recommend that you disable compression by setting the Accept-Encoding request header. This ensures the response from the origin is not compressed, making it directly accessible for manipulation. The following sample code demonstrates how to disable compression on a fetch request:
JavaScript./edge-functions/example.js
1async function handleRequest(request) {
2 // Modify the request to disallow compressed responses
3 const modifiedRequest = new Request(request, {
4 headers: new Headers({
5 ...request.headers,
6 'Accept-Encoding': 'identity', // Disallow compression methods like gzip or br
7 }),
8 });
9
10 // Fetch the response from the origin server
11 const response = await fetch(modifiedRequest);
12
13 // Assuming the response needs to be manipulated
14 let responseBody = await response.text(); // Get the response body as text
15 responseBody = responseBody.replace('foo', 'bar'); // Manipulate the response body
16
17 // Return the manipulated response to the client
18 return new Response(responseBody, response);
19}

Fetch Limitations

Response status codes of fetch() requests must:
  • Be greater than or equal to 200.
  • Be less than or equal to 599.
  • Not be 204 or 304.
If the response status code does not meet these criteria, the edge function will throw an error. It may be necessary to remove specific cache directives from the request before sending it to the origin.
The following sample code strips the cache directive headers from the request before sending it to the origin:
JavaScript./edge-functions/example.js
1export async function handleHttpRequest(request) {
2 /* ... */
3
4 // Remove headers that could cause the a response status of 204/304 to be returned.
5 const headersToRemove = [
6 'etag',
7 'if-modified-since',
8 'if-none-match',
9 'last-modified',
10 ];
11 headersToRemove.forEach((header) => request.headers.delete(header));
12
13 const response = await fetch(request.url, {
14 edgio: {
15 origin: 'web',
16 },
17 method: request.method,
18 headers: request.headers,
19 });
20
21 // handle the response as needed
22
23 return response;
24}

Testing Locally

You may run Edgio in local development mode to preview your website on your local machine prior to deployment. Local development mode allows for rapid development by letting you to quickly test changes prior to deployment.
  1. From the command line or terminal, type edgio dev.
  2. Preview your website by loading https://127.0.0.1:3000 from within your preferred web browser.
Note that edge functions executed in local development mode are simulated and may not reflect the behavior or performance of deployed edge functions.

Deploying Your Property

Evaluate site performance and QA functionality by deploying your property to Edgio. Run the following command from your property’s root directory:
Bash
1edgio deploy
Note that Edge Functions must be enabled for your Edgio Console team in order to deploy your property. Contact support to enable this feature.

Limitations of Edge Functions

Edge functions are only compatible with edge links. Permalinks are unsupported since they bypass the edge network and serve content directly from the origin. This behavior causes either degraded functionality or prevents edge function logic from being applied.
When deploying your project, it’s important to distinguish between the edge link and the permalink for proper testing and functionality verification. The following screenshot indicates where you can find the permalink and edge link for your project in the Edgio Console.
Permalink and Edge Link in Edgio Console
Edge Function Limitations:
  • Wall-time Timeout: 60s
  • CPU Time Limit: 50ms
  • Runtime Memory Limit: 4MB
  • Code Package Size Limit: 512KB
The code package size refers to the compiled JavaScript bytecode. All edge functions are bundled into a single package for deployment to our edge servers. If the total compiled size exceeds 512KB, the deployment will fail and return a 400 error:
plaintext
12023-08-14T17:37:04Z - error - external - Schema validation error: properties.0.edge_functions.quickjs_bytecode_base64 exceeds maximum size. Max: 512000, got: 1514469
22023-08-14T17:37:04Z - error - external - Error: the server responded with status 400
Runtime memory encompasses all variables, HTTP requests and responses, as well as the context object. This total must not exceed 4MB.
Edge functions are confined to 50ms of CPU time and a maximum of 60s for overall execution. Time spent waiting for a response from an origin server is not counted against the 50ms CPU time limit.

Polyfills and Helpers

It’s important to note that edge functions are not Node.js functions. Your code or third-party libraries may not work as expected if they are referencing Node.js specific APIs (e.g. Buffer, process, etc). Because of this, we recommend using polyfills when needed. Below are some examples of polyfills you can use in your edge functions.
  • Buffer API
    JavaScript
    1// 'buffer' polyfill is provided by the Edgio CLI
    2global.Buffer = require('buffer').Buffer;
    3
    4global.btoa = function (str) {
    5 return Buffer.from(str, 'binary').toString('base64');
    6};
    7
    8global.atob = function (b64Encoded) {
    9 return Buffer.from(b64Encoded, 'base64').toString('binary');
    10};
  • process.env Namespace
    This namespace is not globally available in edge functions. You can define the following polyfill to set environment variables from the context object.
    JavaScript
    1/**
    2 * Define a polyfill for 'process'
    3 */
    4global.process = global.process || {env: {}};
    5
    6/**
    7 * Sets environment variables from a given context.
    8 *
    9 * @param {Object} context - The context object containing environment variables.
    10 * @param {Object} context.environmentVars - Key-value pairs of environment variables.
    11 */
    12export function setEnvFromContext({environmentVars}) {
    13 Object.assign(process.env, environmentVars);
    14}
  • URL Class
    This class, provided by the Edgio CLI, is compatible with the standard URL class.
    JavaScript
    1let url = new URL('https://www.url.com/path');
    2url.port = 8080;
    3let newUrl = url.toString(); // newUrl will equal 'https://www.url.com:8080/path'
    JavaScript
    1let url = new URL('/patha/pathb', 'https://www.url.com/');
    2let newUrl = url.toString(); // newUrl will equal 'https://www.url.com/patha/pathb'
  • URLSearchParams Class
    This class, provided by the Edgio CLI, is compatible with the standard URLSearchParams class.
    JavaScript
    1let url = new URL('https://www.url.com/path?a=b');
    2let params = url.searchParams;
    3params.set('c', 'one');
    4let newUrl = url.toString(); // newUrl will equal 'https://www.url.com/path?a=b&c=one'

Polyfill Limitations

It’s worth noting that not all implementations will be able to accept polyfills, either due to the number of dependencies affected or the compiled size of the polyfill exceeding the Limitations of Edge Functions.

Helper Functions

  • createFetchForOrigin for the fetch() API
    This function returns a modified fetch() function that includes the origin server. This is useful for making multiple requests to the same origin or overriding the global function.
    Some third-party libraries let you specify a fetch() function. If you are unable to set this in your library, you can override the global one using this helper. See the Origin Requests Using fetch() section for more details.
    JavaScript
    1/**
    2 * Creates a fetch function with an additional 'edgio' option to specify the origin. Example usage:
    3 *
    4 * // create a fetch function for the 'web' origin.
    5 * const fetch = createFetchForOrigin('web');
    6 * const response = await fetch('https://your-server.com');
    7 *
    8 * // override the global fetch() function
    9 * global.fetch = createFetchForOrigin('web');;
    10 *
    11 * @param {string} originName - The origin name defined in edgio.config.js.
    12 * @returns {function} - A modified fetch function.
    13 * @throws {Error} If the origin name is not provided.
    14 */
    15export default function createFetchForOrigin(originName) {
    16 if (!originName) {
    17 throw new Error(
    18 "'originName' is required and must be a name defined in edgio.config.js"
    19 );
    20 }
    21
    22 return (url, options = {}) => {
    23 const modifiedOptions = {
    24 ...options,
    25 edgio: {
    26 origin: originName,
    27 },
    28 };
    29 return fetch(url, modifiedOptions);
    30 };
    31}
  • parseURL(url: string)
    This function parses a URL string and returns an object containing the URL’s components.
    JavaScript
    1// Show all the possible values
    2parseURL('http://user:password@www.url.com:8080/one/two?a=b#hash')
    3{
    4 fragment: 'hash',
    5 host: 'www.url.com',
    6 password: 'password',
    7 path: [ 'one', 'two' ],
    8 port: 8080,
    9 query: 'a=b',
    10 scheme: 'http',
    11 username: 'user'
    12}
    13
    14// Show the minimum required values
    15parseURL('https://url.com/')
    16{
    17 fragment: null,
    18 host: 'url.com',
    19 password: '',
    20 path: [ '' ],
    21 port: null,
    22 query: null,
    23 scheme: 'https',
    24 username: ''
    25}
    26
    27// Returns null on empty string or string with bad url
    28parseURL('url.com')
    29null
    30
    31// Throws on non-string argument
    32parseURL() // will throw

Edge Function Examples