Open emulienfou opened 1 year ago
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.
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
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 https://github.com/serverless/serverless/issues/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
I could make it work propperly on serverless framework with this configuration. Not sure about cache-related stuff, though
service: next-app
useDotenv: true
configValidationMode: error
plugins:
- serverless-scriptable-plugin
- serverless-s3-sync
- serverless-prune-plugin
package:
individually: true
provider:
name: aws
runtime: nodejs20.x
stage: ${opt:stage, 'dev'}
region: 'sa-east-1'
profile: default
deploymentMethod: direct
environment:
STAGE: ${self:provider.stage}
# OPEN-NEXT
SHARP_VERSION: "0.32.6"
REVALIDATION_QUEUE_URL: !GetAtt CacheRevalidationQueue.QueueUrl
REVALIDATION_QUEUE_REGION: ${self:provider.region}
CACHE_DYNAMO_TABLE: !Ref CacheTable
CACHE_BUCKET_NAME: ${self:custom.cacheBucketName}
CACHE_BUCKET: ${self:custom.cacheBucketName}
# CACHE_BUCKET_KEY_PREFIX: cache
CACHE_BUCKET_REGION: ${self:provider.region}
BUCKET_NAME: ${self:custom.assetsBucketName}
# BUCKET_KEY_PREFIX: assets
# DYNAMO_BATCH_WRITE_COMMAND_CONCURRENCY: 5
# MAX_REVALIDATE_CONCURRENCY: 10
iam:
role:
statements:
- Effect: "Allow"
Action:
- s3:GetObject
- s3:PutObject
- s3:ListObjects
- dynamodb:PutItem
- dynamodb:Query
- sqs:SendMessage
- lambda:InvokeFunction
Resource: "*"
deploymentBucket:
name: ${self:provider.stage}-serverless
blockPublicAccess: true
endpointType: REGIONAL
apiGateway:
shouldStartNameWithService: true
binaryMediaTypes:
- "*/*"
custom:
schema: ${self:service}
aliasHostedZoneId: Z00000000000000000
assetsBucketName: ${self:service}-${self:provider.stage}-assets
cacheBucketName: ${self:service}-${self:provider.stage}-cache
prune:
automatic: true
number: 10
scriptable:
hooks:
after:deploy:finalize:
- result=$(aws lambda invoke --function-name ${self:service}-${self:provider.stage}-dynamodb-provider /dev/null) && echo "$result"
before:package:createDeploymentArtifacts:
# - SHARP_VERSION="0.32.6" OPEN_NEXT_DEBUG=true yarn open-next build --dangerously-disable-dynamodb-cache --dangerously-disable-incremental-cache
- SHARP_VERSION="0.32.6" yarn open-next build --minify
- 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 .
- cd .open-next/revalidation-function && zip -r ../zips/revalidation-function.zip .
- cd .open-next/warmer-function && zip -r ../zips/warmer-function.zip .
s3Sync:
- bucketName: ${self:custom.assetsBucketName}
localDir: .open-next/assets
params:
- "**/*":
CacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate"
- "_next/**/*":
CacheControl: "public,max-age=31536000,immutable"
functions:
server:
description: Default Lambda for Next CloudFront distribution
name: ${self:service}-${self:provider.stage}-server
handler: index.handler
runtime: nodejs20.x
architecture: arm64
memorySize: 512
timeout: 10
url: true
package:
artifact: .open-next/zips/server-function.zip
imageOptimization:
description: Image Lambda for Next CloudFront distribution
name: ${self:service}-${self:provider.stage}-image-optimization
handler: index.handler
runtime: nodejs20.x
architecture: arm64
memorySize: 512
timeout: 10
url: true
package:
artifact: .open-next/zips/image-optimization-function.zip
cacheRevalidation:
description: Cache Revalidation Lambda for Next CloudFront distribution
name: ${self:service}-${self:provider.stage}-cache-revalidation
handler: index.handler
runtime: nodejs20.x
architecture: arm64
memorySize: 512
timeout: 10
url: true
package:
artifact: .open-next/zips/revalidation-function.zip
events:
- sqs:
arn: !GetAtt CacheRevalidationQueue.Arn
warmer:
description: Lambdas warmer (cheap provisioned concurrency)
name: ${self:service}-${self:provider.stage}-warmer
handler: index.handler
runtime: nodejs20.x
architecture: arm64
memorySize: 512
timeout: 10
url: true
package:
artifact: .open-next/zips/warmer-function.zip
environment:
FUNCTION_NAME: ${self:service}-${self:provider.stage}-server
CONCURRENCY: 1
events:
- schedule:
rate: rate(5 minutes)
resources:
Resources:
CacheTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-${self:provider.stage}-cache
AttributeDefinitions:
- AttributeName: tag
AttributeType: S
- AttributeName: path
AttributeType: S
- AttributeName: revalidatedAt
AttributeType: N
KeySchema:
- AttributeName: tag
KeyType: HASH
- AttributeName: path
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
GlobalSecondaryIndexes:
- IndexName: revalidate
KeySchema:
- AttributeName: path
KeyType: HASH
- AttributeName: revalidatedAt
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
CacheRevalidationQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-${self:provider.stage}-cache-revalidation
CacheBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.cacheBucketName}
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.assetsBucketName}
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_All
CustomErrorResponses:
- ErrorCode: "404"
ResponsePagePath: "/index.html"
ResponseCode: "200"
ErrorCachingMinTTL: "30"
ViewerCertificate:
AcmCertificateArn: arn:aws:acm:us-east-1:0000000000
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
Aliases: ["next-app.com"]
Origins:
- Id: ServerFunctionOrigin
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
DomainName: !Select [2, !Split ["/", !GetAtt ImageOptimizationLambdaFunctionUrl.FunctionUrl]]
CustomOriginConfig:
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginGroups:
Quantity: 1
Items:
- Id: ServerAndStaticAssetOriginGroup
FailoverCriteria:
StatusCodes:
Quantity: 2
Items:
- 500
- 502
Members:
Quantity: 2
Items:
- OriginId: ServerFunctionOrigin
- OriginId: StaticAssetOrigin
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:
# public directory (bucket root)
- TargetOriginId: StaticAssetOrigin
ViewerProtocolPolicy: https-only
# Tip: Create only a few directories, or even just one inside "public" and just use /directory/* instead of what I did
PathPattern: "/*.*" # or /*.{jpg}
Compress: true
AllowedMethods: ["GET", "HEAD", "OPTIONS"]
CachedMethods: ["HEAD", "GET"]
ForwardedValues:
QueryString: false
- TargetOriginId: StaticAssetOrigin
ViewerProtocolPolicy: https-only
PathPattern: "/**/*.*" # or /**/*.{jpg}
Compress: true
AllowedMethods: ["GET", "HEAD", "OPTIONS"]
CachedMethods: ["HEAD", "GET"]
ForwardedValues:
QueryString: false
#
- 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:
HostedZoneId: Z00000000000000000
Comment: ${self:provider.stage} Next APP DNS
Name:
- "next-app.com"
Type: CNAME
TTL: 360
ResourceRecords:
- !GetAtt DefaultDistribution.DomainName
I've added the two missing lambdas (warmer and revalidation) to the mix and the missing variables, as per-doc. And aws is now enforcing s3 bucket ACL deprecation, preferring and defaulting new buckets to bucket policy only, disabling ACL by default. My open-next version: 2.3.6 (latest, as I spoke) EDIT: It works ~, except for this problem: https://github.com/sst/open-next/issues/373~ 🟢 The issue regarding sharp is workarounded, and as far as I undertand, lies in next version itself, but the workaround worked, and I've updated the example below. ~EDIT 2: I've ported an already existing next app running as standalone on a container, so I'm currently trying to figure out with the app developers why fonts and the theme isn't loaded propperly on use-client pages.~ 🟢 The serverless hooks plugin cannot read environment variables propperly, so I've written a JS script that loads environment variables and then runs the same build and zip commands, and call it there instead ~EDIT 3: The site deployed using this solution instead of our previous solution (Fargate + Load Balancer) feel much less snappier for some reason. Not quite sure why, yet..~ ~EDIT 4: For some reason, the 'public' directory assets (including /images) seems to be inacessible through cdn~ 🟢 It was because the public directory is at the root of the assets bucket (in my case / vercel default), so I've just added a handler for any files at the root or inside a subfolder of the bucket (it may be safer to specify the exact extensions, perhaps). It solved the slowdown like magic. Just updated the example below
At the docs, there is a "Dynamo provider". In the .open-next directory there is a "dynamodb-provider" (it does not finishes with "-function"), but I thought it was not a lambda. It seems I was wrong, despite it isn't in this docs drawing:
But it is mentioned here https://open-next.js.org/advanced/architecture#dynamo-provider-function. Is it actually a lambda?
@celso-alexandre Sorry for the delay, i totally missed this.
Yeah dynamo-provider is a lambda, but it is only run once at deploy time. It is only used to prepopulate the dynamodb table and is only needed for app router and if you plan on using revalidatePath
and revalidateTag
Trying to deploy Nextjs 13 using open-next with Serverless/Cloudformation.