Edgio
Edgio

Sites (Frameworks)

Edgio Sites allows you to quickly integrate your headless Jamstack applications through one of our framework integrations. This integration allows Edgio to further improve your website’s performance through the server-side rendering (SSR) of your website’s JavaScript.

Availablity

Edgio has multiple global regions in which it will automatically provision the following:

  • Compute resources that run your code in two geographically close but entirely separate data centers.

    Edgio provides high availability for all of its components. You can choose a particular region of the world in which your API servers are located. Edgio will provision two data centers closest to it, connecting them with automatic DNS failover. High availability is also provided within the data centers themselves, with all individual processes being (at least) duplicated and sharing the traffic load among themselves. This architecture minimizes the chances of traffic loss.

  • A level 2 cache to maximize the global cache hit rate and reduce traffic to your servers.

Caveats

NodeJS native extensions

In a lot of scenarios, NodeJS native extensions might be required to perform specific tasks related to your application. For example, you might need to use OpenCV to perform some checks on an image before making it publicly available. Or you might need to use extensions like node-microtime for finer-grained performance analysis.

When Edgio bundles your application for deployment, we also do some “tree-shaking” to remove unnecessary files in your build. This makes the bundle size smaller and more efficient to load on our serverless platform during a cold-start. But it could have unintended consequences where we might strip away native extension binaries required for your application to function.

If that is the case, you might encounter an error like the following when trying to use modules that depend on the native binaries.

1Error: No native build was found for runtime=node abi=83 platform=linuxglibc arch=x64
2at Function.load.path (/var/task/node_modules/microtime/node_modules/node-gyp-build/index.js:59:9)
3at load (/var/task/node_modules/microtime/node_modules/node-gyp-build/index.js:19:30)
4at Object.<anonymous> (/var/task/node_modules/microtime/index.js:1:43)
5at Module._compile (internal/modules/cjs/loader.js:1085:14)
6at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
7at Module.load (internal/modules/cjs/loader.js:950:32)
8at Function.Module._load (internal/modules/cjs/loader.js:790:12)
9at Module.require (internal/modules/cjs/loader.js:974:19)
10at require (internal/modules/cjs/helpers.js:101:18)
11at Object.<anonymous> (/var/task/node_modules/broadcast-channel/dist/es5node/methods/node.js:57:41)

To fix this issue, you need to instruct Edgio to include the binary files that your application requires. This can be done by using the includeFiles property in edgio.config.js like so:

JavaScript
1includeFiles: {
2 'node_modules/microtime/**/*': true,
3},

Or you could choose to bundle everything in the packages listed in the dependencies property of package.json by using includeNodeModules property.

Readonly filesystem in serverless runtime

Web developers often use the filesystem as a temporary data source for their applications. That includes creating and/or manipulating files based on user requests. For example, storing user uploaded files locally and stripping metadata before proceeding. But this can open up security vulnerabilities where a bug in the application can be used to modify the application itself.

So, as a best practice Edgio Sites does not allow you to change the content of application files on the filesystem during runtime. If you need to modify an application file, you must make those changes locally and make a new deployment. This limits the attack surface of your potential application vulnerabilities. It also allows us to make your application more distributed and resilient to outages. Edgio takes your application code and deploys it to multiple regions with a read-only filesystem. This way, if the primary availability zone or region is unavailable, your application will still be accessible from another region.

Edgio Sites runs your application in /var/task directory. If you attempt to write a file in that directory, you may come across an error like the following:

1EROFS: read-only file system, open '/var/task/temp-upload.jpg'

To resolve issues like this you can use “tmp” directory to store any temporary files. But this directory might be different on you local environment vs Edgio serverless runtime. So, following is a good way to write code that will work on your local machine as well as Edgio serverless runtime.

JavaScript
1import { tmpdir } from 'os';
2import * as path from 'path';
3const tmpFilePath = path.join(tmpdir(), 'temp-upload.jpg');

Another thing to keep in mind is that “tmp” directory is ephemeral, meaning that it gets reset/recycled. If you store a file in “tmp”, it most likely won’t be available in the next request. That’s why you’ll need to use external services to store permanent file storage. These external services can be Amazon S3, Google Cloud Storage, or any other storage.

Serverless Bundle Size Limitation

Edgio has a serverless bundle limit for your project of 50 MB (250 MB uncompressed). If your deployment to Edgio fails due to exceeding the bundle limit, you will see the following error message:

12022-08-08T13:47:13Z - internal error - Error in xdn-deploy-lambda: Your production build exceeds the maximum allowed size of 50 MB (compressed) / 250 MB (uncompressed).
2The current size is 51.19 MB (compressed).
3Please ensure that list of dependencies in package.json contains only those packages that are needed at runtime.
4Move all build-time dependencies such as webpack, babel, etc... to devDependencies, rerun npm | yarn install, and try to deploy again.

Following are the possible fixes that would help you reduce serverless bundle size by better engineering. If none of these does it, feel free to raise an issue on Edgio Forums.

Possible Fix [1]: Segregating devDependencies from dependencies

Typically, this is due to node_modules marked as dependencies when they are more appropriate in devDependencies within the package.json file. Modules marked as dependencies will be included in the serverless bundle. Dev-only modules such as babel, jest, webpack, etc. should be moved to devDependencies as shown:

Diff
1"dependencies": {
2 "@nuxtjs/sitemap": "2.4.0",
3 "@nuxt/core": "2.15.7"
4- "babel": "7.12.7",
5- "jest": "28.1.3"
6+ },
7+ "devDependencies": {
8+ "babel": "7.12.7",
9+ "jest": "28.1.3"
10}

Possible Fix [2]: Segregating assets from serverless bundle

Additionally, this can be related to assets (such as fonts or images) that are imported into your project code. These resources are typically better referenced as static assets which are stored outside of the serverless bundle.

You can remedy this by creating a public directory in the root of your project. Move all of your font and image assets to this path. Then, create a route in routes.js to serve those requests as static assets using the following as an example:

JavaScript
1router.get('/assets/:path*', ({ serveStatic }) => {
2 serveStatic('public/:path*')
3})

Now, you can update your code references from importing the assets to referencing the static path, such as:

Diff
1- import myImage from 'public/images/Image1.png'
2...
3- <div><img src={myImage}/></div>
4+ <div><img src="/assets/images/Image1.png"/></div>

Possible Fix [3]: Computing which node_modules be included in the serverless bundle

It might be possible, that Possible Fix [1] reduces your serverless bundle size, but not reduce it to less than 50 MB (250 MB Uncompresssed). Another way to identify which dependencies would be required in the runtime is to use @vercel/nft package (a “Node.js dependency tracing utility”).

Step 1. Install @vercel/nft as devDependency:

Bash
1npm i -D @vercel/nft

Step 2. Create a file named setNodeModules.js in the root directory of your project with the following code:

JavaScript
1const fs = require('fs')
2const { nodeFileTrace } = require('@vercel/nft')
3
4const setNodeModules = async () => {
5 // Enter an entry point to the app, for example in Nuxt(2), the whole app inside core.js
6 const files = ['./node_modules/@nuxt/core/dist/core.js']
7 // Compute file trace
8 const { fileList } = await nodeFileTrace(files)
9 // Store set of packages
10 let packages = {}
11 fileList.forEach((i) => {
12 if (i.includes('node_modules/')) {
13 let temp = i.replace('node_modules/', '')
14 temp = temp.substring(0, temp.indexOf('/'))
15 packages[`node_modules/${temp}`] = true
16 } else {
17 packages[i] = true
18 }
19 })
20 // Sort the set of packages to maintain differences with git
21 fs.writeFileSync(
22 './getNodeModules.js',
23 `module.exports=${JSON.stringify(
24 Object.keys(packages)
25 .sort()
26 .reduce((obj, key) => {
27 obj[key] = packages[key]
28 return obj
29 }, {})
30 )}`
31 )
32}
33
34setNodeModules()

Step 3. Change your existing package.json to have node setNodeModules.js before each command as follows:

Diff
1- "edgio:dev": "edgio dev",
2- "edgio:build": "edgio build",
3- "edgio:deploy": "edgio deploy"
4
5+ "edgio:dev": "node setNodeModules.js && edgio dev",
6+ "edgio:build": "node setNodeModules.js && edgio build",
7+ "edgio:deploy": "node setNodeModules.js && edgio deploy"

Step 4. Change your edgio.config.js to have:

JavaScript
1// https://docs.edg.io/guides/basics/edgio_config
2module.exports = {
3 includeFiles: require('./getNodeModules'),
4}