Deploy to AWS Lambda?

That sounds great :blush:

Glad to hear this as well!

As soon as you have something to show, we would love to take see your project live! In addition, @Reyes is actively looking for showcases for the newsletter.

1 Like

@phoebus if you are still interested in testing AWS Lambdas let me know and I’ll add the handler to our server.

It’s something that’s been on our roadmap for a while but we didn’t do it due to a lack of time. If you are willing to help us testing it out, I’ll add the handler right away :slight_smile:

Hey guys, @ luisherranz really appreciate the good work. I have a leading news portal project in India for which I’m looking for a similar solution for which I could deploy this solution on AWS Lambda. Currently I’m just testing out building my new blog. I already have setup a headless wordpress cms on AWS. I have done some modifications with existing theme on Frontity framework and seems working fine running on localhost. I have created a build with server.js analyze and static folders. Now I’m stuck on deploying to AWS lambda. I tried to import server.js as a lambda function and deploy it. Could you please help me out with this ?. kindly help with the steps.

Could you please try using this wrapper around the server.js file?

Something 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);
};

Let me know if it works and if it does, we’ll add it Frontity itself so you don’t have to use a wrapper.

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

1 Like

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