Deploy to AWS Lambda?

Thanks @luisherranz. I managed to deploy the app as lambda on aws as you mentioned above. It works fine. Now home page of my sample blog is loading fine. If I navigate to menus ( wordpress categories - /category/xyz) I get '“message”:“Forbidden”}" as response from the lambda. This is happening because aws API gateway require ‘stage’ to be appended at the end of API endpoint so that /dev is appended at the end of api endpoint. It seems either i need to provide path without forward slash ‘/’ within menu settings in frontity.settings.js. For users, map to a custom domain though. Is this correct ?. Thanks again. :slight_smile:

Hey, that’s great. I am glad it’s working! :slightly_smiling_face:


You’re right about that, the URL path is wrong. Could you please try this other library? https://github.com/dougmoscrop/serverless-http

It seems to support a basePath option, although in our case that would be "/" so I am not sure if that is going to work. Something like this:

// lambda.js
const serverless = require("serverless-http");
const app = require("./build/server.js").default;

module.exports.handler = serverless(app, {
  basePath: "/",
});

More info about the basePath option here: https://github.com/dougmoscrop/serverless-http/blob/master/docs/ADVANCED.md

Let me know what you find.

Nope. it doesn’t work for me. I receive an 500 internal server error. :frowning:

Does it work without the basePath?

Continuing the discussion from Deploy to AWS Lambda?:

Removing basePath still gives me internal server error. What i finally did is hosted my wordpress on a subdomain pointing to an ec2 instance and configured with free let’s encrypt certificate. And hosted the app itself on aws lambda using serverless express. I’m new to serverless and configuring aws services in general. I created a subdomain certificate on amazon ( domain was bought through amazon ) and pointed to wordpress. It seems working fine for me now. I have few questions about performance and efficiency ( response times etc ) in using lambda for serving web frontend through Frontity nodejs server. I have generally seen using normal rest api services deloyed on lambda. Because as far is know lambda functions are active only when request is processed and doesn’t remain ‘open’ until request is received through API gateways. What about serving through amazon container services ? Could you please shed some :bulb: into this ?. Do you have some resources that i can go through ?. I apologize, too many questions i know. This thing really helped me learn something new :). Thanks :slightly_smiling_face:

@vimalkodoth I’ve merged the topic you opened in another thread to keep the conversation in the same post

You should be able to continue the conversation. If you have any problems send us a message (moderators → @juanma or @mburridge) or mention us in your reply, and we will try to solve it

Thanks for moving this @juanma.


I’m glad you solved it @vimalkodoth, although maybe it’s time we study this ourselves, include the AWS handler in Frontity, and recommend a specific way to set up Frontity with AWS lambdas. What do you think @juanma? Should we add it to our docs roadmap?


The performance of AWS lambdas is not critical because you should be caching the output anyway. If you want to cache it with AWS, you can configure CloudFront. If not, you can use an external service like Fastly or KeyCDN. More info about this here: Moving an Existing WordPress website with over 5000 Articles

@luisherranz Interested in deploying to AWS-Lambda. Is there any official Frontity - AWS integration Template coming on the roadmap? Will keep on eye this thread…

Hi @dejangeorgiev

Regarding this, we have created an issue to have a look at it and try to provide a better explanation on this → https://github.com/frontity/docs/issues/145

Feel free to comment on the issue to add any specific information you missed from the docs so we can take it into account when we work on this.

Thanks a lot for your feedback

1 Like

Yeah, we’d love to add official support :slightly_smiling_face:

The main blocker is that AWS lambdas contain a “stage path” in the URL, for example /dev or /latest, and it’s not consistent when you use a custom/non-custom domain.

https://ov8mwmh2xa.execute-api.us-east-1.amazonaws.com/latest

There doesn’t seem to be an standard way to get rid of it, but I’ve been investigating and using event.pathParameters.proxy seems to be one of the best ways, so I’d like to try that first.

This scaffold is a bit old, but implements the replacement of pathParameters.proxy:

module.exports = (ctx, next) => {
  ctx.lambdaEvent =
    (ctx.headers["x-apigateway-event"] &&
      JSON.parse(decodeURIComponent(ctx.headers["x-apigateway-event"]))) ||
    {};
  ctx.lambdaContext =
    (ctx.headers["x-apigateway-context"] &&
      JSON.parse(decodeURIComponent(ctx.headers["x-apigateway-context"]))) ||
    {};
  ctx.env = (ctx.lambdaEvent && ctx.lambdaEvent.stageVariables) || process.env;

  // Workaround an inconsistency in APIG. For custom domains, it puts the
  // mapping prefix on the url, but non-custom domain requests do not. Fix it by
  // changing the path to the proxy param which has the correct value always.
  if (ctx.lambdaEvent.pathParameters && ctx.lambdaEvent.pathParameters.proxy) {
    const dummyBase = "zz://zz";
    const url = new URL(ctx.url, dummyBase);
    url.pathname = "/" + ctx.lambdaEvent.pathParameters.proxy;
    ctx.url = url.href.replace(dummyBase, "");
  }
  return next();
};

Apart from that, any “Koa/Express to Lambda” library should do the trick. That scaffold is using aws-serverless-express which seems to be one of the most popular ones, so I guess we can try with that.

Frontity uses Koa, not Express, but exports a req/res function by using app.callback() so there’s not much difference.

This is the code of the scaffold:

const awsServerlessExpress = require("aws-serverless-express");
const app = require("./app");
app.proxy = true;
const server = awsServerlessExpress.createServer(app.callback());
exports.handler = (event, context) =>
  awsServerlessExpress.proxy(server, event, context);

In Frontity, the server.js file has already the app.callback(), so it’d be something like this:

const awsServerlessExpress = require("aws-serverless-express");
const frontity = require("./build/server.js").default;
frontity.proxy = true;
const server = awsServerlessExpress.createServer(frontity);
exports.handler = (event, context) =>
  awsServerlessExpress.proxy(server, event, context);

If you want to give it a try let me know what you find out! :slightly_smiling_face:

1 Like

Hi @luisherranz

Thank you so much for the detailed explanation.

Definitely i will give a try at some later stage and let you know my experiences with it.

I also think about to move the backend part “WordPress” on AWS and cache the API’s with Cloudfront. There are some great templates, developed by AWS. Check it out here https://github.com/aws-samples/aws-refarch-wordpress

Not sure about the estimated pricing tough, will discuss this with AWS Account Manager.

Thank you so much.
Best,
Dejan

Hi @luisherranz,

Thank you for all of your help with this issue thus far.

In my case, I was able to use serverless to set up a Lambda with an Application Gateway and a custom domain name that all routes to the root of my domain (https://example.com.au/). However, from there I have been unable to get any files served from the static directory to serve alongside my server.js file. This includes the important *.module.js files that should be requested as the page loads in the browser.

Do you have any recommendations that I can try to understand why these additional files will not load alongside my main request to server.js? Does server.js not handle the routing and requesting of files from the /static/ directory?

My lambda.js file looks like this:

// lambda.js
const awsServerlessExpress = require("aws-serverless-express");
const app = require("./build/server.js").default;
const server = awsServerlessExpress.createServer(app);

exports.handler = (event, context) => {
  awsServerlessExpress.proxy(server, event, context);
};

And the important parts of my serverless.yml look like this:

// serverless.yml
service: example

excludeDevDependencies: true

plugins:
  - serverless-domain-manager

custom:
  customDomain:
    domainName: example.com.au
    stage: ${self:provider.stage}
    createRoute53Record: true
    certificateName: "example.com.au"

provider:
  name: aws
  runtime: nodejs12.x

functions:
  app:
    handler: lambda.handler
    events:
      - http: ANY {proxy+}

Thanks,
Sean

Hey @luisherranz,

I was actually able to figure this out. I was able to use https://github.com/activescott/serverless-aws-static-file-handler to handle serving all my static files correctly. I’ve updated my lambda.js as follows:

// lambda.js
const awsServerlessExpress = require("aws-serverless-express");
const app = require("./build/server.js").default;
const server = awsServerlessExpress.createServer(app);
const path = require("path");
const FileHandler = require("serverless-aws-static-file-handler");

// Handle routing to Frontity Server
exports.handler = (event, context) => {
  awsServerlessExpress.proxy(server, event, context);
};

// Handle returning all static files located in `/build/static/`
const StaticFilesPath = path.join(__dirname, "./build/static/");
const staticFileHandler = new FileHandler(StaticFilesPath);
exports.static = async (event, context) => {
  if (!event.path.startsWith("/static/")) {
    throw new Error(`[404] Invalid filepath for this resource: ${fname}`);
  }
  return staticFileHandler.get(event, context);
};

// Handle returning `favicon.ico` file in the project root
const RootFilesPath = path.join(__dirname);
const rootFileHandler = new FileHandler(RootFilesPath);
exports.favicon = async (event, context) => {
  return rootFileHandler.get(event, context);
}

And I’ve also updated my serverless.yml to the following:

// serverless.yml
service: example

excludeDevDependencies: true

plugins:
  - serverless-domain-manager
  - serverless-aws-static-file-handler

custom:
  customDomain:
    domainName: example.com.au
    stage: ${self:provider.stage}
    createRoute53Record: true
    certificateName: "example.com.au"
 apiGateway:
    binaryMediaTypes:
      - "*/*"

provider:
  name: aws
  runtime: nodejs12.x

functions:
  app:
    handler: lambda.handler
    events:
      - http:
          path: /
          method: any
      - http:
          path: /{proxy+}
          method: any

  static:
    handler: lambda.static
    events:
      - http:
          path: /static/{proxy+}
          method: get

  favicon:
    handler: lambda.favicon
    events:
      - http:
          path: /favicon.ico
          method: get

Hopefully this helps anyone else looking to host their Frontity site on AWS Lambda!

Cheers,
Sean

2 Likes

Hey @sean, that is awesome!!

I did an experiment a while ago to embed the static files inside the server.js file, just to know if that is something we could do in the future: https://github.com/luisherranz/embed-files-in-node-server

But I really like your approach. I am not familiar with the Serverless framework to be honest but it looks really cool.

I guess the next step would be to add CloudFront in front of everything. Have you thought about that? Can it be done with the serverless.yaml file as well?

If so, maybe you can make everything immutable (cache for a year even the HTML) and use a plugin like C3 CloudFront Cache by @hideokamoto to invalidate the cache from your WordPress.

Let us know what you find out.

There’s an issue created (in the docs repository) to create a guide about this

2 Likes

Hey @luisherranz,

Yes, I have been able to cache everything behind CloudFront!

Here is my updated serverless.yml

service: example2020

excludeDevDependencies: true

plugins:
  - serverless-aws-static-file-handler
  - serverless-offline

custom:
  apiGateway:
    binaryMediaTypes:
      - "*/*"

provider:
  name: aws
  runtime: nodejs12.x

functions:
  app:
    handler: lambda.handler
    events:
      - http:
          path: /
          method: any
      - http:
          path: /{proxy+}
          method: any

  static:
    handler: lambda.static
    events:
      - http:
          path: /static/{proxy+}
          method: get

  favicon:
    handler: lambda.favicon
    events:
      - http:
          path: /favicon.ico
          method: get

resources:
  Resources:
    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Comment: Example Site
          Aliases:
            - example.com.au
          ViewerCertificate:
            AcmCertificateArn: arn:aws:acm:us-east-1:100000000000:certificate/some-certificate-slug
            SslSupportMethod: sni-only
          DefaultCacheBehavior:
            TargetOriginId: MyFirstOrigin
            ViewerProtocolPolicy: "redirect-to-https"
            Compress: true
            MinTTL: 31536000
            DefaultTTL: 31536000
            ForwardedValues:
              QueryString: false
          Enabled: true
          HttpVersion: http2
          Origins:
            - Id: MyFirstOrigin
              DomainName:
                Fn::Join:
                  - "."
                  - - Ref: ApiGatewayRestApi
                    - execute-api.us-east-1.amazonaws.com
              OriginPath: /dev
              CustomOriginConfig:
                HTTPPort: 80
                HTTPSPort: 443
                OriginProtocolPolicy: https-only

And I also has to modify my lambda.js file to handle setting a Cache-Control header on my static resources:

const awsServerlessExpress = require("aws-serverless-express");
const app = require("./build/server.js").default;
const server = awsServerlessExpress.createServer(app);
const path = require("path");
const FileHandler = require("serverless-aws-static-file-handler");

// Handle routing to Frontity Server
exports.handler = (event, context) => {
  awsServerlessExpress.proxy(server, event, context);
};

// Handle returning all static files located in `/build/static/`
const StaticFilesPath = path.join(__dirname, "./build/static/");
const staticFileHandler = new FileHandler(StaticFilesPath);
exports.static = async (event, context) => {
  if (!event.path.startsWith("/static/")) {
    throw new Error(`[404] Invalid filepath for this resource: ${fname}`);
  }
  let response = await staticFileHandler.get(event, context);
  response.headers = {
    'Content-Type': response.headers["Content-Type"],
    'Cache-Control': 'max-age=31536000',
 }
 return response;
};

// Handle returning `favicon.ico` file in the project root
const RootFilesPath = path.join(__dirname);
const rootFileHandler = new FileHandler(RootFilesPath);
exports.favicon = async (event, context) => {
  return rootFileHandler.get(event, context);
}

Hope this helps!

1 Like

That’s awesome :clap: :clap: Congratulations :slight_smile:

Are you caching the HTML requests as well?

I guess you could use a s-maxage=31536000 header to store the HTML in Cloudfront for as long as possible, and then clear the cache from WordPress each time you update/publish a post using this plugin: https://wordpress.org/plugins/c3-cloudfront-clear-cache/

This is an old thread, but I successfully deployed Frontity to AWS Lambda tonight. Wordpress is hosted on Amazon Lightsail. I used the aws-serverless-express as a wrapper and managed to get it working out of the box.

const awsServerlessExpress = require('aws-serverless-express');
// Note the .default. If you miss this your lambda functions will timeout.
const app = require('./build/server').default;

const server = awsServerlessExpress.createServer(app);

exports.handler = (event, context) => {
    awsServerlessExpress.proxy(server, event, context);
};

Your lambda function should contain your build/ folder, the node_modules/ for the aws-serverless-express package, and an index.js with the code mentioned above:

build/
node_modules/
index.js

I have a very basic proxy API Gateway which triggers the Lambda and the only problem is indeed the stage that API Gateway enforces. Every static asset is being served from the root (without stage path). API Gateway returns a Forbidden for any static asset you try to access.

We set up a custom domain quickly and got rid of the issue and I would recommend you do so as well.

I would love to use Serverless framework for this (like @sean) but unfortunately I’m just a contractor for a customer who has an AWS account, so I can’t create access keys with high enough permissions. Serverless would help me set up API GW + Lambda and a custom domain without having to set them up via CLI\Console, but it requires so many permissions that I can’t use it here.

However, since we just need an API and Lambda, it takes you approximately 10 minutes to set it up via CLI or the console.

Thanks for this info :arrow_double_up: :arrow_double_up:

That’s awesome @Rcls! Thanks for sharing :slightly_smiling_face:

We’ll try to fix that problem once we add this to the core.