sst / open-next

Open source Next.js serverless adapter

Home Page:https://open-next.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cloudformation template

emulienfou opened this issue · comments

Trying to deploy Nextjs 13 using open-next with Serverless/Cloudformation.

Currently unsuccessful, but this is the start template I currently got, hope this could help some of you and can even help me debug it and make it work.

  1. First of you will need the next 3 Serverless plugins to execute scripts, sync build to S3 and configure Lambda@Edge:
npm i --save @silvermine/serverless-plugin-cloudfront-lambda-edge serverless-s3-sync serverless-scriptable-plugin
  1. Here is the full serverless config template file:
service: nextjs-app
useDotenv: true

plugins:
  - serverless-scriptable-plugin
  - serverless-s3-sync
  - '@silvermine/serverless-plugin-cloudfront-lambda-edge'

custom:
  scriptable:
    hooks:
      before:package:createDeploymentArtifacts:
        - npx open-next@latest build
        - mkdir -p ./.open-next/zips
        - cd .open-next/server-function && zip -r ../zips/server-function.zip .
        - cd .open-next/image-optimization-function && zip -r ../zips/image-optimization-function.zip .
  s3Sync:
    - bucketName: ${self:service}-assets
      localDir: .open-next/assets

package:
  individually: true

provider:
  name: aws
  runtime: nodejs16.x
  stage: ${opt:stage, 'dev'}
  region: us-east-1
  endpointType: REGIONAL
  apiGateway:
    shouldStartNameWithService: true
    binaryMediaTypes:
      - "*/*"

functions:
  default:
    description: Default Lambda for Next CloudFront distribution
    name: ${self:service}-${self:provider.stage}-default
    handler: index.handler
    runtime: nodejs16.x
    memorySize: 512
    timeout: 10
    package:
      artifact: .open-next/zips/server-function.zip
    lambdaAtEdge:
      - distribution: DefaultDistribution
        eventType: origin-request
        includeBody: true
      - distribution: DefaultDistribution
        eventType: origin-response
        includeBody: false
      - distribution: DefaultDistribution
        eventType: origin-request
        includeBody: true
        pathPattern: /api/*
      - distribution: DefaultDistribution
        eventType: origin-request
        includeBody: true
        pathPattern: /_next/data/*
      - distribution: DefaultDistribution
        eventType: origin-response
        includeBody: false
        pathPattern: /_next/data/*
  imageOptimization:
    description: Image Lambda for Next CloudFront distribution
    name: ${self:service}-${self:provider.stage}-image-optimization
    handler: index.handler
    runtime: nodejs16.x
    memorySize: 512
    timeout: 10
    package:
      artifact: .open-next/zips/image-optimization-function.zip
    lambdaAtEdge:
      - distribution: DefaultDistribution
        eventType: origin-request
        includeBody: true
        pathPattern: /_next/image

resources:
  Resources:
    DefaultDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          PriceClass: PriceClass_All
          Origins:
            - DomainName: 'nextjs-app-assets.s3.us-east-1.amazonaws.com'
              Id: ${self:service}
              CustomOriginConfig:
                HTTPPort: '80'
                HTTPSPort: '443'
                OriginProtocolPolicy: http-only
          DefaultCacheBehavior:
            MinTTL: 0
            DefaultTTL: 0
            MaxTTL: 31536000
            TargetOriginId: ${self:service}
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods: [ 'GET', 'HEAD', 'OPTIONS' ]
            CachedMethods: [ 'HEAD', 'GET' ]
            Compress: true
            ForwardedValues:
              QueryString: true
              Headers:
                  - x-op-middleware-request-headers
                - x-op-middleware-response-headers
                - x-nextjs-data
                - x-middleware-prefetch
              Cookies:
                Forward: all
          CacheBehaviors:
            - TargetOriginId: ${self:service}
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/static/*
              Compress: true
              AllowedMethods: [ 'GET', 'HEAD', 'OPTIONS' ]
              CachedMethods: [ 'HEAD', 'GET' ]
              ForwardedValues:
                QueryString: false
            - TargetOriginId: ${self:service}
              ViewerProtocolPolicy: https-only
              PathPattern: /api/*
              AllowedMethods: [ 'GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'PATCH', 'DELETE' ]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers: [ 'Authorization', 'Host', 'Accept-Language' ]
            - TargetOriginId: ${self:service}
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/image
              AllowedMethods: [ 'GET', 'HEAD', 'OPTIONS', 'PUT', 'POST', 'PATCH', 'DELETE' ]
              ForwardedValues:
                QueryString: false
                Headers: [ 'Accept' ]
            - TargetOriginId: ${self:service}
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/data/*
              AllowedMethods: [ 'GET', 'HEAD' ]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers:
                  - x-op-middleware-request-headers
                  - x-op-middleware-response-headers
                  - x-nextjs-data
                  - x-middleware-prefetch

Currently unsuccessful, but this is the start template I currently got, hope this could help some of you and can even help me debug it and make it work.

What isn't working?

Base version of Serverless/CloudFormation, without the Middleware Edge function:

# Service name
service: next-app

# Ensure configuration validation issues fail the command (safest option)
configValidationMode: error

# Package individually as multiple lambdas created
package:
  individually: true

# Define plugins
plugins:
  - serverless-scriptable-plugin
  - serverless-s3-sync

provider:
  name: aws
  region: us-west-2

  # Use direct deployments (faster). This is going to become the default in v4.
  # See https://www.serverless.com/framework/docs/providers/aws/guide/deploying#deployment-method
  deploymentMethod: direct

  # Ensure Lambdas can access Assets S3 Bucket
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - "s3:GetObject"
          Resource:
            - "arn:aws:s3:::${self:service}-assets/*"

functions:
  imageOptimization:
    name: ${self:service}-image-optimization
    description: Image Optimization Lambda for Next.js App
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 1024
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/image-optimization-function.zip
    # Set S3 BUCKET_NAME for Image Optimization Lambda to use
    environment:
      BUCKET_NAME: ${self:service}-assets
  server:
    name: ${self:service}-server
    description: Server Lambda for Next.js App
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 512
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/server-function.zip

custom:
  scriptable:
    hooks:
      before:package:createDeploymentArtifacts:
        - npx nx run build
        - mkdir -p ./.open-next/zips
        - cd .open-next/server-function && zip -r ../zips/server-function.zip .
        - cd .open-next/image-optimization-function && zip -r ../zips/image-optimization-function.zip .
  s3Sync:
    - bucketName: ${self:service}-assets
      localDir: .open-next/assets
      params: # Cache control
        # Un-hashed files, should be cached at the CDN level, but not at the browser level
        - "**/*":
            CacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate"
        # Hashed files, should be cached both at the CDN level and at the browser level
        - "_next/**/*":
            CacheControl: "public,max-age=31536000,immutable"

resources:
  Description: Next App Infrastructure
  Resources:
    # S3 Bucket for assets
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-assets
    # S3 Bucket Policy to allow access from CloudFront Origin Access Control (OAC)
    AssetsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref AssetsBucket
        PolicyDocument:
          Statement:
            - Action: s3:GetObject
              Effect: Allow
              Resource: !Sub ${AssetsBucket.Arn}/*
              Principal:
                Service: cloudfront.amazonaws.com
              Condition:
                StringEquals:
                  AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          PriceClass: PriceClass_All
          # List of origins. S3 Bucket, Server function, and Image Optimization function
          Origins:
            - Id: StaticAssetOrigin
              DomainName: !GetAtt AssetsBucket.DomainName
              S3OriginConfig:
                OriginAccessIdentity: ""
              OriginAccessControlId: !GetAtt CloudFrontAccessToS3Bucket.Id
            - Id: ImageOptimizationFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ImageOptimizationLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
            - Id: ServerFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ServerLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
          # We need a "failover" Origin Group to try the "Server function" origin first, then fallback to the S3 bucket origin if the server function fails
          OriginGroups:
            Quantity: 1
            Items:
              - Id: ServerAndStaticAssetOriginGroup
                FailoverCriteria:
                  StatusCodes:
                    Quantity: 2
                    # TODO: Not sure if these are the correct error codes to use...
                    Items:
                      - 500
                      - 502
                Members:
                  Quantity: 2
                  Items:
                    - OriginId: ServerFunctionOrigin
                    - OriginId: StaticAssetOrigin
          # Default is used by /* resources
          DefaultCacheBehavior:
            MinTTL: 0
            DefaultTTL: 0
            MaxTTL: 31536000
            TargetOriginId: ServerAndStaticAssetOriginGroup
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods: ["GET", "HEAD", "OPTIONS"]
            CachedMethods: ["HEAD", "GET"]
            Compress: true
            ForwardedValues:
              QueryString: true
              Headers:
                - x-op-middleware-request-headers
                - x-op-middleware-response-headers
                - x-nextjs-data
                - x-middleware-prefetch
              Cookies:
                Forward: all
          CacheBehaviors:
            - TargetOriginId: StaticAssetOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/static/*
              Compress: true
              AllowedMethods: ["GET", "HEAD", "OPTIONS"]
              CachedMethods: ["HEAD", "GET"]
              ForwardedValues:
                QueryString: false
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /api/*
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers: ["Authorization", "Host", "Accept-Language"]
            - TargetOriginId: ImageOptimizationFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/image
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Headers: ["Accept"]
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/data/*
              AllowedMethods: ["GET", "HEAD"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers:
                  - x-op-middleware-request-headers
                  - x-op-middleware-response-headers
                  - x-nextjs-data
                  - x-middleware-prefetch
    CloudFrontAccessToS3Bucket:
      Type: AWS::CloudFront::OriginAccessControl
      Properties:
        OriginAccessControlConfig:
          Name: CloudFrontAccessToS3BucketOriginAccess
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4

I'm in the process of moving this to Terraform, does anyone have a CloudFormation template that includes middleware? Still trying to understand how that works. The docs mention API Gateway, but I cannot find any information on that.

Thanks for the template @teriu, super helpful!

So, to add middleware, that function needs to be added as a viewer request lambda function association on the default and /_next/data/* behavior.

commented

Base version of Serverless/CloudFormation, without the Middleware Edge function:

# Service name
service: next-app

# Ensure configuration validation issues fail the command (safest option)
configValidationMode: error

# Package individually as multiple lambdas created
package:
  individually: true

# Define plugins
plugins:
  - serverless-scriptable-plugin
  - serverless-s3-sync

provider:
  name: aws
  region: us-west-2

  # Use direct deployments (faster). This is going to become the default in v4.
  # See https://www.serverless.com/framework/docs/providers/aws/guide/deploying#deployment-method
  deploymentMethod: direct

  # Ensure Lambdas can access Assets S3 Bucket
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - "s3:GetObject"
          Resource:
            - "arn:aws:s3:::${self:service}-assets/*"

functions:
  imageOptimization:
    name: ${self:service}-image-optimization
    description: Image Optimization Lambda for Next.js App
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 1024
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/image-optimization-function.zip
    # Set S3 BUCKET_NAME for Image Optimization Lambda to use
    environment:
      BUCKET_NAME: ${self:service}-assets
  server:
    name: ${self:service}-server
    description: Server Lambda for Next.js App
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 512
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/server-function.zip

custom:
  scriptable:
    hooks:
      before:package:createDeploymentArtifacts:
        - npx nx run build
        - mkdir -p ./.open-next/zips
        - cd .open-next/server-function && zip -r ../zips/server-function.zip .
        - cd .open-next/image-optimization-function && zip -r ../zips/image-optimization-function.zip .
  s3Sync:
    - bucketName: ${self:service}-assets
      localDir: .open-next/assets
      params: # Cache control
        # Un-hashed files, should be cached at the CDN level, but not at the browser level
        - "**/*":
            CacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate"
        # Hashed files, should be cached both at the CDN level and at the browser level
        - "_next/**/*":
            CacheControl: "public,max-age=31536000,immutable"

resources:
  Description: Next App Infrastructure
  Resources:
    # S3 Bucket for assets
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-assets
    # S3 Bucket Policy to allow access from CloudFront Origin Access Control (OAC)
    AssetsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref AssetsBucket
        PolicyDocument:
          Statement:
            - Action: s3:GetObject
              Effect: Allow
              Resource: !Sub ${AssetsBucket.Arn}/*
              Principal:
                Service: cloudfront.amazonaws.com
              Condition:
                StringEquals:
                  AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          PriceClass: PriceClass_All
          # List of origins. S3 Bucket, Server function, and Image Optimization function
          Origins:
            - Id: StaticAssetOrigin
              DomainName: !GetAtt AssetsBucket.DomainName
              S3OriginConfig:
                OriginAccessIdentity: ""
              OriginAccessControlId: !GetAtt CloudFrontAccessToS3Bucket.Id
            - Id: ImageOptimizationFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ImageOptimizationLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
            - Id: ServerFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ServerLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
          # We need a "failover" Origin Group to try the "Server function" origin first, then fallback to the S3 bucket origin if the server function fails
          OriginGroups:
            Quantity: 1
            Items:
              - Id: ServerAndStaticAssetOriginGroup
                FailoverCriteria:
                  StatusCodes:
                    Quantity: 2
                    # TODO: Not sure if these are the correct error codes to use...
                    Items:
                      - 500
                      - 502
                Members:
                  Quantity: 2
                  Items:
                    - OriginId: ServerFunctionOrigin
                    - OriginId: StaticAssetOrigin
          # Default is used by /* resources
          DefaultCacheBehavior:
            MinTTL: 0
            DefaultTTL: 0
            MaxTTL: 31536000
            TargetOriginId: ServerAndStaticAssetOriginGroup
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods: ["GET", "HEAD", "OPTIONS"]
            CachedMethods: ["HEAD", "GET"]
            Compress: true
            ForwardedValues:
              QueryString: true
              Headers:
                - x-op-middleware-request-headers
                - x-op-middleware-response-headers
                - x-nextjs-data
                - x-middleware-prefetch
              Cookies:
                Forward: all
          CacheBehaviors:
            - TargetOriginId: StaticAssetOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/static/*
              Compress: true
              AllowedMethods: ["GET", "HEAD", "OPTIONS"]
              CachedMethods: ["HEAD", "GET"]
              ForwardedValues:
                QueryString: false
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /api/*
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers: ["Authorization", "Host", "Accept-Language"]
            - TargetOriginId: ImageOptimizationFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/image
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Headers: ["Accept"]
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/data/*
              AllowedMethods: ["GET", "HEAD"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers:
                  - x-op-middleware-request-headers
                  - x-op-middleware-response-headers
                  - x-nextjs-data
                  - x-middleware-prefetch
    CloudFrontAccessToS3Bucket:
      Type: AWS::CloudFront::OriginAccessControl
      Properties:
        OriginAccessControlConfig:
          Name: CloudFrontAccessToS3BucketOriginAccess
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4

I'm getting Access Denied from cloudfront url after using this exact config. Is it working for everybody else?

@mrunbanked have you tried going to a public asset such as <your url>/_next/static/favicon.ico (if it exists?) access denied there would mean that the distribution itself does not have access the the S3 bucket.

I have it working on Terraform, and your template is almost identical, with the slight difference that

  1. I'm using an origin access identity for my S3 origin config on the static origin.
  2. I'm using that origin access identity's ARN as the AWS principal for the S3 bucket policy.
  3. I'm using RegionalDomainName rather than DomainName in the DomainName parameter of the static origin.

https://docs.aws.amazon.com/whitepapers/latest/secure-content-delivery-amazon-cloudfront/s3-origin-with-cloudfront.html

commented

Ok seems like something was wrong with my package versions so server wasn't working properly. After updating all the packages it works! thanks 🙏

I am now using:

# Service name
service: abcabcabc

useDotenv: true

plugins:
  - serverless-scriptable-plugin
  - serverless-s3-sync
  #- '@silvermine/serverless-plugin-cloudfront-lambda-edge'

package:
  individually: true

provider:
  name: aws
  region: "${env:AWS_DEFAULT_REGION}" # Resource handler returned message: "Invalid request provided: AWS::CloudFront::Distribution: The function must be in region 'us-east-1'
  endpointType: REGIONAL
  apiGateway:
    shouldStartNameWithService: true
    binaryMediaTypes:
      - "*/*"

custom:
  scriptable:
    hooks:
      before:package:createDeploymentArtifacts:
        - OPEN_NEXT_DEBUG=true npx open-next@latest build
        - mkdir -p ./.open-next/zips
        - cd .open-next/server-function && zip -r ../zips/server-function.zip .
        - cd .open-next/image-optimization-function && zip -r ../zips/image-optimization-function.zip .
  s3Sync:
    - bucketName: ${self:service}-assets
      localDir: .open-next/assets
      acl: public-read # optional
      params: # Cache control
        # Un-hashed files, should be cached at the CDN level, but not at the browser level
        - "**/*":
            CacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate"
        # Hashed files, should be cached both at the CDN level and at the browser level
        - "_next/**/*":
            CacheControl: "public,max-age=31536000,immutable"
  siteName: "${env:SUBDOMAIN}"
  aliasHostedZoneId: Z2FDTNDATAQYW2     # us-east-1

functions:
  server:
    description: Default Lambda for Next CloudFront distribution
    name: "${env:WEB_LAMBDA}"
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 512
    timeout: 10
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/server-function.zip
  imageOptimization:
    description: Image Lambda for Next CloudFront distribution
    name: "${env:IMAGE_LAMBDA}"
    handler: index.handler
    runtime: nodejs18.x
    architecture: arm64
    memorySize: 512
    timeout: 10
    # We need a Function URL to use for the CloudFront Origin URL
    url: true
    package:
      artifact: .open-next/zips/image-optimization-function.zip
    # Set S3 BUCKET_NAME for Image Optimization Lambda to use
    environment:
      BUCKET_NAME: ${self:service}-assets

resources:
  Resources:
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-assets
        AccessControl: PublicRead
    # S3 Bucket Policy to allow access from CloudFront Origin Access Control (OAC)
    AssetsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref AssetsBucket
        PolicyDocument:
          Statement:
            - Action: s3:GetObject
              Effect: Allow
              Resource: !Sub ${AssetsBucket.Arn}/*
              Principal:
                Service: cloudfront.amazonaws.com
              Condition:
                StringEquals:
                  AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${DefaultDistribution}
    DefaultDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          PriceClass: PriceClass_100
          ViewerCertificate:
            AcmCertificateArn: "AAAAAAAARRRRRRRRRRRRRRNNNNNNNNNNN"
            MinimumProtocolVersion: TLSv1.1_2016
            SslSupportMethod: sni-only
          Aliases: ["${env:SUBDOMAIN}"]
          Origins:
            - Id: ServerFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ServerLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
            - Id: StaticAssetOrigin
              DomainName: !GetAtt AssetsBucket.RegionalDomainName
              S3OriginConfig:
                OriginAccessIdentity: ""
              OriginAccessControlId: !GetAtt CloudFrontAccessToS3Bucket.Id
            - Id: ImageOptimizationFunctionOrigin
              # Remove https:// from URL
              DomainName: !Select [2, !Split ["/", !GetAtt ImageOptimizationLambdaFunctionUrl.FunctionUrl]]
              CustomOriginConfig:
                HTTPSPort: 443
                OriginProtocolPolicy: https-only
          # We need a "failover" Origin Group to try the "Server function" origin first, then fallback to the S3 bucket origin if the server function fails
          OriginGroups:
            Quantity: 1
            Items:
              - Id: ServerAndStaticAssetOriginGroup
                FailoverCriteria:
                  StatusCodes:
                    Quantity: 2
                    # TODO: Not sure if these are the correct error codes to use...
                    Items:
                      - 500
                      - 502
                Members:
                  Quantity: 2
                  Items:
                    - OriginId: ServerFunctionOrigin
                    - OriginId: StaticAssetOrigin
          # Default is used by /* resources
          DefaultCacheBehavior:
            MinTTL: 0
            DefaultTTL: 0
            MaxTTL: 31536000
            TargetOriginId: ServerAndStaticAssetOriginGroup
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods: ["GET", "HEAD", "OPTIONS"]
            CachedMethods: ["HEAD", "GET"]
            Compress: true
            ForwardedValues:
              QueryString: true
              Headers:
                - x-op-middleware-request-headers
                - x-op-middleware-response-headers
                - x-nextjs-data
                - x-middleware-prefetch
              Cookies:
                Forward: all
          CacheBehaviors:
            - TargetOriginId: StaticAssetOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/static/*
              Compress: true
              AllowedMethods: ["GET", "HEAD", "OPTIONS"]
              CachedMethods: ["HEAD", "GET"]
              ForwardedValues:
                QueryString: false
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /api/*
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers: ["Authorization", "Host", "Accept-Language"]
            - TargetOriginId: ImageOptimizationFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/image
              AllowedMethods:
                ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
              ForwardedValues:
                QueryString: true
                Headers: ["Accept"]
            - TargetOriginId: ServerFunctionOrigin
              ViewerProtocolPolicy: https-only
              PathPattern: /_next/data/*
              AllowedMethods: ["GET", "HEAD"]
              ForwardedValues:
                QueryString: true
                Cookies:
                  Forward: all
                Headers:
                  - x-op-middleware-request-headers
                  - x-op-middleware-response-headers
                  - x-nextjs-data
                  - x-middleware-prefetch
    CloudFrontAccessToS3Bucket:
      Type: AWS::CloudFront::OriginAccessControl
      Properties:
        OriginAccessControlConfig:
          Name: CloudFrontAccessToS3BucketOriginAccess
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4
    FrontPageDNSName:
      Type: "AWS::Route53::RecordSet"
      Properties:
        AliasTarget:
          DNSName:
            Fn::GetAtt:
              - DefaultDistribution
              - DomainName
          HostedZoneId: ${self:custom.aliasHostedZoneId}
        HostedZoneName: abc.com.
        Name: ${self:custom.siteName}.
        Type: 'CNAME'
  Outputs:
    DefaultDistribution:
      Value:
        Fn::GetAtt:
          - DefaultDistribution
          - DomainName

and it is deploying fine as far as I can see now.

watch serverless/serverless#11424 you should not use the serverless dashboard. The app/org in the serverless.yml destroys the lambda... -

This issue is referenced in the README as being open-next being compatible with Serverless Framework, but I'm a bit confused. Is @emulienfou's post a question or is it the answer? It's the only config file I see that references lambdaAtEdge, so is edge working?

I understand the open-sourced-ness of this, so if the answer is I need to figure it out, that's fine.

Hi @mbaquerizo I post a started template for Serverless Framework.
It's not working properly at 100% at the time of the post.
If you check the other answers you should be able to make it work by adapting all the examples of the templates