I have an SSR server for React.JS app made with CRA, ConnectedRouter, Redux and Saga. I am trying to host this server under AWS Lambda using the below code:
const serverlessExpress = require('@vendia/serverless-express');
const app = require('./index');
const binaryMimeTypes = [
'application/javascript',
...
'text/xml'
];
exports.handler = serverlessExpress({
app,
binaryMimeTypes
}).handler;
This wrapper pulls the setup code below:
const md5File = require('md5-file');
const fs = require('fs');
const path = require('path');
// CSS styles will be imported on load and that complicates matters... ignore those bad boys!
const ignoreStyles = require('ignore-styles');
const register = ignoreStyles.default;
// We also want to ignore all image requests
// When running locally these will load from a standard import
// When running on the server, we want to load via their hashed version in the build folder
const extensions = ['.gif', '.jpeg', '.jpg', '.png', '.svg'];
// Override the default style ignorer, also modifying all image requests
register(ignoreStyles.DEFAULT_EXTENSIONS, (mod, filename) => {
if (!extensions.find(f => filename.endsWith(f))) {
// If we find a style
return ignoreStyles.noOp();
}
// for images that less than 10k, CRA will turn it into Base64 string, but here we have to do it again
const stats = fs.statSync(filename);
const fileSizeInBytes = stats.size / 1024;
if (fileSizeInBytes <= 10) {
mod.exports = `data:image/${mod.filename
.split('.')
.pop()};base64,${fs.readFileSync(mod.filename, {
encoding: 'base64'
})}`;
return ignoreStyles.noOp();
}
// If we find an image
const hash = md5File.sync(filename).slice(0, 8);
const bn = path.basename(filename).replace(/(.w{3})$/, `.${hash}$1`);
mod.exports = `/static/media/${bn}`;
});
// Set up babel to do its thing... env for the latest toys, react-app for CRA
// Notice three plugins: the first two allow us to use import rather than require, the third is for code splitting
// Polyfill is required for Babel 7, polyfill includes a custom regenerator runtime and core-js
require('@babel/polyfill');
require('@babel/register')({
ignore: [//(build|node_modules)//],
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-proposal-class-properties',
'dynamic-import-node',
'react-loadable/babel'
]
});
// Now that the nonsense is over... load up the server entry point
const app = require('./server');
module.exports = app;
Then I have the regular express server in my server.js
// Express requirements
import bodyParser from 'body-parser';
import compression from 'compression';
import express from 'express';
import morgan from 'morgan';
import path from 'path';
import forceDomain from 'forcedomain';
import Loadable from 'react-loadable';
import cookieParser from 'cookie-parser';
// Our loader - this basically acts as the entry point for each page load
import loader from './loader';
// Create our express app using the port optionally specified
const main = () => {
const app = express();
const PORT = process.env.PORT || 3000;
// Compress, parse, log, and raid the cookie jar
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
app.use(cookieParser());
// Set up homepage, static assets, and capture everything else
app.use(express.Router().get('/', loader));
const favicon = require('serve-favicon');
app.use(favicon(path.resolve(__dirname, '../build/icons/favicon.ico')));
app.use(express.static(path.resolve(__dirname, '../build')));
app.use(loader);
// We tell React Loadable to load all required assets and start listening - ROCK AND ROLL!
Loadable.preloadAll().then(() => {
app.listen(PORT, console.log(`App listening on port ${PORT}!`));
});
// Handle the bugs somehow
app.on('error', error => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof PORT === 'string' ? 'Pipe ' + PORT : 'Port ' + PORT;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
return app;
};
module.exports = main();
Then, I have the loader do:
// Express requirements
import path from 'path';
import fs from 'fs';
// React requirements
import React from 'react';
import { renderToString } from 'react-dom/server';
import Helmet from 'react-helmet';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { Frontload, frontloadServerRender } from 'react-frontload';
import Loadable from 'react-loadable';
// Our store, entrypoint, and manifest
import createStore from '../src/configureStore';
import App from '../src/containers/app';
import manifest from '../build/asset-manifest.json';
// Some optional Redux functions related to user authentication
//import { setCurrentUser, logoutUser } from '../src/modules/auth';
// LOADER
export default (req, res) => {
/*
A simple helper function to prepare the HTML markup. This loads:
- Page title
- SEO meta tags
- Preloaded state (for Redux) depending on the current route
- Code-split script tags depending on the current route
*/
const injectHTML = (data, { html, title, meta, body, scripts, state }) => {
data = data.replace('<html>', `<html ${html}>`);
data = data.replace(/<title>.*?</title>/g, title);
data = data.replace('</head>', `${meta}</head>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${body}</div><script>window.__PRELOADED_STATE__ = ${state}</script>${scripts.join(
''
)}`
);
return data;
};
// Load in our HTML file from our build
fs.readFile(
path.resolve(__dirname, '../build/index.html'),
'utf8',
(err, htmlData) => {
...
The entire process, including transpilation, and the time necessary to start the express server takes quite a while. My latency could be anywhere between 100ms for a warm lambda, all the way to around 3 seconds.
Is there some low-hanging fruit improvements that I could apply to my code?
question from:
https://stackoverflow.com/questions/65928878/how-could-i-reduce-the-long-cold-start-time-when-doing-ssr-from-lambda