How to configure SSR in Nuxt3 and deploy it with Serverless Architecture

SEO optimization, high performance, and cost-effective scaling.
Sep 28 2023 · 9 min read

Background

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.

Why SSR(Server-Side-Rendering)?

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.

Why Serverless Approach?

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.

Prerequisites

Before we begin, make sure you have the following:

  • Node.js and npm are installed on your development machine.
  • A Nuxt 3 project set up or just create it with npx nuxi@latest init <project-name> .

Nitro Overview

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!

AWS Lambda Configuration using Nitro

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

Add your code to s3

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
  • test_lambda.zip contains the actual code of the website which will be deployed on AWS Lambda.
  • aws s3 cp command will copy the zip file to AWS S3.

Create Cloudformation stack using AWS CLI

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.

Validate Cloudformation template

- 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.

Create Cloudformation stack using AWS CLI

- 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
  • stack-name: A name of the stack we want to create(can be customized).
  • template-file: Path of the Cloudformation template
  • parameter-overrides: Parameters we want to override in the Cloudformation template
  • capabilities: A list of capabilities required to permit AWS Cloudformation to create certain resources like IAM.

Visit Cloudformation stack deploy using AWS CLI for more information.

Create AWS Lambda Execution Role

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.

  • LambdaExecutionRole — A Lambda execution role resource name(can be customized).
  • AWS::IAM::Role — it represents the type of resource we want to create(just like a type of variable), it will be different for different resources.
  • Properties — it gives several options like RoleName, RoleArn, AssumeRolePolicyDocument, ManagePolicyArns, etc.

Explore more at Cloudformation IAM Role template format for more details.

Create AWS Lambda function using Cloudformation

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"
  • LambdaFunction — A Lambda function resource name(custom).
  • AWS::Lambda::Function — It’s the default type for creating a lambda function.
  • Properties — it consists of several entities like FunctionName, Runtime, Code, Handler, Role, Environment, etc.

Visit the AWS Lambda function template Cloudformation to get deeper details.

Create API Gateway resource using Cloudformation

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}
  • ApiGateway — A name for API gateway resource.
  • AWS::ApiGateway::RestApi — It’s the default type for creating an API Gateway.
  • Properties — It consists of several attributes like Description, EndpointConfiuration, Name, DisableExecuteApiEndpoint, etc.

Notes:

  • If you add 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.
  • If you want the API Gateway should respond to all kinds of files like .png or .jpg you just need to provide the BinaryMediaTypes field.

Refer API Gateway RestAPI template for more details.

Create API Gateway method, resource, and proxy using Cloudformation

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
  • ApiGatewayRootMethod — It’s the name of the API Gateway resource
  • AWS::ApiGateway::Method — API Gateway methods that define the parameters and body that clients must send in their requests
  • Properties — It consists of several attributes like AuthorizationType, HTTPMethod, Integration, RestApiId, etc.

API Gateway Resource

Resources:
  ApiGatewayRootResource:
    Type: AWS::ApiGateway::Resource
    DependsOn:
      - ApiGatewayRootMethod
    Properties:
      RestApiId: !Ref ApiGateway
      ParentId: !GetAtt ApiGateway.RootResourceId
      PathPart: "{proxy+}"
  • ApiGatewayRootResource — API Gateway resource name.
  • AWS::ApiGateway::Resource — It creates a resource for an API.
  • Properties — It consists of properties like RestApiId, ParentId, PathPart, etc.

AWS Lambda proxy method

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.

Lambda permission

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
  • LambdaApiGatewayInvoke — A name of Lambda function permission resource.
  • AWS::Lambda::Permission — It grants an AWS service or another account permission to use a function.
  • Properties — It consists of attributes like Action, FucntionName, Principal, SourceArn, etc.
  • ApiGatewayDeployment — A name of API Gateway deployment resource.
  • AWS::ApiGateway::Deployment — It deploys an API Gateway RestApi resource to a stage so that clients can call the API over the internet
  • Properties — It consists of attributes like RestApiId, StageName, etc.

Visit Lambda Invoke Permission and API Gateway Deployment for more information.

Configure custom domain to access API Gateways using Cloudformation(optional)

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
  • ApiGatewayCustomDomainMapping — A name for custom domain mapping resource.
  • AWS::ApiGateway::BasePathMapping — It creates a base path that clients who call your API must use in the invocation URL
  • Properties — It consists of attributes such as DomainName, RestApiId, and Stage.

Refer CustomDomain mapping template and API Gateway Custom domain mapping for more details.

Sitemap

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.

Conclusion

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!! 👋

Similar Articles


dharti-r image
Dharti Ramoliya
Web developer at canopas | Eager to Elevate Your Web Experience


dharti-r image
Dharti Ramoliya
Web developer at canopas | Eager to Elevate Your Web Experience

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.