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.
Hey, thatās great. I am glad itās working!
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.
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 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
@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ā¦
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
Yeah, weād love to add official support
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!
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
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
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!
Thatās awesome Congratulations
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.