Upgrading from Edgio Applications version 6 or earlier may require significant changes to your CDN-as-code configuration as certain core legacy components have limited support. View our upgrading guide.
The
@edgio/core
package provides a JavaScript API for controlling routing and caching from your code base rather than a CDN web portal. Using this EdgeJS approach allows this vital routing logic to be properly tested, reviewed, and version controlled, just like the rest of your application code.Using the Router, you can:
- Proxy requests to upstream sites
- Send redirects from the network edge
- Render responses on the server using Next.js and Nuxt.js
- Alter request and response headers
- Send synthetic responses
Prerequisites
Before proceeding, you will need an Edgio property. Create one now if you do not already have one.
Configuration
Define routes within the
routes.[js|ts]
file. This file should export an instance of @edgio/core/router/Router
:JavaScript./routes.js
1import {Router} from '@edgio/core';23export default new Router();
By default, our CLI automatically creates
routes.js
and edgio.config.js
upon initializing a property (edgio init
). If your web application supports TypeScript and it uses a framework for which we have a TypeScript implementation, then our CLI will create routes.ts
instead of routes.js
.Declare Routes
Declare routes using the method corresponding to the HTTP method you want to match. All HTTP methods are available:
- get
- put
- post
- patch
- delete
- head
To match all methods, use
match
:JavaScript./routes.js
1router.match('/:path*', {});
Route Pattern Syntax
The syntax for route paths is provided by path-to-regexp, which is the same library used by Express.
Named Parameters
Named parameters are defined by prefixing a colon to the parameter name (
:foo
).JavaScript
1router.get('/:foo/:bar', {2 /* ... */3});
Please note: Parameter names must use “word characters” (
[A-Za-z0-9_]
).Custom Matching Parameters
Parameters can have a custom regexp, which overrides the default match (
[^/]+
). For example, you can match digits or names in a path:JavaScript
1router.get('/icon-:foo(\\d+).png', {2 /* ... */3});
Tip: Backslashes need to be escaped with another backslash in JavaScript strings.
Custom Prefix and Suffix
Parameters can be wrapped in
{}
to create custom prefixes or suffixes for your segment:JavaScript
1router.get('/:attr1?{-:attr2}?{-:attr3}?', {2 /* ... */3});
Unnamed Parameters
It is possible to write an unnamed parameter that only consists of a regexp. It works the same the named parameter, except it will be numerically indexed:
JavaScript
1router.get('/:foo/(.*)', {2 /* ... */3});
Modifiers
Modifiers must be placed after the parameter (e.g.
/:foo?
, /(test)?
, /:foo(test)?
, or {-:foo(test)}?
).Optional
Parameters can be suffixed with a question mark (
?
) to make the parameter optional.JavaScript
1router.get('/:foo/:bar?', {2 /* ... */3});
Tip: The prefix is also optional, escape the prefix
\/
to make it required.Zero or More
Parameters can be suffixed with an asterisk (
*
) to denote zero or more parameter matches.JavaScript
1router.get('/:foo*', {2 /* ... */3});
The captured parameter value will be provided as an array.
One or More
Parameters can be suffixed with a plus sign (
+
) to denote one or more parameter matches.JavaScript
1router.get('/:foo+', {2 /*... */3});
The captured parameter value will be provided as an array.
Matching Method, Query Parameters, Cookies, and Headers
Match can either take a URL path, or an object which allows you to match based on method, query parameters, cookies, or request headers:
JavaScript
1router.match(2 {3 path: '/some-path', // value is route-pattern syntax4 method: /GET|POST/i, // value is a regular expression5 cookies: {currency: /^(usd)$/i}, // keys are cookie names, values are regular expressions6 headers: {'x-moov-device': /^desktop$/i}, // keys are header names, values are regular expressions7 query: {page: /^(1|2|3)$/}, // keys are query parameter names, values are regular expressions8 },9 {10 /* ... */11 }12);
Negated Route Matching
Previously, we showed how to match requests based on path, method, query parameters, cookies, and request headers. You can also negate these matches by specifying a
not
key in the object passed to your route criteria. For example, the following route matches all requests whose relative path does not match /some-path
:JavaScript
1router.match(2 {3 path: {4 not: '/some-path',5 },6 },7 {8 caching: {9 max_age: '1d',10 },11 }12);
Similarly, you can negate matches based on method, query parameters, cookies, and request headers:
JavaScript
1router.match(2 {3 path: '/some-path',4 query: {5 page: {6 not: /^(1|2|3)$/,7 },8 },9 method: {10 not: /POST/i,11 },12 cookies: {13 currency: {14 not: /^(usd)$/i,15 },16 },17 headers: {18 'x-device': {19 not: /^desktop$/i,20 },21 },22 },23 {24 caching: {25 max_age: '1d',26 },27 }28);
This example matches all requests to
/some-path
except for those with query parameter page=1|2|3
Exact Path, Inclusive, and Regular Expression Matching
As described in Route Pattern Syntax, this type of route matching is based on path-to-regexp. While this is a rather universal approach to matching requests, Edgio provides additional options for matching requests.
Exact Path Matching
Exact path matching, also known as strict matching, gives you precise control over how requests are matched. Traditionally, you may match
/some-path
with the following route:JavaScript
1router.match('/some-path', {2 /* ... */3});
This will match
/some-path
, /Some-Path
, and other variations in between that are case-insensitive. However, using exact
will use strict comparison in matching the request path. The following example shows how to import the exact
function and use it to match requests to /some-path
:JavaScript
1import {Router, exact} from '@edgio/core';23const router = new Router();45router.match(exact('/some-path'), {6 /* ... */7});89export default router;
This matches the path literally, so
/some-path
will match, but /Some-Path
will not.Inclusive Matching
Inclusive matching uses the
InOperatorValues
type for matching a generic array of values. To use this, you must specify the argument as a RouteCriteria
type for the path
you would like to match against. This type of matching is similar to exact
matching in that it uses strict comparison.For example, the following route matches requests to
/some-path
and /another-path
, but not /Some-Path
or /Another-Path
:JavaScript
1router.match(2 {3 path: ['/some-path', '/another-path'],4 },5 {6 /* ... */7 }8);
Regular Expression Matching
For complex routes that cannot be easily matched using
path-to-regexp
, you can use regular expressions to match requests. For example, the following route matches requests to /some-path
and /another-path
, but not /Some-Path
or /Another-Path
:JavaScript
1router.match(2 {3 path: /^(\/some-path|\/another-path)$/i,4 },5 {6 /* ... */7 }8);
You may also use Negated Route Matching with regular expressions:
JavaScript
1router.match(2 {3 path: {4 not: /^(\/some-path|\/another-path)$/i,5 },6 },7 {8 /* ... */9 }10);
Regular expression matching is also available for matching query parameters, cookies, and request headers, and more. Any property of
RouteCriteria
that accepts CriteriaValue
or OptionalCriteriaValue
types can use a regular expression and negation.Request Handling
The second argument to routes is a function that receives a
Features
type and uses it to send a response, such as:-
Proxy a backend configured in
edgio.config.js
-
Serve a static file
-
Send a redirect
-
Cache the response at edge and in the browser
-
Manipulate request and response headers
For example, to cache a response for requests to
/hello-world
:JavaScript
1router.get('/hello-world', {2 caching: {3 max_age: '1d',4 },5});
Route Execution
When Edgio receives a request, it executes each route that matches the request in the order in which they are declared until one sends a response.
Multiple routes can therefore be executed for a given request. A common pattern is to add caching with one route and render the response with a later one using middleware. In the following example we cache then render a response with Next.js:
JavaScript
1import {Router} from '@edgio/core';2import {nextRoutes} from '@edgio/next';34// In this example a request to /products/1 will be cached by the first route, then served by the `nextRoutes` middleware5export default new Router()6 .get('/products/:id', {7 caching: {max_age: {200: '1h'}, stale_while_revalidate: '1h'},8 })9 .use(nextRoutes);
Alter Requests and Responses
Edgio offers APIs to manipulate request and response headers and cookies. The APIs are:
Operation | Request | Response sent to Browser |
---|---|---|
Add header | set_request_headers | add_response_headers |
Add cookie | * | * |
Update header | set_request_headers | set_response_headers |
Update cookie | * | * |
Remove header | set_request_headers | remove_response_headers remove_origin_response_headers |
Remove cookie | * | * |
*
Adding, updating, or removing request cookies can be achieved with set_request_headers
applied to cookie
header. Similarly, adding, updating, or removing response cookies can be achieved with set_response_headers
applied to set-cookie
header.Request / Response Variables
You can inject values into the request or response via cache key rewrite, headers, cookies, URL redirect and rewrite as template literals using the
%{<FEATURE VALUE>}
format.Feature Variable | Description |
---|---|
%{arg_<QUERY STRING PARAMETER>} | Returns the value corresponding to the query string parameter identified by the <QUERY STRING PARAMETER> term. |
%{cookie_<COOKIE>} | Returns the value corresponding to the cookie identified by the <COOKIE> term. |
%{host} | Indicates the host defined in the request URL. |
%{http_<REQUEST HEADER>} | Returns the value corresponding to the request header identified by the <REQUEST HEADER> term. |
%{normalized_path} | Indicates the normalized relative path for the request submitted to the CDN. |
%{normalized_query} | Indicates the normalized query string defined in the request URL. |
%{normalized_uri} | Indicates the normalized relative path and query string for the request submitted to the CDN. |
%{path} | Indicates the relative path to the requested content. This relative path reflects URL rewrites due to url_rewrite . |
%{query_string} | Indicates the entire query string value defined in the request URL. |
%{referring_domain} | Indicates the domain defined in the Referer request header. |
%{request} | Describes the request. |
%{request_method} | Indicates the HTTP request method. |
%{request_protocol} | Indicates the request protocol used by an edge server to proxy the request. |
%{request_uri} | Indicates the relative path, including the query string, defined in the request URI. |
%{resp_<RESPONSE HEADER>} | Returns the value corresponding to the response header identified by the <RESPONSE HEADER> term. |
%{status} | Indicates the HTTP status code for the response. |
Example
This example shows how you would add an
original-request-path
response header for all requests whose value is the request path:JavaScript
1router.match(2 {},3 {4 headers: {5 set_response_header: {6 'original-request-path': '%{path}',7 },8 },9 }10);
For a comprehensive list of variables, see the Feature Variables guide.
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.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 hostnames5 host: /dev.example.com|staging.example.com/,6 },7 },8 {9 headers: {10 set_response_headers: {11 'x-robots-tag': 'noindex, nofollow',12 },13 },14 }15);
Full Example
This example shows typical usage of
@edgio/core
, including serving a service worker, Next.js routes (vanity and conventional routes), and falling back to a legacy backend.JavaScript🗄️ routes.js
1import { Router } from "@edgio/core";23export default new Router()4 .get("/:path*", {5 caching: { max_age: "30758400s", bypass_client_cache: true },6 url: {7 url_rewrite: [8 {9 source: "/service-worker.js",10 syntax: "path-to-regexp",11 destination: "/dist/service-worker.js",12 },13 ],14 },15 headers: { set_request_headers: { "x-edg-serverless-hint": "" } },16 origin: { set_origin: "edgio_static" },17 })18 .get("/:path*", {19 caching: {20 max_age: "3600s",21 stale_while_revalidate: "3600s",22 service_worker_max_age: 3600,23 bypass_client_cache: true,24 },25 headers: { set_response_headers: { "x-sw-cache-control": "max-age=3600" } },26 origin: { set_origin: "origin" },27 })28 .match("/:path*", { origin: { set_origin: "origin" } });