Next.js Website
The nextjs-website
resource is designed to deploy Next.js applications to AWS. It leverages serverless technologies like AWS Lambda and Lambda@Edge to run your application efficiently. Stacktape automatically provisions and configures all the necessary AWS resources, so you can focus on writing your application code.
resources:web:type: nextjs-webproperties:appDirectory: ./
Basic use of nextjs-web
resource
How it Works
Stacktape uses the open-source project OpenNEXT to build your Next.js application, making it compatible with AWS Lambda. Behind the scenes, Stacktape provisions a set of AWS resources to host your app:
Component | Type | Description |
---|---|---|
Server function | Lambda or Lambda@Edge | Runs your Next.js server logic. |
Asset Bucket | S3 bucket | Stores static assets (like images, CSS) and cached assets. |
Image optimization function | Lambda | Handles on-the-fly image optimization for the Next.js <Image> component. |
CloudFront distribution | CDN | A CDN that caches and distributes your website content globally for low latency. |
Revalidation function | Lambda | Handles content revalidation when using Incremental Static Regeneration (ISR). |
Revalidation queue | SQS Queue | A message queue used by the server function to trigger the revalidation function. |
Revalidation table | DynamoDB table | A NoSQL database that supports Next.js revalidateTag for on-demand revalidation. |
Revalidation inserter | Lambda | Populates the revalidation table during deployment. |
Warmer function (Optional) | Lambda | Keeps the server function warm to prevent cold starts (not available for Lambda@Edge). |
For more information refer to OpenNext docs.
Edge Deployments
To deploy your Next.js application to the edge for lower latency, you can use Lambda@Edge by setting the useEdgeLambda
property to true
. This deploys your server function to CDN locations around the world.
- 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.
resources:web:type: nextjs-webproperties:appDirectory: ./useEdgeLambda: true
Performance Optimization
Mitigating Cold Starts
To mitigate cold starts and ensure your server function responds quickly, you can enable a warmer. This keeps a specified number of function instances active. By default, the warmer is disabled. This feature is not available when using useEdgeLambda
.
- 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)
resources:web:type: nextjs-webproperties:appDirectory: ./warmServerInstances: 5
Security
Web Application Firewall
You can protect your Next.js application from common web exploits by attaching a web-app-firewall. This can help prevent attacks that could affect your application's availability, compromise security, or consume excessive resources.
resources:web:type: nextjs-webproperties:appDirectory: ./useFirewall: wafwaf:type: web-app-firewallproperties:scope: cdn
Environment variables
You can inject environment variables into the server function. These variables are available at build time and runtime.
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).
resources:web:type: nextjs-webproperties:appDirectory: ./environment:- name: STATIC_ENV_VARvalue: my-env-var- name: DYNAMICALLY_SET_ENV_VARvalue: $MyCustomDirective('input-for-my-directive')- name: DB_HOSTvalue: $ResourceParam('myDatabase', 'host')- name: DB_PASSWORDvalue: $Secret('dbSecret.password')
Custom Domain Names
You can configure custom domains for your Next.js application. Stacktape will automatically provision an SSL certificate and configure DNS routing.
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.
resources:web:type: nextjs-webproperties:appDirectory: ./customDomains:- domainName: mydomain.com
Accessing Other Resources
By default, most AWS resources are not allowed to communicate with each other. This is a security measure to ensure resource isolation. To allow your Next.js application to access other resources, you need to grant permissions explicitly.
- IAM Permissions: Access to most AWS services is controlled by IAM (Identity and Access Management). Stacktape automatically manages the necessary permissions for the resources it creates.
- Relational Databases: Relational Databases use their own access control (a connection string with a username and password) and are not managed by IAM. They are accessible by default unless you configure more restrictive access controls.
If your Next.js application needs to communicate with other resources in your stack, you can grant permissions in two ways:
Using connectTo
The connectTo
property is a simplified way to grant access to other Stacktape-managed resources. Stacktape will automatically configure the required IAM permissions and inject connection details as environment variables into your function.
resources:web:type: nextjs-webproperties:appDirectory: ./connectTo:- myDatabasemyDatabase:type: relational-databaseproperties:engine:type: aurora-postgresqlproperties: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
orSTP_MY_EVENT_BUS_ARN
, - list of injected variables for each resource type can be seen below.
- names of environment variables use upper-snake-case and are in form
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 toscoping-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
- Allows connection to a cluster with
- Injected env variables:
CONNECTION_STRING
Relational(SQL) database
- Permissions:
- Allows connection to a relational database with
accessibilityMode
set toscoping-workloads-in-vpc
. To learn more about relational database accessibility modes, refer to Relational databases docs.
- Allows connection to a relational database with
- 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 toscoping-workloads-in-vpc
. To learn more about redis cluster accessibility modes, refer to Redis clusters docs.
- Allows connection to a redis cluster with
- 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
- full control over the user pool (
- 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
- gives full permissions to aws ses (
Using iamRoleStatements
For more granular control, you can provide raw IAM role statements. This allows you to define precise permissions for accessing any AWS resource, including those not managed by Stacktape.
resources:web:type: nextjs-webproperties:appDirectory: ./iamRoleStatements:- Resource:- $CfResourceParam('NotificationTopic', 'Arn')Effect: 'Allow'Action:- 'sns:Publish'cloudformationResources:NotificationTopic:Type: AWS::SNS::Topic
Known Issues
Here are some known issues and workarounds when deploying Next.js applications with Stacktape.
Bundle Size
Next.js can sometimes include development dependencies in the production bundle, which significantly increases its size. To prevent this, add the following configuration to your next.config.js
file:
// next.config.jsmodule.exports = {outputFileTracingExcludes: {'*': ['@swc/core','esbuild','uglify-js','watchpack','webassemblyjs','sharp'],},}
Fetch Behavior for ISR
Applies only to Next.js v13.5.1 and later.
A bug in Next.js can cause inconsistent revalidation behavior when using ISR. It may use the lowest revalidate
value from all fetch
calls on a page, ignoring their individual settings. To fix this, add the following snippet to your root layout component:
// app/layout.tsxexport default function RootLayout({ children }: { children: React.ReactNode }) {const asyncStorage = require("next/dist/client/components/static-generation-async-storage.external");//@ts-ignoreconst staticStore = (fetch as any).__nextGetStaticStore?.() || asyncStorage.staticGenerationAsyncStorage;const store = staticStore.getStore();if (store) {store.isOnDemandRevalidate = store.isOnDemandRevalidate && !(process.env.OPEN_NEXT_ISR === "true");}return <>{children}</>;}
Pricing
All resources provisioned for your Next.js application are pay-per-use. This means you pay nothing for idle resources and only for what you use. As your traffic grows, your costs will increase, but they are generally much lower than managed hosting providers like Vercel.
Cost Breakdown
CloudFront CDN
CloudFront serves as a CDN, caching your content in edge locations close to your users.
- Data Transfer: ~$0.085 per GB (first 1 TB/month is free)
- HTTPS Requests: ~$0.0085 per 10,000 requests (first 10,000,000/month are free)
For current pricing, see the AWS CloudFront Pricing page.
AWS Lambda
Costs depend on whether you use regional Lambda or Lambda@Edge.
Regional AWS Lambda
- Execution Duration: ~$0.0000000167 per millisecond (with 1024MB memory)
- Requests: $0.20 per 1 million requests
Lambda@Edge
- Execution Duration: ~$0.00000005001 per millisecond (with 1024MB memory)
- Requests: $0.60 per 1 million requests
For current pricing, see the AWS Lambda Pricing page.
S3 for Caching
A S3 bucket is used to store the cache for ISR and fetch
requests.
GetObject
: ~$0.0004 per 1,000 requests (reading from cache)PutObject
: ~$0.005 per 1,000 requests (writing to cache)
For current pricing, see the AWS S3 Pricing page.
DynamoDB for Cache Tags
A DynamoDB table stores cache tags for on-demand revalidation.
- Read Requests: ~$0.25 per 1 million read units
- Write Requests: ~$1.25 per 1 million write units
For current pricing, see the AWS DynamoDB Pricing page.
Example Pricing
The following example is a rough estimate for a website with 300,000 monthly visitors. Actual costs can vary significantly based on your application's caching strategy, dynamic content, and API usage.
Assumptions
- Monthly Visitors: 300,000
- Page Views per Visitor: 3
- Cache Hit Rate: 50% of page views are served from the CDN cache.
- API Calls per Page: 2 API calls for each page view that hits the server.
This results in:
- Total Page Views: 900,000
- Server Page Views: 450,000
- Total Server Requests: 1,350,000 (450,000 page views + 900,000 API calls)
Monthly Cost Estimate
Service | Calculation | Estimated Cost |
---|---|---|
CloudFront | Within free tier limits | $0 |
AWS Lambda | 675,000 requests, 100ms duration each | $1.26 |
S3 Storage | 675,000 reads and writes | $3.65 |
DynamoDB | 675,000 reads and writes | $1.01 |
Total | ~$5.92 per month |