This system implements a serverless architecture for deploying and serving Next.js applications on AWS Lambda. The core functionality is implemented in a Lambda function that handles both static file serving and server-side rendering (SSR) through a sophisticated request processing pipeline.
The Lambda function requires specific environment variables:
AWS_REGION=us-east-1 // AWS region for S3 operations
JOB_ID=unique-id // Unique identifier for the deployment
S3_BUCKET_NAME=bucket // S3 bucket containing deployments-
Server Caching
const serverCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
- Implements a server cache with 5-minute TTL
- Cache key is based on
JOB_ID - Prevents unnecessary server reloads
-
Bundle Extraction
const workDir = join(TMP_DIR, JOB_ID); await mkdir(workDir, { recursive: true }); const zip = new AdmZip(bundlePath); zip.extractAllTo(extractDir, true);
- Downloads bundle from S3
- Extracts to
/tmp/{JOB_ID}/extracted - Uses
adm-zipfor extraction
-
Environment Configuration
process.env.NODE_ENV = 'production'; process.env.NEXT_TELEMETRY_DISABLED = '1'; process.env.NEXT_RUNTIME = 'nodejs'; process.env.NEXT_SHARP_PATH = join(extractDir, 'node_modules/sharp'); process.env.NEXT_DIST_DIR = join(extractDir, '.next'); process.env.NEXT_BUILD_ID = 'standalone'; process.env.NEXT_PHASE = 'production';
- Sets critical Next.js environment variables
- Configures runtime environment
- Manages dependencies
const path = event.rawPath || event.path || '/';
const queryString = event.rawQueryString || '';
const fullPath = queryString ? `${path}?${queryString}` : path;- Extracts path from Lambda event
- Handles query parameters
- Normalizes paths
function isStaticFileRequest(path) {
if (!path) return false;
return path.startsWith('/_next/') ||
path.startsWith('/public/') ||
path.startsWith('/static/');
}- Identifies static file requests
- Handles Next.js static files
- Manages public files
function createRequest(event) {
const path = event.rawPath || event.path || '/';
const httpMethod = event.requestContext?.http?.method || event.httpMethod || 'GET';
const headers = event.headers || {};
const queryStringParameters = event.queryStringParameters || {};
const body = event.body;
// Create a proper Node.js request object
const req = {
method: httpMethod,
url: path,
headers: headers,
query: queryStringParameters,
body: body ? JSON.parse(body) : undefined,
cookies: event.cookies || {},
rawHeaders: Object.entries(headers).flat(),
rawUrl: path,
protocol: 'https',
hostname: event.requestContext?.domainName || 'localhost',
ip: event.requestContext?.http?.sourceIp || '127.0.0.1',
// Event emitter implementation
_events: {},
on: function(event, listener) {
if (!this._events[event]) this._events[event] = [];
this._events[event].push(listener);
return this;
},
once: function(event, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.removeListener(event, onceWrapper);
};
return this.on(event, onceWrapper);
},
removeListener: function(event, listener) {
if (!this._events || !this._events[event]) return this;
this._events[event] = this._events[event].filter(l => l !== listener);
return this;
},
removeAllListeners: function(event) {
if (!this._events) return this;
if (event) {
delete this._events[event];
} else {
this._events = {};
}
return this;
},
listeners: function(event) {
return this._events[event] || [];
},
emit: function(event, ...args) {
if (!this._events || !this._events[event]) return false;
this._events[event].forEach(listener => listener.apply(this, args));
return true;
},
// Stream interface
readable: true,
read: function() {
if (this.body) {
const chunk = this.body;
this.body = null;
return chunk;
}
return null;
},
pipe: function(dest) {
if (this.body) {
dest.write(this.body);
}
dest.end();
return dest;
},
// Socket simulation
socket: {
remoteAddress: event.requestContext?.http?.sourceIp || '127.0.0.1',
remotePort: 443,
encrypted: true,
on: function() {},
once: function() {},
removeListener: function() {},
removeAllListeners: function() {},
listeners: function() { return []; },
emit: function() { return true; }
},
connection: {
encrypted: true,
on: function() {},
once: function() {},
removeListener: function() {},
removeAllListeners: function() {},
listeners: function() { return []; },
emit: function() { return true; }
}
};
return req;
}- Creates Node.js-compatible request object
- Implements event emitter interface
- Provides stream capabilities
- Simulates socket behavior
- Handles headers and cookies
function createResponse() {
const response = {
statusCode: 200,
headers: {},
body: '',
_chunks: [],
_ended: false,
_events: {},
_implicitHeader: function() {
if (!this.headersSent) {
this.writeHead(this.statusCode);
}
},
_header: '',
_headerSent: false,
headersSent: false,
// Response methods
setHeader: function(name, value) {
this.headers[name] = value;
return this;
},
getHeader: function(name) {
return this.headers[name];
},
removeHeader: function(name) {
delete this.headers[name];
return this;
},
write: function(chunk) {
if (this._ended) return false;
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk);
}
this._chunks.push(chunk);
return true;
},
end: function(chunk) {
if (chunk) {
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk);
}
this._chunks.push(chunk);
}
this._ended = true;
return this;
},
writeHead: function(statusCode, headers) {
this.statusCode = statusCode;
if (headers) {
Object.assign(this.headers, headers);
}
this.headersSent = true;
this._headerSent = true;
return this;
},
flushHeaders: function() {
if (!this.headersSent) {
this.writeHead(this.statusCode);
}
return this;
},
// Event emitter implementation
on: function(event, listener) {
if (!this._events[event]) this._events[event] = [];
this._events[event].push(listener);
return this;
},
once: function(event, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.removeListener(event, onceWrapper);
};
return this.on(event, onceWrapper);
},
removeListener: function(event, listener) {
if (!this._events[event]) return this;
this._events[event] = this._events[event].filter(l => l !== listener);
return this;
},
removeAllListeners: function(event) {
if (event) {
delete this._events[event];
} else {
this._events = {};
}
return this;
},
listeners: function(event) {
return this._events[event] || [];
},
emit: function(event, ...args) {
if (!this._events[event]) return false;
this._events[event].forEach(listener => listener.apply(this, args));
return true;
},
off: function(event, listener) {
return this.removeListener(event, listener);
}
};
return response;
}- Implements Node.js response interface
- Handles chunked responses
- Manages headers and status codes
- Provides event emitter capabilities
const responsePromise = new Promise((resolve, reject) => {
let responseData = {
statusCode: 200,
headers: {},
body: '',
chunks: []
};
// Set up response event handlers
const handleResponseComplete = () => {
console.log('β
Response complete');
responseData.statusCode = res.statusCode || 200;
responseData.headers = res.headers;
// Join chunks and convert to string
const buffer = Buffer.concat(res._chunks);
responseData.body = buffer.toString('utf8');
responseData.isBase64Encoded = false;
resolve(responseData);
};
// Set up write handler to collect chunks
const originalWrite = res.write;
res.write = function(chunk, encoding, callback) {
console.log('π Writing chunk:', {
chunkLength: chunk ? chunk.length : 0,
encoding,
hasCallback: !!callback,
isBuffer: Buffer.isBuffer(chunk)
});
if (chunk) {
if (!Buffer.isBuffer(chunk)) {
chunk = Buffer.from(chunk);
}
res._chunks.push(chunk);
}
const result = originalWrite.call(this, chunk, encoding, callback);
return result;
};
// Set up end handler
const originalEnd = res.end;
res.end = function(chunk, encoding, callback) {
console.log('π Ending response:', {
chunkLength: chunk ? chunk.length : 0,
encoding,
hasCallback: !!callback,
isBuffer: chunk ? Buffer.isBuffer(chunk) : false
});
if (chunk) {
if (!Buffer.isBuffer(chunk)) {
chunk = Buffer.from(chunk);
}
res._chunks.push(chunk);
}
const result = originalEnd.call(this, chunk, encoding, callback);
handleResponseComplete();
return result;
};
// Set up error handler
res.on('error', (error) => {
console.error('β Response error:', error);
reject(error);
});
// Set up finish handler
res.on('finish', () => {
console.log('β
Response finished');
handleResponseComplete();
});
// Set up close handler
res.on('close', () => {
console.log('β
Response closed');
handleResponseComplete();
});
});- Collects response chunks
- Handles timeouts
- Manages partial responses
- Converts to Lambda response format
try {
// Handle request
} catch (error) {
console.error('β Error handling request:', error);
// If we have partial response data, try to return it
if (error.message === 'Response timeout' && res._chunks && res._chunks.length > 0) {
console.log('β οΈ Returning partial response due to timeout');
return {
statusCode: res.statusCode || 200,
headers: {
...res.headers,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
},
body: res._chunks.join(''),
isBase64Encoded: res.headers['Content-Encoding'] === 'gzip'
};
}
return {
statusCode: error.message === 'Response timeout' ? 504 : 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
error: error.message === 'Response timeout' ? 'Gateway Timeout' : 'Internal Server Error',
message: error.message,
stack: error.stack
})
};
}- Implements comprehensive error handling
- Provides detailed error responses
- Handles timeouts gracefully
- Manages partial responses
const contentTypes = {
// Images
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'svg': 'image/svg+xml',
'webp': 'image/webp',
'ico': 'image/x-icon',
// Fonts
'woff2': 'font/woff2',
'woff': 'font/woff',
'ttf': 'font/ttf',
'eot': 'application/vnd.ms-fontobject',
'otf': 'font/otf',
// Documents
'html': 'text/html',
'htm': 'text/html',
'css': 'text/css',
'js': 'application/javascript',
'json': 'application/json',
'xml': 'application/xml',
'pdf': 'application/pdf',
// Text
'txt': 'text/plain',
'md': 'text/markdown',
'csv': 'text/csv',
// Archives
'zip': 'application/zip',
'tar': 'application/x-tar',
'gz': 'application/gzip',
// Other
'mp4': 'video/mp4',
'webm': 'video/webm',
'mp3': 'audio/mpeg',
'wav': 'audio/wav'
};- Comprehensive MIME type support
- Proper content type mapping
if (path.startsWith('/_next/image')) {
const url = new URL(fullPath, 'http://localhost');
const width = parseInt(url.searchParams.get('w') || '0');
const quality = parseInt(url.searchParams.get('q') || '75');
if (width > 0) {
try {
console.log('Processing image:', { width, quality, contentType });
const sharp = require('sharp');
const processedImage = await sharp(buffer)
.resize(width, null, { withoutEnlargement: true })
.jpeg({ quality })
.toBuffer();
buffer = processedImage;
contentType = 'image/jpeg';
} catch (error) {
console.error('Error processing image:', error);
// If image processing fails, return the original image
}
}
}- Uses Sharp for image processing
- Resizes images based on width parameter
- Optimizes JPEG quality
- Handles processing failures gracefully
console.log('π Processing request:', { path, fullPath, event });
console.log('π¦ Handling static file request');
console.log('Static file info:', staticFileInfo);
console.error('β Error handling request:', error);
console.error('Error fetching static file:', error, { path, fullPath, staticFileInfo });- Implements comprehensive logging
- Tracks request lifecycle
- Logs errors and warnings
- Monitors performance