Serverless: In a serverless architecture, developers write and deploy code in the form of functions or services without the need to manage servers or worry about infrastructure scaling.
Server-Side Rendering (SSR): It is a technique used in web development to render web pages on the server and then send the fully rendered HTML to the client(browser).
This is in contrast to traditional client-side rendering (CSR), where a web page’s HTML structure is initially delivered to the browser, and then JavaScript is used to populate and render the content.
Using Nuxt 3 for SSR with a serverless architecture combines the benefits of a powerful SSR framework with the ease of serverless deployment. This combination can lead to faster development, improved SEO, enhanced user experience, and cost-effective scaling for your web applications.
We know that achieving goals and improving quality of life can be a challenging process. Justly is designed to provide the guidance and support needed to achieve your goals.
SSR comes with several benefits as below.
1. SEO and Performance: SSR can improve SEO because search engines can crawl fully-rendered HTML content. It can also improve initial page load performance by sending pre-rendered content to the client.
2. Full Control: With SSR, you have more control over the rendering process, allowing you to customize how content is served to users.
3. Offline Support: SSR can provide better offline support because some content is pre-rendered on the server and can be cached for offline use.
4. Security: SSR can enhance security by keeping sensitive code and data on the server, reducing exposure to client-side attacks.
5. Complexity: SSR can simplify some aspects of web development, particularly when dealing with data fetching and authentication.
1. Scalability: Serverless architectures, such as AWS Lambda or Azure Functions, automatically scale based on incoming traffic. You only pay for the actual computing resources used, making it cost-efficient.
2. Cost Efficiency: With serverless, you don’t need to maintain and pay for dedicated servers. You’re billed per execution, which can be more cost-effective for low to moderate-traffic applications.
3. Deployment and Maintenance: Serverless providers handle server maintenance and updates, reducing the operational overhead for your development team.
4. Flexibility: Serverless allows you to focus on writing code and building features without worrying about infrastructure. It’s well-suited for event-driven and microservices architectures.
Before we begin, make sure you have the following:
npx nuxi@latest init <project-name>
.Nitro can generate different output formats suitable for different hosting providers from the same code base.
Using built-in presets, you can easily configure Nitro to adjust its output format with almost no additional code or configuration!
A Nuxt application can be deployed on a Node.js server, pre-rendered for static hosting, or deployed to serverless or edge (CDN) environments.
When running nuxt build
with the Node server preset, the result will be an entry point that launches a ready-to-run Node server.
> node .output/server/index.mjs
Nitro provides a built-in preset to generate an output format compatible with AWS Lambda.
The output entry point in .output/server/index.mjs
is compatible with AWS Lambda format.
Nitro output, by default, uses dynamic chunks for lazy loading code only when needed. However, this sometimes can not be ideal for performance. You can enable chunk inlining behavior using inlineDynamicImports
config.
nuxt.config.ts
import { defineNuxtConfig } from "nuxt/config";
export default defineNuxtConfig({
app: {
head: {
title: "Title of your website",
htmlAttrs: {
lang: "en",
},
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ hid: "description", name: "description", content: "" },
{ name: "format-detection", content: "telephone=no" },
],
},
},
devtools: { enabled: true },
nitro: {
preset: "aws-lambda",
inlineDynamicImports: true,
serveStatic: true,
},
});
For making a lambda function to serve our code, we need to give it a zip of our code. Let’s upload the code zip to s3(a smaller zip can be uploaded without s3 as well).
Install aws-cli on your machine and execute the following commands to set up AWS CLI operations.
- aws configure set aws_access_key_id your_aws_acces_key_id
- aws configure set aws_secret_access_key your_aws_secret_access_key
- aws configure set region your_aws_region
- aws s3 cp test_lambda.zip s3://your_aws_bucket
aws s3 cp
command will copy the zip file to AWS S3.As we have our code handy at s3, let’s address it and create a Clouformation stack. But, Before creating the stack directly I would recommend validating the template, that we will use for stack creation.
- aws cloudformation validate-template --template-body file://serverless_deployment.yml
After a successful validation check, let’s create a stack. If any misconfiguration will be there, validation will fail with errors.
- aws cloudformation deploy --stack-name test-lambda-stack --template-file serverless_deployment.yml --parameter-overrides
CustomDomainName=example.com ApiGatewayStageName=prod ApiGatewayName=test-web-api
LambdaName=test-website-lambda LambdaBucket=lambda-test-bucket LambdaUrl=test_lambda.zip LambdaRoleArnName=LambdaRole-TestWeb
LambdaFunctionArnName=LambdaArn-TestWeb LambdaRoleName=test-website-lambda-role
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM
Visit Cloudformation stack deploy using AWS CLI for more information.
Let’s create a Lambda execution role, that is required to develop the Lambda function.
Clouformation creates resources from the Resources attributes of the template and gives outcomes as Outputs attributes of the template.
serverless_deployment.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
Test website SSR with AWS Lambda
Parameters:
LambdaBucket:
Type: String
Description: AWS bucket, where lambda function is stored
LambdaUrl:
Type: String
Description: Path of lambda
LambdaRoleName:
Type: String
Description: Name of the lambda role
LambdaName:
Type: String
Description: Name of the lambda function
LambdaRoleArnName:
Type: String
Description: ARN Name of the role attached with lambda function
LambdaFunctionArnName:
Type: String
Description: Name of the lambda function ARN
ApiGatewayStageName:
Type: String
Description: ApiGateway stage name
ApiGatewayName:
Type: String
Description: ApiGateway name
CustomDomainName:
Type: String
Description: Name of the custom domain
Outputs:
LambdaRoleARN:
Description: Role for Lambda execution.
Value:
Fn::GetAtt:
- LambdaExecutionRole
- Arn
Export:
Name: !Ref "LambdaRoleArnName"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref "LambdaRoleName"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AWSLambdaExecute"
- "arn:aws:iam::aws:policy/AmazonS3FullAccess"
- "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
- "arn:aws:iam::aws:policy/AmazonKinesisFullAccess"
Path: "/"
The above snippet will create a resource LambdaExecutionRole, which will be used to grant required permissions lambda function.
Explore more at Cloudformation IAM Role template format for more details.
Let’s create a Lambda function, that is required to run or code without setting up the development environment.
As we have seen in the creation of the LambdaExecutionRole, we will need resources and outputs for Lambda, too.
Resources:
LambdaFunction:
Type: AWS::Lambda::Function
DependsOn:
- LambdaExecutionRole
Properties:
FunctionName: !Ref "LambdaName"
Description: LambdaFunction for test website
Runtime: "nodejs18.x"
Code:
S3Bucket: !Ref "LambdaBucket"
S3Key: !Ref "LambdaUrl"
Handler: .output/server/index.handler
MemorySize: 256
Timeout: 100
Role: !GetAtt "LambdaExecutionRole.Arn"
Visit the AWS Lambda function template Cloudformation to get deeper details.
Now as we already created a lambda function, it’s time to integrate it with API Gateway.
Let’s create an API gateway resource using Cloudformation.
Resources:
ApiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Description: Test Web API Gateway
EndpointConfiguration:
Types:
- REGIONAL
BinaryMediaTypes: ["*/*"]
DisableExecuteApiEndpoint: false
MinimumCompressionSize: 100
Name: !Ref "ApiGatewayName"
Outputs:
ApiGatewayInvokeURL:
Value: !Sub https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${ApiGatewayStageName}
Notes:
DisableExecuteApiEndpoint: true
, the endpoint returned by API Gateway(default) won’t work. So, if you want to integrate a custom domain with an API gateway, you can make it true otherwise it should be false.Refer API Gateway RestAPI template for more details.
We have already created API Gateway, now it’s time to saturate it with required details like root method and proxy method.
API Gateway Method
Resources:
ApiGatewayRootMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: ANY
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt LambdaFunction.Arn
ResourceId: !GetAtt ApiGateway.RootResourceId
RestApiId: !Ref ApiGateway
Resources:
ApiGatewayRootResource:
Type: AWS::ApiGateway::Resource
DependsOn:
- ApiGatewayRootMethod
Properties:
RestApiId: !Ref ApiGateway
ParentId: !GetAtt ApiGateway.RootResourceId
PathPart: "{proxy+}"
Resources:
ApiGatewayResourceProxyMethod:
Type: AWS::ApiGateway::Method
DependsOn:
- ApiGatewayRootResource
Properties:
AuthorizationType: NONE
HttpMethod: ANY
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt LambdaFunction.Arn
ResourceId: !Ref ApiGatewayRootResource
RestApiId: !Ref ApiGateway
It’s used to enable APIs to call a Lambda function with a single integration setup on a catch-all ANY method.
For example,
CRUD operations consist of multiple request methods such as POST, GET, PUT, and DELETE and we don’t need to specify all of them to invoke our lambda.
Create a Lambda permission to grant API Gateway permission to invoke the Lambda function using Cloudformation
As we are already done with the API Gateway configuration.
Let’s permit our API Gateway to invoke the earlier created Lambda function and deploy it.
Resources:
LambdaApiGatewayInvoke:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGateway}/*/*/*
ApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- ApiGatewayResourceProxyMethod
Properties:
RestApiId: !Ref ApiGateway
StageName: !Ref ApiGatewayStageName
RestApi
resource to a stage so that clients can call the API over the internetVisit Lambda Invoke Permission and API Gateway Deployment for more information.
By default, API Gateway provides a default route using which we can access our application routes, but it keeps changing on every deployment.
Hence, we need to bind it with the custom domain, which will remain constant, doesn’t matter API gateway changes though.
Resources:
ApiGatewayCustomDomainMapping:
Type: AWS::ApiGateway::BasePathMapping
DependsOn:
- ApiGatewayDeployment
Properties:
DomainName: !Ref CustomDomainName
RestApiId: !Ref ApiGateway
Stage: !Ref ApiGatewayStageName
Refer CustomDomain mapping template and API Gateway Custom domain mapping for more details.
A sitemap is a valuable tool for website owners and webmasters as it provides a structured list of web pages on a site, allowing search engines and users to easily navigate and understand the website’s structure.
nuxt.config.ts
import { defineNuxtConfig } from "nuxt/config";
export default defineNuxtConfig({
app: {
head: {
title: "Title of your website",
htmlAttrs: {
lang: "en",
},
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ hid: "description", name: "description", content: "" },
{ name: "format-detection", content: "telephone=no" },
],
},
},
devtools: { enabled: true },
nitro: {
preset: "aws-lambda",
inlineDynamicImports: true,
serveStatic: true,
},
sitemap: {
urls: [
{
loc: "/",
changefreq: "monthly",
lastmod: new Date(),
priority: 1,
},
{
loc: "/resources",
changefreq: "monthly",
lastmod: new Date(),
priority: 0.8,
},
{
loc: "/community",
changefreq: "monthly",
lastmod: new Date(),
priority: 0.9,
},
{
loc: "/blog",
changefreq: "daily",
lastmod: new Date(),
priority: 0.9,
},
],
},
});
Overall, sitemaps contribute to a more efficient and effective online presence by aiding in SEO efforts, enhancing user experience, and assisting in content management and organization. They are a valuable tool for both website owners and their visitors.
In this blog post, we’ve explored how to deploy a serverless SSR website using Nuxt 3 and AWS Lambda. This approach combines the benefits of serverless computing with the power of Nuxt 3’s SSR capabilities. It simplifies the deployment process and allows you to focus on building exceptional web experiences.
Serverless SSR is a great choice for applications that require SEO optimization, high performance, and cost-effective scaling. As you continue to develop your Nuxt 3 projects, consider serverless deployment options to streamline your development workflow and improve your users’ experience.
Thanks for reading!! 👋