I’m attempting to convert an Angular application to Angular Universal for SSR purposes, which was considerably easy to manage. I’ve followed multiple guides found on the web in a vain attempt to then deploy the application to AWS. Put simply, serverless itself just doesn’t work. Take this guide as a prime example and to avoid too much bloat going over all of the variations I’ve tried, I’ll simplify this post with only details from this version.
I’ve basically copy/pasted everything recommended in the blog post. This is my lambda.js:
const awsServerlessExpress = require("aws-serverless-express");
const server = require("./dist/app-name/serverless/main");
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
const binaryMimeTypes = [
"application/javascript",
"application/json",
"application/octet-stream",
"application/xml",
"image/jpeg",
"image/png",
"image/gif",
"text/comma-separated-values",
"text/css",
"text/html",
"text/javascript",
"text/plain",
"text/text",
"text/xml",
"image/x-icon",
"image/svg+xml",
"application/x-font-ttf",
];
server.app.use(awsServerlessExpressMiddleware.eventContext());
const serverProxy = awsServerlessExpress.createServer(
server.app,
null,
binaryMimeTypes
);
module.exports.handler = (event, context) =>
awsServerlessExpress.proxy(serverProxy, event, context);
The tsconfig.serverless.json:
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/serverless",
"target": "es2019",
"types": [
"node"
]
},
"files": [
"src/main.server.ts",
"serverless.ts"
]
}
The serverless.ts:
import 'zone.js/dist/zone-node';
import {APP_BASE_HREF} from '@angular/common';
import {ngExpressEngine} from '@nguniversal/express-engine';
import * as express from 'express';
import {existsSync} from 'fs';
import {join} from 'path';
import {AppServerModule} from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/app-name/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
export * from './src/main.server';
The serverless.yml:
service: app-name
plugins:
- serverless-apigw-binary
- serverless-offline
provider:
name: aws
runtime: nodejs12.x
memorySize: 192
timeout: 10
package:
exclude:
- ./**
include:
- "node_modules/aws-serverless-express/**"
- "node_modules/binary-case/**"
- "node_modules/type-is/**"
- "node_modules/media-typer/**"
- "node_modules/mime-types/**"
- "node_modules/mime-db/**"
- "dist/**"
- "lambda.js"
custom:
apigwBinary:
types:
- "*/*"
functions:
api:
handler: lambda.handler
events:
- http: GET {proxy+}
- http: GET /
And the package json:
{
"name": "app-name",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"dev:ssr": "ng run app-name:serve-ssr",
"serve:ssr": "node dist/app-name/server/main.js",
"build:ssr": "ng build && ng run app-name:server",
"prerender": "ng run app-name:prerender",
"serve:serverless": "serverless offline start",
"build:serverless": "ng build --configuration production && ng run app-name:serverless:production"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.0.0",
"@angular/common": "^14.0.0",
"@angular/compiler": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/forms": "^14.0.0",
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/platform-server": "^14.0.0",
"@angular/router": "^14.0.0",
"@nguniversal/express-engine": "^14.2.0",
"aws-serverless-express": "^3.4.0",
"express": "^4.15.2",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.0.2",
"@angular/cli": "~14.0.2",
"@angular/compiler-cli": "^14.0.0",
"@nguniversal/builders": "^14.2.0",
"@types/express": "^4.17.0",
"@types/jasmine": "~4.0.0",
"@types/node": "^14.15.0",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"serverless": "^3.23.0",
"serverless-apigw-binary": "^0.4.4",
"serverless-offline": "^11.1.3",
"typescript": "~4.7.2"
}
}
serve:ssr and dev:ssr execute correctly, I can visit the application in the browser in its SSR form. build:serverless also executes correctly, and the main.js is generated properly. When I run serve:serverless, I get this error:
Unexpected "handler" function configuration: Expected object received 'lambda.handler'
This is incorrect, as the handler fn does exist and is nested properly in the yml. There is clearly something else wrong, but serverless isn’t letting on to the actual error. Has anyone had any experience with this, or can point me to actual debugging methods to isolate the issue? Neither the forum, documentation or even stack overflow has been of any help, and I’ve been working on this for literally 3 days straight with no forward movement.