Stacktape

Sign up for freeSign up



Next.js website

Overview

  • Next.js website resource is a purpose-built resource for deploying Next.js app using AWS Lambda (or AWS Lambda@Edge).
  • On the background It creates multiple different AWS resources required to run your Next.js website as efficiently as possible.

Under the hood

  • Stacktape uses open-source project OpenNEXT to build your Next.js application so that it is runnable in AWS Lambda function.
  • On the background Stacktape creates following resources to host your Next.js app:
ComponentTypeDescription
Server functionLambda or Lambda@EdgeRuns your Next.js server.
BucketS3 bucketUsed for storing static assets and cache assets.
Image optimization functionLambdaHandles image optimization when Next.js <Image> component is used.
Cloudfront distributionCloudfront CDNTakes care of caching and web content distribution.
Revalidation functionLambdaTakes care of revalidation when using ISR.
Revalidation queueSQS QueueMessage queue that server function uses to notify revalidation function about needed revalidation.
Revalidation tableDynamoDB tableProvides support for Next.js revalidateTag.
Revalidation inserterLambdaUsed to populate revalidation table during app deployment.
Warmer function (OPTIONAL)LambdaMitigates server function cold starts by keeping it warm (does not work for Lambda@Edge).

For more information refer to OpenNext docs.

Architecture overview (credit: OpenNext)
Architecture overview (credit: OpenNext)

Using edge

You can use Lambda@Edge by specifying useEdgeLambda property.

  • When using edge lambda functions, your server function is executed at the regional cache locations across the globe. This means that the function is executed closer to your user, which results in reduced response time.
  • Downside of using lambda@edge are:
    • slower deployment times compared to using regular lambda,
    • you cannot use warmer (see warmServerInstances property) to keep server function warm.

Copy

resources:
web:
type: nextjs-web
properties:
useEdgeLambda: true

Using warmer

You can specify amount of warm instances you want warmer to keep. By default, warmer is disabled.

  • Server function may experience performance issues due to Lambda cold starts (slower response times).
  • To mitigate cold start effects, the server function can be invoked periodically by warmer function. This is done using warmer function which periodically invokes server function to keep it warm.
  • By default, warmer is disabled.

At the moment warmer does not work when using lambda@edge for server function (see useEdgeLambda property)

Copy

resources:
web:
type: nextjs-web
properties:
warmServerInstances: 5

Using firewall

You can use web-app-firewall resource to protect your Next.js application from common web exploits that could affect application availability, compromise security, or consume excessive resources.

Copy

resources:
web:
type: nextjs-web
properties:
useFirewall: waf
waf:
type: web-app-firewall
properties:
scope: cdn

Environment variables

Injects environment variables into server function.

Most commonly used types of environment variables:

  • Static - string, number or boolean (will be stringified).
  • Result of a custom directive.
  • Referenced property of another resource (using $ResourceParam directive). To learn more, refer to referencing parameters guide. If you are using environment variables to inject information about resources into your script, see also property connectTo which simplifies this process.
  • Value of a secret (using $Secret directive).

Copy

resources:
web:
type: nextjs-web
properties:
environment:
- name: STATIC_ENV_VAR
value: my-env-var
- name: DYNAMICALLY_SET_ENV_VAR
value: $MyCustomDirective('input-for-my-directive')
- name: DB_HOST
value: $ResourceParam('myDatabase', 'host')
- name: DB_PASSWORD
value: $Secret('dbSecret.password')

Custom domain names

Stacktape allows you to connect your custom domain names to some of your resources (Web Service, Nextjs web, HTTP API Gateways, Application Load Balancers and Buckets with CDNs).

Connecting a custom domain to the resource does 2 things:

  • Creates DNS records:
    • If you use your custom domain with a resource, Stacktape automatically creates a DNS record (during deploy) pointing the specified domain name to the resource.
  • Adds TLS certificates
    • If the origin resource (HTTP API Gateway, Application Load Balancer or CDN) uses HTTPS protocol, Stacktape takes care of issuing and attaching correct (free, AWS-managed) certificate to the resource. This means, you do not have to deal with TLS termination as it is handled by the connected resource.
    • If you want to use your own certificates, you can configure customCertificateArns.

To manage a custom domain, it first needs to be added to your AWS account. This means that a hosted zone (collection of records managed together for a given domain) for your domain exists in your AWS account and your domain registrar's name servers are pointing to it. To learn more, refer to Adding a domain guide.

domainName
Required
customCertificateArn
disableDnsRecordCreation

Copy

resources:
web:
type: nextjs-web
properties:
customDomains:
- domainName: mydomain.com

Accessing other resources

  • For most of the AWS resources, resource-to-resource communication is not allowed by default. This helps to enforce security and resource isolation. Access must be explicitly granted using IAM (Identity and Access Management) permissions.

  • Access control of Relational Databases is not managed by IAM. These resources are not "cloud-native" by design and have their own access control mechanism (connection string with username and password). They are accessible by default, and you don't need to grant any extra IAM permissions. You can further restrict the access to your relational databases by configuring their access control mode.

  • Stacktape automatically handles IAM permissions for the underlying AWS services that it creates (i.e. granting web services permission to write logs to Cloudwatch, allowing web services to communicate with their event source and many others).


If your compute resource (nextjs server function) needs to communicate with other infrastructure components, you need to add permissions manually. You can do this in 2 ways:

Using connectTo

  • List of resource names or AWS services that the nextjs server function will be able to access (basic IAM permissions will be granted automatically). Granted permissions differ based on the resource.
  • Works only for resources managed by Stacktape in resources section (not arbitrary Cloudformation resources)
  • This is useful if you don't want to deal with IAM permissions yourself. Handling permissions using raw IAM role statements can be cumbersome, time-consuming and error-prone. Moreover, when using connectTo property, Stacktape automatically injects information about resource you are connecting to as environment variables into your function.

Copy

resources:
web:
type: nextjs-web
properties:
connectTo:
- myDatabase
myDatabase:
type: relational-database
properties:
engine:
type: aurora-postgresql
properties:
instances:
- instanceSize: db.t4g.medium


By referencing resources (or services) in connectTo list, Stacktape automatically:

  • configures correct compute resource's IAM role permissions if needed
  • sets up correct security group rules to allow access if needed
  • injects relevant environment variables containing information about resource you are connecting to into the compute resource's runtime
    • names of environment variables use upper-snake-case and are in form STP_[RESOURCE_NAME]_[VARIABLE_NAME],
    • examples: STP_MY_DATABASE_CONNECTION_STRING or STP_MY_EVENT_BUS_ARN,
    • list of injected variables for each resource type can be seen below.

Granted permissions and injected environment variables are different depending on resource type:


Bucket

  • Permissions:
    • list objects in a bucket
    • create / get / delete / tag object in a bucket
  • Injected env variables: NAME, ARN

DynamoDB table

  • Permissions:
    • get / put / update / delete item in a table
    • scan / query a table
    • describe table stream
  • Injected env variables: NAME, ARN, STREAM_ARN

MongoDB Atlas cluster

  • Permissions:
    • Allows connection to a cluster with accessibilityMode set to scoping-workloads-in-vpc. To learn more about MongoDB Atlas clusters accessibility modes, refer to MongoDB Atlas cluster docs.
    • Creates access "user" associated with compute resource's role to allow for secure credential-less access to the the cluster
  • Injected env variables: CONNECTION_STRING

Relational(SQL) database

  • Permissions:
    • Allows connection to a relational database with accessibilityMode set to scoping-workloads-in-vpc. To learn more about relational database accessibility modes, refer to Relational databases docs.
  • Injected env variables: CONNECTION_STRING, JDBC_CONNECTION_STRING, HOST, PORT (in case of aurora multi instance cluster additionally: READER_CONNECTION_STRING, READER_JDBC_CONNECTION_STRING, READER_HOST)

Redis cluster

  • Permissions:
    • Allows connection to a redis cluster with accessibilityMode set to scoping-workloads-in-vpc. To learn more about redis cluster accessibility modes, refer to Redis clusters docs.
  • Injected env variables: HOST, READER_HOST, PORT

Event bus

  • Permissions:
    • publish events to the specified Event bus
  • Injected env variables: ARN

Function

  • Permissions:
    • invoke the specified function
    • invoke the specified function via url (if lambda has URL enabled)
  • Injected env variables: ARN

Batch job

  • Permissions:
    • submit batch-job instance into batch-job queue
    • list submitted job instances in a batch-job queue
    • describe / terminate a batch-job instance
    • list executions of state machine which executes the batch-job according to its strategy
    • start / terminate execution of a state machine which executes the batch-job according to its strategy
  • Injected env variables: JOB_DEFINITION_ARN, STATE_MACHINE_ARN

User auth pool

  • Permissions:
    • full control over the user pool (cognito-idp:*)
    • for more information about allowed methods refer to AWS docs
  • Injected env variables: ID, CLIENT_ID, ARN


SNS Topic

  • Permissions:
    • confirm/list subscriptions of the topic
    • publish/subscribe to the topic
    • unsubscribe from the topic
  • Injected env variables: ARN, NAME


SQS Queue

  • Permissions:
    • send/receive/delete message
    • change visibility of message
    • purge queue
  • Injected env variables: ARN, NAME, URL

Upstash Kafka topic

  • Injected env variables: TOPIC_NAME, TOPIC_ID, USERNAME, PASSWORD, TCP_ENDPOINT, REST_URL

Upstash Redis

  • Injected env variables: HOST, PORT, PASSWORD, REST_TOKEN, REST_URL, REDIS_URL

Private service

  • Injected env variables: ADDRESS

aws:ses(Macro)

  • Permissions:
    • gives full permissions to aws ses (ses:*).
    • for more information about allowed methods refer to AWS docs

Using iamRoleStatements

  • List of raw IAM role statement objects. These will be appended to the nextjs server function's role.
  • Allows you to set granular control over your nextjs server function's permissions.
  • Can be used to give access to any Cloudformation resource

Copy

resources:
web:
type: nextjs-web
properties:
iamRoleStatements:
- Resource:
- $CfResourceParam('NotificationTopic', 'Arn')
Effect: 'Allow'
Action:
- 'sns:Publish'
cloudformationResources:
NotificationTopic:
Type: AWS::SNS::Topic

Known issues

Bundle size

Next will incorrectly trace dev dependencies to be included in the output node_modules, which will significantly increase the lambda bundle. For example, the @swc/core-* binary is ~33MB!

Add this to your next.config.js to help minimize the lambda bundle size

Copy

outputFileTracingExcludes: {
'*': [
'@swc/core',
'esbuild',
'uglify-js',
'watchpack',
'webassemblyjs',
'sharp'
],
},

Fetch behavior for ISR.

Only for next@13.5.1+

If you use ISR and fetch in your app, you may encounter a bug that makes your revalidate values inconsistent. The issue is that it revalidates using the lowest revalidate of all fetch calls in your page, regardless of their individual values. To fix this bug, you need to modify the fetch function in your root layout component with the following code snippet:

Copy

export default function RootLayout() {
const asyncStorage = require("next/dist/client/components/static-generation-async-storage.external");
//@ts-ignore
const staticStore = (fetch as any).__nextGetStaticStore?.() || asyncStorage.staticGenerationAsyncStorage;
const store = staticStore.getStore();
store.isOnDemandRevalidate = store.isOnDemandRevalidate && !(process.env.OPEN_NEXT_ISR === "true");
return <>...</>;
}

Pricing

All of the resources used in the overall architecture are pay-per-use. If your website does not get traffic, you have no costs.

As the traffic increases, the costs get higher - however costs are much lower compared to Vercel.

// TODO pricing examples

API reference

NextjsWeb  API reference
type
Required
properties.appDirectory
Default: .
properties.serverLambda
properties.warmServerInstances
properties.useEdgeLambda
properties.buildCommand
properties.fileOptions
properties.environment
properties.customDomains
properties.useFirewall
properties.connectTo
properties.iamRoleStatements
overrides
NextjsServerLambdaProperties  API reference
Parent:NextjsWeb
memory
Default: 1024
timeout
Default: 30
logging
LambdaFunctionLogging  API reference
disabled
retentionDays
Default: 180
logForwarding
EnvironmentVar  API reference
Parent:NextjsWeb
name
Required
value
Required
DirectoryUploadFilter  API reference
Parent:NextjsWeb
includePattern
Required
excludePattern
headers
tags

Need help? Ask a question on SlackDiscord or info@stacktape.com.