Edgio Sites’s limits and caveats are listed below.
- You may need to manually include NodeJS addons (aka native extensions).
- Your code will only be granted read-only access to the file system.
- Your project’s bundle size cannot exceed 50 MB compressed or 250 MB uncompressed.
- Our serverless functions have a maximum runtime of 20 seconds per request. The response for a function that exceeds this limit is a 539 Project Timeout.
- Our Serverless Compute workers are allowed to generate a response body with a maximum file size of 6 MB.
- Your project must comply with all applicable Edgio Performance limitations.
NodeJS native extensions
In a lot of scenarios, NodeJS native extensions might be required in order 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=x642at 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:
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.
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 service.
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.
[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:
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}
[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:
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:
1- import myImage from 'public/images/Image1.png'2...3- <div><img src={myImage}/></div>4+ <div><img src="/assets/images/Image1.png"/></div>
[3]: Computing which node_modules be included in the serverless bundle
It might be possible, that [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:
1npm i -D @vercel/nft
Step 2. Create a file named setNodeModules.js
in the root directory of your project with the following code:
1const fs = require('fs')2const { nodeFileTrace } = require('@vercel/nft')34const setNodeModules = async () => {5 // Enter an entry point to the app, for example in Nuxt(2), the whole app inside core.js6 const files = ['./node_modules/@nuxt/core/dist/core.js']7 // Compute file trace8 const { fileList } = await nodeFileTrace(files)9 // Store set of packages10 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}`] = true16 } else {17 packages[i] = true18 }19 })20 // Sort the set of packages to maintain differences with git21 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 obj29 }, {})30 )}`31 )32}3334setNodeModules()
Step 3. Change your existing package.json
to have node setNodeModules.js
before each command as follows:
1- "edgio:dev": "edgio dev",2- "edgio:build": "edgio build",3- "edgio:deploy": "edgio deploy"45+ "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:
1// https://docs.edg.io/guides/basics/edgio_config2module.exports = {3 includeFiles: require('./getNodeModules'),4}