This guide gives examples of common routing patterns using Edgio.
Proxying an Origin
Same Path
To forward a request to the same path on one of the backends listed in
layer0.config.js
, use the proxy
method of ResponseWriter
:JavaScript
1router.get('/some-path', ({ proxy }) => {2 proxy('origin')3})
The first argument corresponds to the name of a backend in
layer0.config.js
. For example:JavaScript
1module.exports = {2 backends: {3 origin: {4 domainOrIp: 'my-shop.example.com',5 hostHeader: 'my-shop.example.com',6 },7 },8}
Different Path
To forward the request to a different path, use the
path
option of the ProxyOptions
interface:JavaScript
1router.get('/products/:productId', ({ proxy }) => {2 proxy('origin', { path: '/p/:productId' })3})
Adding Caching
To cache proxied requests at the edge, use the
cache
method.JavaScript
1router.get('/products/:productId', ({ cache, proxy }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24 // keep entries in the cache for 24 hours5 staleWhileRevalidateSeconds: 60 * 60 // when a cached page is older than 24 hours, serve it one more time6 // for up to 60 minutes while fetching a new version from the origin7 }8 })9 proxy('origin')10})
Altering the Request
You can alter request headers when forwarding a request to a backend:
JavaScript
1router.get(2 '/products/:productId',3 ({ setRequestHeader, updateRequestHeader, removeRequestHeader, proxy }) => {4 setRequestHeader('header-name', 'header-value')5 updateRequestHeader('header-name', /some-.*-part/gi, 'some-replacement')6 removeRequestHeader('header-name')7 proxy('origin')8 },9)
The above example makes use of
setRequestHeader
, updateRequestHeader
, and removeRequestHeader
API calls.Altering the Response
You can also alter the response before and after the cache:
JavaScript
1router.get(2 '/products/:productId',3 ({4 setUpstreamResponseHeader,5 setResponseHeader,6 removeResponseHeader,7 removeUpstreamResponseHeader,8 updateResponseHeader9 updateUpstreamResponseHeader10 proxy,11 }) => {12 proxy('origin')1314 // applied before the cache15 setUpstreamResponseHeader('header-name', 'header-value')16 updateUpstreamResponseHeader('header-name', /some-.*-part/gi, 'some-replacement')17 removeUpstreamResponseHeader('header-name')1819 // applied after the cache20 setResponseHeader('header-name', 'header-value')21 updateResponseHeader('header-name', /some-.*-part/gi, 'some-replacement')22 removeResponseHeader('header-name')23 },24)
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
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 ({ setResponseHeader }) => {9 setResponseHeader('x-debug-xff', '${req:x-forwarded-for}')10 },11)12// The rest of your router...
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.
Manipulating Cookies
You can manipulate cookies before they are sent to the browser using cookie response API calls like
addResponseCookie
:JavaScript
1router.get('/some/path', ({2 addUpstreamResponseCookie,3 addResponseCookie,4 removeResponseCookie,5 removeUpstreamResponseCookie,6 updateResponseCookie7 updateUpstreamResponseCookie,8 proxy9}) => {10 proxy('origin')1112 // applied before the cache13 addUpstreamResponseCookie('cookie-to-add', 'cookie-value')14 removeUpstreamResponseCookie('cookie-to-remove')15 updateUpstreamResponseCookie('cookie-to-alter', /Domain=.+;/, 'Domain=mydomain.com;')1617 // applied after the cache18 addResponseCookie('cookie-to-add', 'cookie-value')19 removeResponseCookie('cookie-to-remove')20 updateResponseCookie('cookie-to-alter', /Domain=.+;/, 'Domain=mydomain.com;')21})
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
1router.get('/some/path', ({ addUpstreamResponseCookie, addResponseCookie, proxy }) => {2 proxy('origin')34 addUpstreamResponseCookie('cookie-to-add', 'cookie-value', {5 domain: 'test.com',6 })78 addResponseCookie('cookie-to-add', 'cookie-value', { 'max-age': 50000 })9})
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
1router2 .match(3 {4 path: '/:path*',5 headers: {6 host: 'yoursite.c1',7 },8 },9 ({ proxy }) => {10 proxy('country1-backend')11 },12 )13 .match(14 {15 path: '/:path*',16 headers: {17 host: 'yoursite.c2',18 },19 },20 ({ proxy }) => {21 proxy('country2-backend')22 },23 )24 .match(25 {26 path: '/:path*',27 },28 ({ proxy }) => {29 proxy('everybody-else-backend')30 },31 )
Serving a Static File
To serve a specific file use the
serveStatic
API:JavaScript
1router.get('/favicon.ico', ({ serveStatic, cache }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache at the edge for 24 hours5 },6 browser: {7 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours8 },9 })10 serveStatic('assets/favicon.ico') // path is relative to the root of your project11})
Serving Static Files From a Directory
Here’s an example that serves all requests by sending the corresponding file in the
public
directoryJavaScript
1router.get('/:path*', ({ serveStatic, cache }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache at the edge for 24 hours5 },6 browser: false, // prevent caching of stale html in the browser7 })8 serveStatic('public/:path*')9})
Routing to Serverless
If your request needs to be run on the serverless tier, you can use the
renderWithApp
handler to render your result using your application. Use this method to respond with an SSR or API result from your application.Example using the
renderWithApp
handler:JavaScript
1router.get('/some/:path*', ({ renderWithApp, cache }) => {2 cache(CACHE_PAGES)3 renderWithApp()4})
Falling Back to Server-side Rendering
If you render some but not all paths for a given route at build time, you can fall back to server side rendering using the
onNotFound
option. Add the loadingPage
option to display a loading page while server-side rendering is in progress.JavaScript
1router.get('/products/:id', ({ serveStatic, cache, renderWithApp }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache at the edge for 24 hours5 },6 })7 serveStatic('dist/products/:id.html', {8 onNotFound: () => renderWithApp(),9 loadingPage: 'dist/products/loading.html',10 })11})
This hybrid of static and dynamic rendering was first introduced in Next.js as Incremental Static Generation (ISG). In Next.js apps, developers enable this behavior by returning
fallback: true
from
getStaticPaths()
. The @layer0/next
package automatically configures the routes for ISG pages to use onNotFound
and loadingPage
.Returning a Custom 404 Page
When a request matches a route with
serveStatic
, but no matching static asset exists, you can serve a custom 404 page using the onNotFound
option.JavaScript
1router.get('/products/:id', ({ serveStatic, cache }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache at the edge for 24 hours5 },6 })7 serveStatic('dist/products/:id.html', {8 onNotFound: async () => {9 await serveStatic('/products/not-found.html', {10 statusCode: 404,11 statusMessage: 'Not Found',12 })13 },14 })15})
Responding with a String Response Body
To respond with a simple, constant string as the response body use the
send
method:JavaScript
1router.get('/some-path', ({ cache, setResponseHeader, send }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours5 },6 })7 setResponseHeader('Content-Type', 'text/html')8 send(`9 <!doctype html>10 <html>11 <body>Hello World</body>12 </html>13 `)14})
To compute a dynamic response use the
compute
method:JavaScript
1router.get('/hello/:name', ({ cache, setResponseHeader, compute, send }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours5 },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
redirect
API:JavaScript
1router.get('/p/:productId', ({ redirect }) => {2 return redirect('/products/:productId', 301) // overrides the default status of 302 (Temporary Redirect)3})
If you need to compute the destination with sophisticated logic:
JavaScript
1router.get('/p/:productId', ({ redirect, compute, cache }) => {2 cache({3 edge: {4 maxAgeSeconds: 60 * 60 * 24, // cache for 24 hours5 },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.com2router.match({ headers: { host: /^(?!www\.).*$/ } }, ({ redirect }) => {3 redirect('https://www.mydomain.com${path}')4})56// Redirect all traffic from www.domain.com to domain.com7router.match({ headers: { host: /^(www\.).*$/ } }, ({ redirect }) => {8 redirect('https://domain.com${path}')9})
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 geolocation header:
JavaScript
1router.get(2 {3 headers: {4 'x-0-geo-country-code': /XX|XY|XZ/, // Regex matching two-letter country codes of the countries you want to block5 },6 },7 ({ send }) => {8 send('Blocked', 403)9 },10)
You can find more about geolocation headers here.
Allowing Specific IPs
If you need to block all traffic except requests that originate from specific IP addresses, you can do so by matching requests by the x-0-client-ip header:
JavaScript
1router.get(2 {3 headers: {4 // Regex that will do a negative lookahead for IPs you want to allow.5 // In this example 172.16.16.0/24 and 10.10.10.3/32 will be allowed and everything else will receive a 4036 'x-0-client-ip': /\b((?!172\.16\.16)(?!10.10.10.3)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/,7 },8 },9 ({ send }) => {10 send('Blocked', 403)11 },12)
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.To block search engine traffic for Edgio edge links and permalinks, you can use the built-in
.noIndexPermalink()
call on the router:JavaScript
1router.noIndexPermalink()
This will match requests with the
host
header matching /layer0.link|layer0-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
1router2 .noIndexPermalink()3 .get(4 {5 headers: {6 // Regex to catch multiple hostnames7 host: /dev.example.com|staging.example.com/,8 },9 },10 ({ setResponseHeader }) => {11 setResponseHeader('x-robots-tag', 'noindex')12 },13 )
For other available directives, see Google Developer Central and Bing Webmaster Tools for lists of supported options.