--- title: Backend for Frontend (BFF) Pattern description: Docs intro --- # Backend for Frontend (BFF) Pattern The Backend for Frontend (BFF) Pattern is a design pattern commonly used in software engineering, particularly in the context of building applications with separate client-side and server-side components. It involves creating specialized backend services tailored to the needs of specific frontend clients. The main idea behind the BFF pattern is to have dedicated backend components that serve as intermediaries between frontend clients and the core backend systems, providing customized data and functionality to each frontend client. Backend for Frontend (BFF) pattern for a web application built in React where the build process generates two bundles: one for the client and one for the server. Let's break down how each tier fits into the BFF pattern: 1. **Presentation Layer (Client Bundle)**: - The client bundle contains the presentation layer of the application. This includes all the UI components, logic for handling user interactions, and rendering views in the browser. - React components are bundled into JavaScript files that are served to the client's web browser. - The client-side presentation layer interacts with the BFF server to fetch data and perform other backend operations via API requests. 2. **Backend for Frontend (BFF) Server (Server Bundle)**: - The server bundle, which acts as the Backend for Frontend (BFF), serves as the intermediary between the client-side presentation layer and the backend services. - It contains business logic, data aggregation, and transformation logic tailored specifically for the needs of the frontend client. - The BFF server handles client requests, fetches data from various backend services or databases, processes it, and sends back the appropriate response to the client. - It may expose API endpoints that are optimized for the frontend client's requirements, abstracting away complexities of the underlying backend systems. 3. **Database**: - The database tier stores and manages the application's data. - It could be a relational database (e.g., MySQL, PostgreSQL) or a NoSQL database (e.g., MongoDB) depending on the application's requirements. - The BFF server interacts with the database to perform CRUD operations (Create, Read, Update, Delete) and fetch data needed to fulfill client requests. 4. **API Repository (API Endpoints from Other Services)**: - The API Repository consists of API endpoints from other services or external systems that the BFF server needs to interact with. - These endpoints could belong to other microservices, third-party APIs, or internal services within the organization. - The BFF server integrates with these API endpoints to fetch additional data or perform actions required to fulfill client requests. - It may aggregate data from multiple API endpoints, transform the data, and then serve it to the client in the desired format. The client bundle serves as the presentation layer, the server bundle acts as the BFF server responsible for aggregating data and serving tailored APIs to the client, the database stores application data, and the API Repository provides integration with external services and APIs. This architecture allows for better separation of concerns, improved scalability, and enhanced flexibility in catering to the specific needs of the frontend client. --- --- title: The Modern Three-Tier Pattern description: --- # The Modern Three-Tier Pattern The Three-Tier Pattern in software engineering is a design architecture that divides an application into three logically separate layers: presentation, business logic, and data storage. Each layer has its own responsibilities and interacts with the other layers in a specific way. This pattern helps in achieving better modularity, scalability, and maintainability of the software system. Here's a breakdown of the three tiers: 1. **Presentation Tier (also known as the User Interface Tier)**: - This tier is responsible for presenting information to the user and collecting user inputs. - It typically includes components such as user interfaces, web pages, mobile apps, or any other means through which users interact with the system. - The presentation tier focuses on providing a user-friendly interface and handling user interactions. - In web applications, this tier often consists of HTML, CSS, JavaScript, and frontend frameworks like React or Angular. 2. **Application Tier (also known as the Business Logic Tier or Middle Tier)**: - This tier contains the business logic of the application. - It processes and manipulates data based on the user input received from the presentation tier. - Business rules, algorithms, calculations, and validation logic are implemented in this tier. - The application tier acts as an intermediary between the presentation tier and the data tier. - It encapsulates the core functionalities of the system, ensuring that the business logic remains independent of the presentation and data layers. - In web applications, this tier often involves server-side scripting languages like Java, C#, Python, or Node.js. 3. **Data Tier (also known as the Data Storage Tier or Database Tier)**: - This tier is responsible for storing and managing data. - It includes databases or any other storage mechanisms where data is persisted. - Data retrieval, insertion, updating, and deletion operations are performed in this tier. - The data tier ensures data integrity, security, and consistency. - It abstracts the underlying data storage details from the application and presentation layers. - Relational databases (such as MySQL, PostgreSQL, SQL Server) or NoSQL databases (such as MongoDB, Cassandra) are commonly used in this tier. The Three-Tier Pattern promotes separation of concerns, making the software easier to understand, maintain, and scale. It also facilitates parallel development as different teams can work on different tiers independently. Additionally, it allows for easier replacement or upgrade of individual tiers without affecting the entire system. ## Reactive Web Frameworks Modern Single Page Application (SPA) meta-frameworks like Next.js, Nuxt.js, and Remix can be effectively utilized to develop applications following the Three-Tier Pattern, albeit with some adjustments. Let's break down how each tier can be implemented using these frameworks: 1. **Presentation Tier**: - In the context of SPA meta-frameworks, the presentation tier mainly involves creating user interfaces and managing client-side interactions. - These frameworks offer powerful tools for building interactive user interfaces using components, routing, and state management. - Developers can create UI components using the framework's features and structure them hierarchically to represent the application's user interface. - Components can be organized into pages, layouts, and reusable UI elements to ensure consistency and modularity. - The framework handles client-side routing, allowing developers to define routes and associate them with specific components or pages. - Modern frontend libraries like React (used in Next.js and Remix) or Vue.js (used in Nuxt.js) are typically employed to build the presentation tier. 2. **Application Tier**: - The application tier encompasses the business logic of the application, including data processing, validation, and state management. - SPA meta-frameworks provide facilities for implementing application logic both on the client-side and server-side. - Client-side logic can be written within the components or services to handle user interactions and manage local state. - Server-side logic can be implemented using server-side rendering (SSR) or serverless functions to execute business logic on the server before sending the response to the client. - Framework-specific APIs and libraries can be utilized to handle data fetching, form validation, authentication, authorization, and other application-specific functionalities. - Business logic should be organized into reusable modules or services to maintain separation of concerns and promote code reusability. 3. **Data Tier**: - The data tier involves managing data storage, retrieval, and manipulation. - SPA meta-frameworks support various approaches for integrating with data sources such as RESTful APIs, GraphQL endpoints, or serverless functions. - Data fetching can be performed both on the client-side and server-side depending on the application requirements. - Framework-specific libraries and utilities can be used to make asynchronous requests to fetch data from external APIs or databases. - Client-side data caching and state management solutions can be employed to optimize performance and reduce unnecessary data fetching. - Data received from the server can be processed, validated, and transformed as per the business requirements before being rendered in the UI. By leveraging the capabilities of SPA meta-frameworks like Next.js, Nuxt.js, and Remix, developers can effectively implement the Three-Tier Pattern in their applications, ensuring separation of concerns, modularity, and maintainability while building modern and interactive web experiences. ## Database-as-a-Service (DBaaS) Using Database as a Service (DBaaS) providers in conjunction with Single Page Applications (SPAs) involves leveraging cloud-based database solutions to handle data storage and management for your application. Here's how you can integrate DBaaS providers with SPAs: 1. **Choose a DBaaS Provider**: - Research and select a DBaaS provider that best fits your application requirements. Popular options include Amazon Web Services (AWS) with Amazon RDS or Amazon DynamoDB, Google Cloud Platform (GCP) with Cloud SQL or Firestore, Microsoft Azure with Azure SQL Database or Cosmos DB, and various other providers like MongoDB Atlas, Firebase, etc. 2. **Set Up Your Database**: - Sign up for an account with the chosen DBaaS provider and create a new database instance. Follow the provider's documentation to configure the database instance according to your application needs. - Configure security settings such as access control, encryption, and firewall rules to ensure the security of your data. 3. **Integrate Database Access in Your SPA**: - Use the appropriate client libraries or SDKs provided by the DBaaS provider to interact with the database from your SPA. - For relational databases, you might use SQL-based query languages (e.g., SQL for MySQL, PostgreSQL) to perform CRUD operations (Create, Read, Update, Delete). - For NoSQL databases, you may use document-based query languages (e.g., MongoDB's query language) or APIs provided by the provider to interact with the database. - Ensure that you handle authentication and authorization properly to restrict access to the database and prevent unauthorized operations. 4. **Implement Data Fetching and Manipulation**: - In your SPA, implement logic to fetch data from the database using API calls or database client libraries provided by the DBaaS provider. - Use asynchronous programming techniques to handle data fetching and manipulation without blocking the user interface. - Consider implementing caching mechanisms to improve performance and reduce the number of database requests, especially for frequently accessed data. 5. **Handle Data Synchronization and Real-Time Updates**: - Depending on your application requirements, implement mechanisms to handle data synchronization and real-time updates between the client and the database. - Use features provided by the DBaaS provider such as websockets, change streams, or real-time database triggers to receive updates from the server and reflect them in the SPA's UI without manual refresh. 6. **Optimize Performance and Scalability**: - Monitor and optimize database performance by analyzing query performance, indexing strategies, and resource utilization. - Configure auto-scaling options provided by the DBaaS provider to handle fluctuations in workload and ensure scalability of your application. 7. **Handle Error and Recovery**: - Implement error handling mechanisms to handle database errors gracefully and provide meaningful feedback to users. - Implement retry mechanisms and error recovery strategies to handle transient failures and ensure data consistency. By following these steps, you can effectively integrate Database as a Service providers with your Single Page Application, allowing you to leverage scalable, reliable, and managed database solutions while focusing on building a rich and interactive user experience. --- --- title: Connect and Manage AWS Accounts with Thunder description: Learn how to efficiently manage multiple AWS accounts and regions using Thunder, enhancing your workflow across diverse environments. --- # Connect and Manage AWS Accounts with Thunder Thunder allows you to manage multiple AWS accounts and regions effortlessly. This feature is particularly useful for users working with diverse environments or projects spread across different AWS accounts or regions. ## IAM This method uses AWS Identity and Access Management (IAM) credentials for authentication. ### Step-by-Step Instructions 1. **Generate AWS Access Keys** - Log in to your [AWS Management Console](https://console.aws.amazon.com/) - Navigate to **IAM** (Identity and Access Management) - Click on **Users** in the left sidebar - Select your user or create a new IAM user for Thunder - Go to the **Security credentials** tab - Under **Access keys**, click **Create access key** - Choose **Application running outside AWS** as the use case - Click **Next** - Copy your **Access Key ID** and **Secret Access Key** (store the Secret Access Key securely) - For detailed instructions, see [AWS: Managing access keys for IAM users](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) 2. **Add Account to Thunder** - Navigate to your organization → AWS Accounts - Click the button to add a new AWS account - Select the **Using Access Key** option - Enter your AWS account alias - Paste your **Access Key ID** - Paste your **Secret Access Key** - Click **Connect** 3. **Verify the Connection** - Once connected, your AWS account will appear in the AWS Accounts list - You are now ready to deploy on AWS ### Security Best Practices - Use dedicated IAM users for Thunder rather than root account credentials - Rotate access keys regularly (see [AWS: Rotating access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys_rotate.html)) - Consider using temporary security credentials for enhanced security - Apply principle of least privilege to your IAM user permissions ## CloudFormation This method uses AWS CloudFormation to set up the necessary permissions and resources. 1. **Start the Setup** - Navigate to your organization → AWS Accounts - Click the button to add a new AWS account - Select the **Using CloudFormation** option - Enter an alias for your AWS account 2. **Create the CloudFormation Stack** - Click **Open CloudFormation** to go to the AWS CloudFormation console - Review the stack details - At the bottom of the page, check the acknowledgment checkbox - Click the Create stack button 3. **Complete the Setup** - Wait for the stack to be successfully created - Return to Thunder - Your AWS account will now appear in your account list You are now ready to deploy on AWS. --- --- title: Claude Code Skill description: Deploy any web app to AWS by asking Claude. The /aws-deploy skill auto-installs with Thunder and guides Claude through framework detection, cost estimation, and stack generation. --- # Claude Code Skill The `aws-deploy` skill is a [Claude Code](https://claude.ai/code) skill that ships with Thunder. When you add `@thunder-so/thunder` to your project, the skill is automatically registered — no manual setup required. From that point on, you can ask Claude to deploy your app to AWS and it will handle the rest. ## How It Works When you invoke `/aws-deploy` (or simply ask Claude to deploy), the skill runs a structured 5-step workflow: 1. **Scans your project** — reads `package.json`, framework config files (`next.config.ts`, `nuxt.config.ts`, `astro.config.mjs`, `svelte.config.js`, `app.config.ts`, `vite.config.ts`, `Dockerfile`, etc.) without asking you first 2. **Detects your framework** — identifies the framework, adapter, and output mode from the scanned files 3. **Asks clarifying questions** — at most 2, only when something is genuinely ambiguous (e.g. monorepo root, SSR vs static, WebSocket requirement) 4. **Presents a recommendation** — the right Thunder construct with a cost estimate and explanation 5. **Generates everything** — a complete `stack/dev.ts` file, a Dockerfile if needed, and deploy scripts in `package.json` ## Installation Thunder uses a `postinstall` script to register the skill. Bun blocks postinstall scripts by default — run this once to allow it: ```bash bun pm untrusted ``` Then install Thunder: ```bash bun add @thunder-so/thunder --development ``` The skill is now available in any Claude Code session inside your project. Invoke it with: ``` /aws-deploy ``` Or just tell Claude: _"Deploy this to AWS"_. ## What Gets Detected The skill uses a decision matrix to map your project to the right [Thunder construct](/docs/patterns): | Framework | Possible constructs | | --- | --- | | Next.js (`output: 'export'`) | `Static` | | Next.js (`output: 'standalone'`) | `Fargate` | | Nuxt, Astro, SvelteKit, TanStack Start, Solid Start, AnalogJS | `Static`, `Serverless`, or `Fargate` | | Hono, Express, Fastify | `Lambda` or `Fargate` | | Any app with a Dockerfile | `Fargate` | | Pure Vite SPA | `Static` | When multiple patterns are valid, Claude presents the tradeoffs and asks which you prefer. ## AWS MCP Servers The skill integrates with [AWS MCP servers](https://awslabs.github.io/mcp/) when they are connected to your Claude Code session. These are optional but unlock real-time data that makes the skill significantly more useful. ### AWS Pricing MCP Server Used during the recommendation step to fetch live pricing data for Lambda, Fargate, CloudFront, S3, and API Gateway — instead of relying on static estimates. - Docs: [awslabs.github.io/mcp/servers/aws-pricing-mcp-server](https://awslabs.github.io/mcp/servers/aws-pricing-mcp-server) - Source: [github.com/awslabs/mcp — aws-pricing-mcp-server](https://github.com/awslabs/mcp/tree/main/src/aws-pricing-mcp-server) ### AWS Billing and Cost Management MCP Server Gives Claude visibility into your actual AWS spend and budget alerts. Useful for reviewing costs after deployment or setting up budget guardrails. - Source: [github.com/awslabs/mcp — billing-cost-management-mcp-server](https://github.com/awslabs/mcp/tree/main/src/billing-cost-management-mcp-server) ### CloudWatch MCP Server Used after deployment to help Claude debug issues — querying [CloudWatch Logs](https://aws.amazon.com/cloudwatch/), reading Lambda error traces, and checking ECS task health without leaving your editor. - Docs: [awslabs.github.io/mcp/servers/cloudwatch-mcp-server](https://awslabs.github.io/mcp/servers/cloudwatch-mcp-server) - Source: [github.com/awslabs/mcp — cloudwatch-mcp-server](https://github.com/awslabs/mcp/tree/main/src/cloudwatch-mcp-server) --- --- title: Build configuration description: Configure build settings in Thunder Console --- # Build configuration Configure the install and build scripts used by Thunder Console's CI/CD pipelines (CodePipeline + CodeBuild). These settings control how your project is installed, built, and where build artifacts are placed. Below are the available properties and short descriptions for each pattern's build properties. The `rootDir` is common for all patterns used for monorepos. ## Single Page Applications The build configuration for [Static](/docs/patterns/static)
public/
Node.js 24
bun install
bun run build
- `outputDir` — Required. Directory containing built assets to publish (e.g., `dist/`, `build/`). - `runtime` — Nodejs runtime version (e.g., `20`, `18`). - `installcmd` — Command to install dependencies (e.g., `bun install`, `npm ci`). - `buildcmd` — Command to build the site (e.g., `bun run build`, `npm run build`). ## Serverless Functions (Lambda) The build configuration for [Lambda](/docs/patterns/lambda) ### Container mode Thunder supports Docker containers for Lambda functions.
Container
Dockerfile
- `dockerfile` — Path to your Dockerfile - `memorySize` — Memory (MB) allocated to your Lambda function ### Zip mode You can use the default Zip mode to deploy Lambda functions.
Zip
bun install
bun run build
Node.js 24
dist/server
index.handler
- `installcmd` — Command to install dependencies (e.g., `bun install`, `npm ci`). - `buildcmd` — Command to build the site (e.g., `bun run build`, `npm run build`). - `runtime` — Nodejs runtime version (e.g., `20`, `18`). - `codeDir` — The output directory for your server-side code - `handler` — Lambda handler - `memorySize` — Memory (MB) allocated to your Lambda function ## Web Service (Fargate) The build configuration for [Fargate](/docs/patterns/fargate) ### Container Use a Dockerfile to deploy any web app on AWS using Thunder.
Custom Dockerfile
Dockerfile
0.25 vCPU
512 MB
3000
### Nixpacks Use Nixpacks to generate your Dockerfile.
Nixpacks
bun install
bun run build
bun run start
0.25 vCPU
512 MB
3000
--- --- title: Custom domains description: Learn how to attach custom domains to your application using Amazon Route53 and AWS Certificate Manager. Follow these steps to create a hosted zone, request an ACM certificate, and configure your domain settings. --- # Custom domains You can attach custom domains if you are using Amazon Route53. ## Create Route53 Hosted Zone - Go to Route53 console - Click "Create hosted zone" - Enter your domain name - Note the NS records - update these at your domain registrar - Copy the Hosted Zone ID ## Request ACM Certificate (us-east-1 region) - Go to AWS Certificate Manager - Click "Request certificate" - Add your domain (e.g., example.com and *.example.com) - Choose DNS validation - Copy the generated certificate ARN ## Configure Enter your domain, Hosted Zone ID and the ARN of your certificate. Thunder will automatically configure your application to point to the domain. --- --- title: Environment variables and Secrets description: Manage environment variables and secrets across your deployment pipeline --- # Environment variables and Secrets Thunder supports environment variables and secrets across all deployment patterns. Variables can be configured during the build phase via CodeBuild/CodePipeline, and at runtime for Lambda and Fargate deployments. ## Architecture Support Different deployment architectures support different variable scopes: | Pattern | Build Env Vars | Runtime Env Vars | |---------|---|---| | **Single Page Application (SPA)** | ✓ CodeBuild | — | | **Serverless Functions** | ✓ CodeBuild | ✓ Lambda | | **Web Service** | ✓ CodeBuild | ✓ Fargate | ## Build Environment Variables Build environment variables are available during the CodeBuild phase of your deployment pipeline and are used to configure your application before deployment. ### Plain Variables Pass key-value pairs directly to CodeBuild: ```ts const stackProps: StaticProps = { // ... other props buildProps: { buildcmd: 'bun run build', variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, { ANALYTICS_ID: 'gtag-12345' } ], }, }; ``` Variables are available during the build process and embedded in your application bundle: ```bash # During build echo $NODE_ENV # production echo $PUBLIC_API_URL # https://api.example.com ``` ### Secrets Store sensitive build secrets in **AWS Parameter Store** as SecureString parameters. CodeBuild automatically decrypts and injects them during the build phase. ```ts const stackProps: StaticProps = { // ... other props buildProps: { buildcmd: 'bun run build', secrets: [ { key: 'NPM_TOKEN', resource: 'arn:aws:ssm:us-east-1:123456789012:parameter/npm-token' }, { key: 'GITHUB_TOKEN', resource: 'arn:aws:ssm:us-east-1:123456789012:parameter/github-token' } ], }, }; ``` **Creating Parameter Store Secrets:** 1. Go to AWS Systems Manager → Parameter Store 2. Create parameter with name: e.g. `/thunder/npm-token` 3. Select **SecureString** type (uses KMS encryption) 4. Paste your secret value 5. Reference in your stack configuration ```bash # Create a SecureString parameter aws ssm put-parameter \ --name "/thunder/npm-token" \ --value "your-npm-token-value" \ --type "SecureString" ``` Secrets are not embedded in your build output and are only available during the build phase. ## Runtime Environment Variables Runtime environment variables are available when your application is executing. Supported for Serverless Functions (Lambda) and Web Service (Fargate) patterns. ### Plain Variables Pass configuration to your Lambda function or Fargate container: ```ts // Lambda/Serverless Functions const fnProps: LambdaProps = { // ... other props functionProps: { variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, { MAX_CONNECTIONS: '100' } ], } }; ``` ```ts // Fargate/Web Service const svcProps: FargateProps = { // ... other props serviceProps: { variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, { LOG_LEVEL: 'info' } ], }, }; ``` Access variables in your application code: ```ts // Node.js/TypeScript const apiUrl = process.env.PUBLIC_API_URL; const maxConnections = parseInt(process.env.MAX_CONNECTIONS || '50'); ``` ### Secrets Store sensitive runtime secrets in **AWS Secrets Manager**. Your Lambda function or Fargate task automatically receives permissions to read these secrets. ```ts // Lambda/Serverless Functions const fnProps: LambdaProps = { // ... other props functionProps: { secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:db-url-abc123' }, { key: 'API_KEY', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:external-api-key-xyz789' } ], } }; ``` ```ts // Fargate/Web Service const svcProps: FargateProps = { // ... other props serviceProps: { secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:db-url-abc123' }, { key: 'STRIPE_SECRET_KEY', resource: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:stripe-key-def456' } ], }, }; ``` Access secrets the same way as environment variables: ```ts // Access in your application code const dbUrl = process.env.DATABASE_URL; const stripeKey = process.env.STRIPE_SECRET_KEY; ``` **Creating Secrets Manager Secrets:** 1. Go to AWS Secrets Manager 2. Click **Store a new secret** 3. Select **Other type of secret** 4. Enter secret value (plain text) 5. Give it a name: e.g. `db-url-abc123` 6. Note the full ARN 7. Reference the ARN in your stack configuration ```bash # Create a secret with plain text value aws secretsmanager create-secret \ --name "db-url-abc123" \ --secret-string "postgres://user:password@host:5432/dbname" ``` The library automatically grants your Lambda function or Fargate task the `secretsmanager:GetSecretValue` permission for referenced secrets. --- --- title: Redirects and rewrites description: Configure redirects and rewrites in Thunder Console --- # Redirects and rewrites The terms "redirect" and "rewrite" refer to different ways of handling HTTP requests, and they serve distinct purposes in web development. ## CloudFront Lambda@Edge For patterns that use CloudFront distributions (such as [Static](/docs/patterns/static)), redirects and rewrites are implemented using [**AWS Lambda@Edge**](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html) functions that execute at [CloudFront](https://docs.aws.amazon.com/cloudfront/) edge locations. This provides low-latency routing decisions across a global network of edge servers, allowing you to: - Process requests without additional server round-trips - Implement intelligent routing based on request properties - Serve dynamic content patterns with minimal latency - Scale globally without infrastructure management This feature is available for static site hosting patterns that leverage CloudFront. Other hosting patterns may have different routing capabilities. ## Redirect A redirect is an HTTP response that instructs the client's browser to make a new request to a different URL. When implemented through Lambda@Edge at CloudFront, redirects happen at edge locations without requiring requests to reach your origin server. *HTTP Status Codes*: Commonly uses status codes like 301 (Moved Permanently) or 302 (Found/Temporary Redirect). See [HTTP Status Codes](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Redirects.html) in the CloudFront documentation for more details. *Client-Side Action*: The browser updates the URL in the address bar to the new location specified in the Location header of the response. Use Cases: - Moving content to a new URL to avoid a broken link when the address of a page changes. - To avoid a broken link when a user makes a predictable typo in an address. SEO Impact: A 301 redirect passes most of the SEO value from the old URL to the new one, while a 302 redirect does not. ## Rewrite A rewrite (also called a 200 Redirect) modifies the URL path internally on the server without changing the URL in the client's browser. When implemented through [Lambda@Edge](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html) at CloudFront, rewrites are processed at edge locations, allowing CloudFront to serve the rewritten content without client awareness or additional requests. *No HTTP Status Code*: Since the rewrite is internal, it doesn't involve sending a new HTTP status code to the client. *Server-Side Action*: The server processes the request as if it was made to the rewritten URL. Use Cases: - Serving a single-page application (SPA) by rewriting all paths to index.html. - Handling legacy URLs without changing the visible URL structure. SEO Impact: Since the URL in the browser doesn't change, rewrites don't directly impact SEO. ## Pattern Configure redirects and rewrites for CloudFront-based deployments using pattern matching. Patterns support static paths, wildcards, and placeholders to handle various URL transformation scenarios. Using static paths: | **Source** | **Destination** | |----------------------------|--------------------------| | /home | / | Using wildcards: | **Source** | **Destination** | **Example Effect** | |----------------------------------|-------------------------------|----------------------------------------------| | /guide/* | /blog/* | /guide/path1/blog/path1 | | /cms/* | /* | /cms/path1/path1 | Using placeholders: | **Source** | **Destination** | **Example Effect** | |----------------------------|--------------------------|-------------------------------------------| | /docs/:any | /:any | /docs/introduction/introduction | | /blog/posts/:postid | /blog/:postid | /blog/posts/123/blog/123 | | /updates/:year/:month | /changelog/:year/:month | /updates/2023/10/changelog/2023/10 | --- --- title: Response Headers description: Configure response headers in Thunder Console --- # Response Headers Response headers are HTTP headers sent from your server to clients, controlling caching behavior, security policies, and cross-origin access. ## CloudFront Lambda@Edge For patterns that use CloudFront distributions (such as [Static](/docs/patterns/static)), response headers are configured using [**AWS Lambda@Edge**](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html) functions that execute at [CloudFront](https://docs.aws.amazon.com/cloudfront/) edge locations. This allows you to: - Set headers at the edge before content is served to clients - Apply headers globally across all edge locations - Control caching and security headers with minimal latency - Override or supplement origin headers without origin server changes This feature is available for static site hosting patterns that leverage CloudFront. Other hosting patterns may have different header configuration capabilities. ## Default Settings Thunder provides factory defaults for your single page application that implement security best practices: | Security Header | Default Value | |------------------------------|-----------------------------------------| | `x-frame-options` | `DENY` | | `referrer-policy` | `strict-origin-when-cross-origin` | | `x-content-type-options` | `nosniff` | | `strict-transport-security` | `max-age=31536000; includeSubDomains` | | `Content-Security-Policy` | `default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:` | | `X-XSS-Protection` | `1; mode=block` | | CORS Header | Default Value | |---------------------------------------------|---------------------------------------| | `Access-Control-Allow-Origin` | `*` | | `Access-Control-Allow-Credentials` | `false` | | `Access-Control-Allow-Methods` | `GET, HEAD, OPTIONS` | | `Access-Control-Allow-Headers` | `*` | | `Access-Control-Max-Age` | `600` | ## Header Syntax Define which requests receive custom headers using path patterns. The header path must be a relative path without the domain and will be matched across all custom domains attached to your site. You can use wildcards to match arbitrary request paths: | Path | Effect | |--------------------|------------------------------------------| | `/*` | Only the root directory paths. | | `/**` | All request paths, including the root path and all sub-paths | | `/blog/*` | Matches `/blog/`, `/blog/latest-post/`, and all other paths under `/blog/` | | `/**/*` | Matches `/blog/`, `/assets/`, and all other paths with at least two slashes. | ## Custom Response Headers Override the defaults and add custom headers with path patterns to control caching, security, and CORS behavior. Examples: | Path | Name | Example Value | |--------------------|------------------------------|---------------------------------------| | `/*` | Cache-Control | `public, max-age=864000` | | `/api/*` | Cache-Control | `max-age=0, no-cache, no-store, must-revalidate` | | `/blog/*` | Cache-Control | `public, max-age=31536000` | | `/**` | Access-Control-Allow-Origin | `https://www.foo.com` | | `/**` | Referrer-Policy | `same-origin` | | `/**` | Content-Type | `text/html; charset=UTF-8` | --- --- title: Frequently Asked Questions about Thunder description: Frequently asked questions about Thunder --- # Frequently Asked Questions about Thunder ## General Questions ### What is Thunder? Thunder is an open source platform-as-a-service (PaaS) for AWS that makes it easy to deploy modern web applications without managing infrastructure. It's an alternative to AWS Amplify, Heroku, Render, and Vercel that runs on your own AWS infrastructure and costs $0 to run (you only pay for AWS resources). Thunder provides production-ready AWS CDK construct libraries that handle the complexity of setting up scalable, secure, and cost-effective deployments on AWS. ### How does Thunder differ from other platforms? Thunder has several key advantages: - **Infrastructure as Code** — Use TypeScript/AWS CDK for full control and visibility - **Cost Effective** — Only pay for AWS resources you use - **Framework Agnostic** — Supports any web framework (Next.js, Astro, Express, Hono, etc.) - **Deployment Patterns** — Three proven patterns for different use cases (SPA, Serverless, Web Services) - **Open Source** — Source code is public on GitHub, community can contribute - **Your Infrastructure** — Deploy to your own AWS account, complete control ### Do I need to know AWS to use Thunder? No, but it helps. Thunder abstracts away most AWS complexity by providing pre-configured CDK stacks. You define your deployment in a TypeScript configuration file and Thunder handles creating the necessary AWS resources. If you want to customize deployments or troubleshoot issues, basic AWS knowledge is helpful. ### Is Thunder free? Thunder itself is free and open source. The Console has a free tier. You only pay for the AWS resources you use (S3, Lambda, ECS Fargate, CloudFront, etc.) on your own AWS account. --- ## Getting Started ### Which deployment pattern should I choose? Thunder provides three deployment patterns, each optimized for different use cases: **Single Page Applications (SPA)** - Use for: Client-side apps, static sites, Astro, Next.js static export - Resources: S3 + CloudFront CDN - Cost: ~$1-5/month for typical traffic - Best for: Fast, globally distributed static content **Serverless Functions** - Use for: APIs, microservices, event-driven workloads - Resources: Lambda + API Gateway - Cost: ~$0-10/month for typical traffic (pay-per-request) - Best for: Dynamic backends, lightweight APIs, variable traffic **Web Services** - Use for: Full-stack apps (Next.js, Nuxt), server-rendered content - Resources: ECS Fargate + Application Load Balancer - Cost: ~$30-50/month baseline - Best for: Complex applications, persistent state, background jobs See [Patterns](/docs/patterns) for detailed comparison. ### What frameworks does Thunder support? **Single Page Applications:** - Astro, Next.js (static export), Vite, Gatsby, React Router, and any static site generator **Serverless APIs:** - Express.js, Hono, NestJS, Fastify, Koa, AdonisJS, Sails.js, LoopBack, Feathers, Restify, and any Node.js web framework **Web Services:** - Next.js (full), Nuxt, TanStack Start, SvelteKit, Astro (server mode), Remix, and any containerized application If your framework isn't listed, you can likely still use Thunder with some customization. Check [Frameworks](/docs/frameworks) or ask in our [Discord community](https://discord.gg/uNbrp6QYZ6). ### How do I get started? 1. **Sign up** with your GitHub account at [thunder.so](https://thunder.so) 2. **Connect an AWS account** through the console 3. **Install the GitHub App** to give Thunder access to your repositories 4. **Import a project** from GitHub 5. **Choose a deployment pattern** (SPA, Functions, or Web Services) 6. **Deploy** with one click --- ## AWS & Infrastructure ### How do I connect my AWS account? Thunder uses AWS CloudFormation to create an IAM role in your account that grants us permission to provision infrastructure on your behalf. 1. In the Thunder console, go to AWS Accounts 2. Click "Connect AWS Account" 3. You'll be redirected to CloudFormation with a pre-filled template 4. Click "Create stack" in CloudFormation 5. Thunder gets notified and you're connected You remain in full control—we only have permissions you grant through CloudFormation. ### Is my AWS account secure? Yes. Thunder uses industry-standard security practices: - **IAM Roles** — We use temporary credentials, not API keys - **Least Privilege** — IAM role has minimal required permissions - **No Access to Secrets** — We can't read your data - **Your Control** — You can revoke permissions or delete the IAM role anytime - **No Vendor Lock-in** — Deploy infrastructure with standard AWS CDK You can review the exact IAM permissions granted in the CloudFormation template before creating the stack. ### What happens if Thunder goes down? Your applications continue running on AWS—they're not affected by Thunder's availability. Thunder is only used during deployment and configuration. Your AWS resources (Lambda, S3, CloudFront, Fargate, etc.) run independently and are managed by AWS's infrastructure. ### Can I manage resources outside Thunder? Yes, absolutely. You own the AWS infrastructure. You can: - Modify CloudFormation stacks directly - Use AWS Console to manage resources - Add additional resources alongside Thunder - Modify security groups, IAM policies, etc. Thunder doesn't prevent or interfere with direct AWS management. --- ## Development & Deployment ### How do I manage environment variables and secrets? Thunder supports two ways to handle sensitive data: **Environment Variables** — Plain text configuration ```ts variables: [ { key: 'API_URL', value: 'https://api.example.com' } ] ``` **Secrets** — Stored securely in AWS Secrets Manager ```ts secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:...' } ] ``` Create secrets in AWS Secrets Manager, then reference them in your Thunder configuration. Thunder automatically grants your Lambda/Fargate permissions to read them. ### Can I use a database? Yes! You can use any AWS database service: - **RDS** — PostgreSQL, MySQL, MariaDB, Oracle, SQL Server - **DynamoDB** — NoSQL managed database - **ElastiCache** — Redis, Memcached for caching - **DocumentDB** — MongoDB-compatible database - **Neptune** — Graph database Create the database separately in AWS (or with CDK), then pass connection strings as environment variables or secrets. ### How do CI/CD and deployments work? Thunder integrates with GitHub Actions: 1. Create a `.github/workflows/deploy.yml` file in your repository 2. Add AWS credentials as GitHub repository secrets 3. On each push, GitHub Actions builds and deploys your app 4. Thunder runs `cdk deploy` to update your infrastructure See deployment guides for [Static](/docs/patterns/static), [Lambda](/docs/patterns/lambda), and [Fargate](/docs/patterns/fargate). ### What's the cold start latency? **Lambda (Serverless Functions):** - Warm: ~10-50ms - Cold start: ~500ms-2s (Node.js), ~100-500ms (Bun) - Use `keepWarm: true` in config to prevent cold starts **Fargate (Web Services):** - Always warm, no cold starts - ~50-200ms response time depending on your app **CloudFront (SPA):** - Cached: under 50ms globally - Origin: ~100-500ms for cache misses --- --- title: Supported Frameworks description: Supported frameworks for AWS deployment and suitable architecture --- # Supported Frameworks Thunder supports a wide range of modern web frameworks across four deployment patterns. Deploy your favorite framework on AWS. ## Frameworks --- ## Pattern Reference --- ## Quick Reference | Framework | Static | Lambda | Serverless | Fargate | |-----------|--------|--------|------------|---------| | **Next.js** | ✓ | | | ✓ | | **Nuxt** | ✓ | | ✓ | ✓ | | **Astro** | ✓ | | ✓ | ✓ | | **TanStack Start** | ✓ | | ✓ | ✓ | | **SvelteKit** | ✓ | | ✓ | ✓ | | **Solid Start** | ✓ | | ✓ | ✓ | | **AnalogJS** | ✓ | | ✓ | ✓ | | **React Router** | ✓ | | | ✓ | | **NestJS** | | ✓ | | ✓ | | **Hono** | | ✓ | | ✓ | | **Vite** | ✓ | | | | | **VitePress** | ✓ | | | | --- --- title: Deploy Analog on AWS description: Deploy Analog applications on AWS using static hosting with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy Analog on AWS Deploy your [Analog](https://analogjs.org/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Analog project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx create-analog@latest my-analog-app cd my-analog-app ``` ```sh npm create analog@latest my-analog-app cd my-analog-app ``` ```sh pnpm create analog my-analog-app cd my-analog-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Analog Static Site Deployment Deploy a fully pre-rendered Analog site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. Every page is generated at build time and served as static files — no server required. ### Configure Enable static site generation in your Vite config by setting `static: true` on the Analog plugin. Refer to the [Analog SSG docs](https://analogjs.org/docs/features/server/static-site-generation) for route configuration. The build output will be placed in `dist/analog/public/`. ```ts title="vite.config.ts" export default defineConfig({ plugins: [ analog({ static: true }), ], }); ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'dist/analog/public', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your Analog app first to generate the static files, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## Analog Containerized Deployment with Fargate Run your Analog app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server Analog enables SSR by default via [Nitro](https://nitro.unjs.io/). No additional configuration is needed for the Fargate pattern — the default build output in `dist/analog/` includes a Node.js-compatible server entry point. Refer to the [Analog SSR docs](https://analogjs.org/docs/features/server/server-side-rendering) for details. ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/dist/analog ./ EXPOSE 3000 CMD ["bun", "run", "server/index.mjs"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## Analog Serverless Fullstack Deployment Deploy Analog with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Configure Analog for AWS Lambda Analog uses [Nitro](https://nitro.unjs.io/) as its server engine. Set the `aws-lambda` preset in your Vite config to tell Nitro to output a Lambda-compatible handler instead of a Node.js HTTP server. ```ts title="vite.config.ts" export default defineConfig({ plugins: [ analog({ nitro: { preset: 'aws-lambda', }, }), ], }); ``` The build will produce `dist/analog/server/` (Lambda handler) and `dist/analog/public/` (static assets for S3). ### Stack (Zip mode) The `AnalogJS` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: AnalogJSProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new AnalogJS(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: AnalogJSProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: AnalogJSProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your Analog app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy Astro on AWS description: Deploy Astro applications on AWS using static hosting with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy Astro on AWS Deploy your [Astro](https://astro.build/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Astro project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx create-astro@latest my-astro-app cd my-astro-app ``` ```sh npm create astro@latest my-astro-app cd my-astro-app ``` ```sh pnpm create astro my-astro-app cd my-astro-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Astro Static Site Deployment Deploy a fully pre-rendered Astro site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. This is the simplest and most cost-effective pattern — no server required. Every page is generated at build time and served as static files. ### Configure Astro defaults to static output, but it's good practice to be explicit. Set `output: 'static'` in your config to ensure all pages are pre-rendered at build time. ```js title="astro.config.mjs" export default defineConfig({ output: 'static', }); ``` ### Stack Create a stack file that defines your AWS infrastructure. The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your Astro site first — this generates the static files in `dist/`. Then deploy with CDK, which uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## Astro Containerized Deployment with Fargate Run your Astro app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server Install the official Astro Node.js adapter, then configure it in standalone mode so the build output is a self-contained server entry point. ```sh bun add @astrojs/node ``` ```sh npm install @astrojs/node ``` ```sh pnpm add @astrojs/node ``` ```js title="astro.config.mjs" export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), }); ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 4321, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM public.ecr.aws/docker/library/node:22-alpine AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH" RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM public.ecr.aws/docker/library/node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=4321 COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ EXPOSE 4321 CMD ["node", "./dist/server/entry.mjs"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## Astro Serverless Fullstack Deployment Deploy Astro with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Install Adapter for Lambda The `@astro-aws/adapter` package adapts Astro's SSR output to the [Lambda function handler](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) format expected by API Gateway. ```sh bun add @astro-aws/adapter ``` ```sh npm install @astro-aws/adapter ``` ```sh pnpm add @astro-aws/adapter ``` ### Configure Astro for AWS Lambda Set `output: 'server'` to enable SSR and point the adapter at `@astro-aws/adapter`. The build will produce a Lambda handler in `dist/lambda/` and static assets in `dist/client/`. ```js title="astro.config.mjs" export default defineConfig({ output: 'server', adapter: aws(), }); ``` ### Stack (Zip mode) The `Astro` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: AstroProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new Astro(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: AstroProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: AstroProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your Astro app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy Hono on AWS description: Deploy Hono applications on AWS Lambda and API Gateway, or as containerized services with ECS Fargate and Application Load Balancer. --- # Deploy Hono on AWS Deploy your [Hono](https://hono.dev/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Hono project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bun create hono my-hono-app cd my-hono-app ``` ```sh npm create hono@latest my-hono-app cd my-hono-app ``` ```sh pnpm create hono my-hono-app cd my-hono-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Hono Lambda Deployment Deploy your Hono API to [AWS Lambda](https://aws.amazon.com/lambda/) with [API Gateway](https://aws.amazon.com/api-gateway/) as the public HTTP endpoint. Hono has first-class support for Lambda via its `hono/aws-lambda` adapter — no additional packages needed. ### Configure Hono for AWS Lambda Hono's `handle()` adapter wraps your app in the [Lambda handler signature](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) expected by API Gateway. Export it as `handler` and your function is ready to deploy. ```ts title="src/index.ts" const app = new Hono() app.get('/', (c) => c.json({ message: 'Hello from Hono!' })) export const handler = handle(app) ``` Hono on Lambda works best bundled to a single file with esbuild. Add a build script to `package.json`: ```json title="package.json" { "scripts": { "build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node22 ./src/index.ts" } } ``` Running the build produces `dist/index.js` — the file Lambda will execute. ### Stack (Zip mode) The `Lambda` construct provisions a Lambda function and an API Gateway HTTP API. The Zip mode packages `dist/` directly — no Docker required. ```ts title="stack/prod.ts" const config: LambdaProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, codeDir: 'dist', handler: 'index.handler', memorySize: 512, timeout: 10, keepWarm: true, }, }; new Lambda(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies, switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `functionProps` to enable container mode. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { dockerFile: 'Dockerfile', memorySize: 1792, timeout: 10, keepWarm: true, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 AS builder WORKDIR ${LAMBDA_TASK_ROOT} COPY . . RUN npm install RUN npm run build FROM public.ecr.aws/lambda/nodejs:22 WORKDIR ${LAMBDA_TASK_ROOT} COPY --from=builder /var/task/dist/* ./ COPY --from=builder /var/task/node_modules ./node_modules CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build the handler first, then deploy with CDK. CDK outputs the API Gateway URL. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **API Gateway URL** for your function. --- ## Hono Containerized Deployment with Fargate Run your Hono API as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern is ideal for long-running services, persistent connections, and workloads that exceed Lambda's limits. ### Configure for Node Server Install `@hono/node-server` to run Hono as a standard HTTP server — the same code works locally and inside the container. ```sh bun add @hono/node-server ``` ```sh npm install @hono/node-server ``` ```sh pnpm add @hono/node-server ``` ```ts title="src/index.ts" const app = new Hono() app.get('/', (c) => c.json({ message: 'Hello from Hono!' })) serve({ fetch: app.fetch, port: Number(process.env.PORT) || 3000, }) ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM public.ecr.aws/docker/library/node:22-alpine AS builder WORKDIR /app COPY package.json bun.lockb tsconfig.json ./ RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH" RUN bun install --frozen-lockfile COPY src ./src RUN bun run build FROM public.ecr.aws/docker/library/node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package.json ./ EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- --- title: Deploy NestJS on AWS description: Deploy NestJS applications on AWS Lambda and API Gateway, or as containerized services with ECS Fargate and Application Load Balancer. --- # Deploy NestJS on AWS Deploy your [NestJS](https://nestjs.com/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Install the NestJS CLI and scaffold a new project. This sets up the project structure, installs dependencies, and prepares you for development. ```sh npm install -g @nestjs/cli nest new my-nestjs-app cd my-nestjs-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## NestJS Lambda Deployment Deploy your NestJS API to [AWS Lambda](https://aws.amazon.com/lambda/) with [API Gateway](https://aws.amazon.com/api-gateway/) as the public HTTP endpoint. NestJS adapts to Lambda using `@vendia/serverless-express`, which wraps the Express HTTP adapter in a Lambda-compatible handler. ### Install Adapter for Lambda `@vendia/serverless-express` bridges NestJS's Express adapter to the [Lambda handler format](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) expected by API Gateway. ```sh bun add @vendia/serverless-express bun add -D @types/aws-lambda ``` ```sh npm install @vendia/serverless-express npm install -D @types/aws-lambda ``` ```sh pnpm add @vendia/serverless-express pnpm add -D @types/aws-lambda ``` ### Configure NestJS for AWS Lambda Create a separate entry point for Lambda. This bootstraps the NestJS app once on cold start and reuses the cached handler across subsequent invocations — avoiding the cost of re-initializing the app on every request. ```ts title="src/lambda.ts" let cachedHandler: any; async function bootstrap() { const expressApp = express(); const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp)); await app.init(); return serverlessExpress({ app: expressApp }); } export const handler = async (event: any, context: any) => { if (!cachedHandler) { cachedHandler = await bootstrap(); } return cachedHandler(event, context); }; ``` Reference: [NestJS Serverless docs](https://docs.nestjs.com/faq/serverless) ### Stack (Zip mode) The `Lambda` construct provisions a Lambda function and an API Gateway HTTP API. Point `codeDir` at the NestJS build output (`dist/`) and set the handler to the Lambda entry point. ```ts title="stack/prod.ts" const config: LambdaProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, codeDir: 'dist', handler: 'lambda.handler', memorySize: 1792, timeout: 10, keepWarm: true, }, }; new Lambda(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. NestJS apps with many dependencies can exceed this. Switch to container mode — Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `functionProps` to enable container mode. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { dockerFile: 'Dockerfile', memorySize: 1792, timeout: 10, keepWarm: true, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 AS builder WORKDIR ${LAMBDA_TASK_ROOT} COPY . . RUN npm ci RUN npm run build FROM public.ecr.aws/lambda/nodejs:22 WORKDIR ${LAMBDA_TASK_ROOT} COPY --from=builder /var/task/dist/ ./ COPY --from=builder /var/task/node_modules ./node_modules CMD ["lambda.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build the NestJS app first — `nest build` compiles TypeScript to `dist/`. Then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **API Gateway URL** for your function. --- ## NestJS Containerized Deployment with Fargate Run your NestJS API as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). No Lambda adapter needed — NestJS runs as a standard HTTP server. This pattern is ideal for long-running services, WebSocket support, and workloads that exceed Lambda's limits. ### Configure for Node Server NestJS listens on port 3000 by default. Make it configurable via environment variable so the container runtime can override it if needed. ```ts title="src/main.ts" async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(process.env.PORT ?? 3000); } bootstrap(); ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build compiles TypeScript in the builder stage, then copies only the compiled output and production dependencies into the final image. ```dockerfile title="Dockerfile" FROM public.ecr.aws/docker/library/node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM public.ecr.aws/docker/library/node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/main"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- --- title: Deploy Next.js on AWS description: Deploy Next.js applications on AWS using static export with S3 and CloudFront, or full-stack SSR with ECS Fargate and Application Load Balancer. --- # Deploy Next.js on AWS Deploy your [Next.js](https://nextjs.org/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Next.js project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx create-next-app@latest my-nextjs-app cd my-nextjs-app ``` ```sh npm create next-app@latest my-nextjs-app cd my-nextjs-app ``` ```sh pnpm create next-app my-nextjs-app cd my-nextjs-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Next.js Static Export Deployment Deploy Next.js as a fully static site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. In [static export mode](https://nextjs.org/docs/pages/guides/static-exports), Next.js pre-renders all pages at build time and outputs plain HTML, CSS, and JavaScript — no server required. ### Configure Set `output: 'export'` in your Next.js config to enable static export mode. Setting `distDir` to `dist` keeps the output directory consistent with other frameworks. ```ts title="next.config.ts" const nextConfig: NextConfig = { output: 'export', distDir: 'dist', }; export default nextConfig; ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your Next.js app first to generate the static export, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## Next.js Containerized Deployment with Fargate Run your Next.js app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, image optimization, and all Next.js features. ### Configure for Node Server Set `output: 'standalone'` in your Next.js config. This tells Next.js to produce a minimal, self-contained server bundle in `.next/standalone/` that includes only the files needed to run the app — ideal for Docker. ```ts title="next.config.ts" const nextConfig: NextConfig = { output: 'standalone', }; export default nextConfig; ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build uses Bun to install dependencies and build the app, then copies only the standalone output into a minimal Node.js runtime image. ```dockerfile title="Dockerfile" FROM public.ecr.aws/docker/library/node:22-alpine AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH" RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM public.ecr.aws/docker/library/node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV HOSTNAME=0.0.0.0 ENV PORT=3000 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 CMD ["node", "server.js"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, { NEXT_PUBLIC_API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- --- title: Deploy Nuxt on AWS description: Deploy Nuxt applications on AWS using client-side rendering with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy Nuxt on AWS Deploy your [Nuxt](https://nuxt.com/) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Nuxt project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx nuxi@latest init my-nuxt-app cd my-nuxt-app bun install ``` ```sh npx nuxi@latest init my-nuxt-app cd my-nuxt-app npm install ``` ```sh pnpm dlx nuxi@latest init my-nuxt-app cd my-nuxt-app pnpm install ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Nuxt Static Site Deployment Deploy a client-side rendered Nuxt app to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. In this mode, Nuxt disables server-side rendering and outputs a fully client-rendered SPA — no server required. ### Configure Disable SSR in your Nuxt config to switch to client-side rendering mode. The build output will be placed in `.output/public/`. ```ts title="nuxt.config.ts" export default defineNuxtConfig({ ssr: false, }) ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: '.output/public', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your Nuxt app first, then deploy with CDK. CDK uploads the static files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your app is live. --- ## Nuxt Containerized Deployment with Fargate Run your Nuxt app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server Nuxt's default build output (`.output/`) is already compatible with a Node.js server — no additional adapter is needed. Just ensure your `nuxt.config.ts` does not have `ssr: false`. ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/.output ./ EXPOSE 3000 CMD ["bun", "run", "server/index.mjs"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## Nuxt Serverless Fullstack Deployment Deploy Nuxt with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Configure Nuxt for AWS Lambda Nuxt uses [Nitro](https://nitro.unjs.io/) as its server engine. Set the `aws-lambda` preset to tell Nitro to output a Lambda-compatible handler instead of a Node.js HTTP server. You can set this via an environment variable or directly in your config. ```sh title=".env" NITRO_PRESET=aws-lambda ``` ```ts title="nuxt.config.ts" export default defineNuxtConfig({ nitro: { preset: 'aws-lambda', }, }) ``` The build will produce `.output/server/` (Lambda handler) and `.output/public/` (static assets for S3). ### Stack (Zip mode) The `Nuxt` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: NuxtProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new Nuxt(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your Nuxt app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy React Router on AWS description: Deploy React Router applications on AWS using CSR/static pre-rendering with S3 and CloudFront, or full-stack SSR with ECS Fargate and Application Load Balancer. --- # Deploy React Router on AWS Deploy your [React Router](https://reactrouter.com/) applications to AWS using Thunder patterns. This guide covers client-side rendering (CSR), static pre-rendering, and full-stack server-side rendering options. There are two deployment patterns available for React Router on AWS: ## Getting Started ### Create Project ```sh npm create vite@latest my-react-router-app -- --template react-ts cd my-react-router-app npm install react-router-dom@7 ``` ```sh pnpm create vite my-react-router-app --template react-ts cd my-react-router-app pnpm add react-router-dom@7 ``` ```sh bun create vite my-react-router-app --template react-ts cd my-react-router-app bun add react-router-dom@7 ``` ## Single Page Application (SPA) Deployment --- Deploy React Router applications to S3 and CloudFront using the `Static` construct. This pattern supports both client-side rendering (CSR) and static pre-rendering, offering flexibility for different use cases. ### Configure React Router Choose one of the following configurations based on your needs: **Client-Side Rendering (CSR)** — Routes are always client-side rendered as users navigate: ```ts title="react-router.config.ts" export default { ssr: false, } satisfies Config; ``` **Static Pre-rendering** — Generate static HTML at build time for specific routes: ```ts title="react-router.config.ts" export default { // Return a list of URLs to prerender at build time async prerender() { return ["/", "/about", "/contact"]; }, } satisfies Config; ``` Pre-rendering generates static HTML and client navigation data payloads for a list of URLs, offering better performance and SEO without requiring a server. Route module loaders are used to fetch data at build time. Individual routes can also use client data loading with `clientLoader` to supplement pre-rendered data. ### Stack ```ts title="stack/prod.ts" const myApp: StaticProps = { env: { account: 'your-account-id', region: 'us-east-1' }, application: 'your-application-id', service: 'your-service-id', environment: 'production', rootDir: '', // e.g. 'frontend' for monorepos outputDir: 'dist', }; new Static( new Cdk.App(), `${myApp.application}-${myApp.service}-${myApp.environment}-stack`, myApp ); ``` ### Deploy Build and deploy your React Router SPA: ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" ``` ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" ``` After deployment, you'll receive a **CloudFront URL** to access your application. ## Full Stack Deployment (SSR) --- Deploy server-side rendered React Router applications using ECS Fargate and Application Load Balancer with the `Fargate` construct. ### Configure React Router for SSR ```ts title="react-router.config.ts" export default { ssr: true, } satisfies Config; ``` Server-side rendering requires a deployment that supports it. Individual routes can still be statically pre-rendered, and routes can also use client data loading with `clientLoader` to avoid server rendering/fetching for their portion of the UI. ### Stack ```ts title="stack/prod.ts" const svcProps: FargateProps = { env: { account: 'your-account-id', region: 'us-east-1' }, application: 'your-application-id', service: 'your-service-id', environment: 'production', rootDir: '', serviceProps: { architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate( new Cdk.App(), `${svcProps.application}-${svcProps.service}-${svcProps.environment}-stack`, svcProps ); ``` ### Build Settings Using Nixpacks Configure automatic containerization with Nixpacks: ```ts title="stack/prod.ts" const svcProps: FargateProps = { // ... other props buildProps: { buildSystem: 'Nixpacks', installcmd: 'bun install', buildcmd: 'bun run build', startcmd: 'bun start', }, }; ``` ### Build Settings Using Docker Container Alternatively, use a custom Dockerfile: ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/build ./build EXPOSE 3000 CMD ["bun", "run", "./build/server/index.js"] ``` ```ts title="stack/prod.ts" const svcProps: FargateProps = { // ... other props serviceProps: { dockerFile: 'Dockerfile', port: 3000, }, }; ``` ### Environment Variables and Secrets for SSR Configure runtime environment variables and secrets: ```ts title="stack/prod.ts" const svcProps: FargateProps = { // ... other props serviceProps: { variables: [ { NODE_ENV: 'production' }, { VITE_API_URL: 'https://api.example.com' } ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-west-2:123456789012:secret:/my-app/DATABASE_URL-abc123' }, ], }, }; ``` ### Deploy Build and deploy your containerized application: ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" ``` ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" ``` After deployment, you'll receive an **Application Load Balancer URL** to access your SSR application. --- --- title: Deploy Solid Start on AWS description: Deploy Solid Start applications on AWS using static site generation with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy Solid Start on AWS Deploy your [Solid Start](https://start.solidjs.com) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Solid Start project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx create-solid@latest my-solid-app cd my-solid-app ``` ```sh npm create solid@latest my-solid-app cd my-solid-app ``` ```sh pnpm create solid my-solid-app cd my-solid-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Solid Start Static Site Deployment Deploy a fully pre-rendered Solid Start site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. Every page is generated at build time and served as static files — no server required. ### Configure Solid Start uses [Nitro](https://nitro.unjs.io/) as its server engine. Set the preset to `static` to pre-render all routes at build time and output them to `dist/`. ```ts title="app.config.ts" export default defineConfig({ server: { preset: 'static' }, }); ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your app first to generate the static files, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## Solid Start Containerized Deployment with Fargate Run your Solid Start app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server Set the Nitro preset to `node-server` so the build output is a standard Node.js HTTP server that can run inside a container. The output will be placed in `.output/`. ```ts title="app.config.ts" export default defineConfig({ server: { preset: 'node-server' }, }); ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/.output ./ EXPOSE 3000 CMD ["bun", "run", "server/index.mjs"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## Solid Start Serverless Fullstack Deployment Deploy Solid Start with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Configure Solid Start for AWS Lambda Solid Start uses [Nitro](https://nitro.unjs.io/) as its server engine. Set the `aws-lambda` preset to tell Nitro to output a Lambda-compatible handler instead of a Node.js HTTP server. ```ts title="app.config.ts" export default defineConfig({ server: { preset: 'aws-lambda' }, }); ``` The build will produce `.output/server/` (Lambda handler) and `.output/public/` (static assets for S3). ### Stack (Zip mode) The `SolidStart` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: SolidStartProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new SolidStart(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: SolidStartProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: SolidStartProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your Solid Start app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy SvelteKit on AWS description: Deploy SvelteKit applications on AWS using static site generation with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy SvelteKit on AWS Deploy your [SvelteKit](https://svelte.dev/docs/kit) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new SvelteKit project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx sv create my-sveltekit-app cd my-sveltekit-app ``` ```sh npx sv create my-sveltekit-app cd my-sveltekit-app ``` ```sh pnpm dlx sv create my-sveltekit-app cd my-sveltekit-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## SvelteKit Static Site Deployment Deploy a fully pre-rendered SvelteKit site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. This pattern supports both SSG (pre-rendered pages) and SPA mode (client-side routing with a single HTML shell). No server required. ### Configure Install the static adapter and configure SvelteKit to use it. Then enable prerendering globally by setting `prerender = true` in your root layout. ```sh bun add -D @sveltejs/adapter-static ``` ```sh npm install -D @sveltejs/adapter-static ``` ```sh pnpm add -D @sveltejs/adapter-static ``` ```js title="svelte.config.js" export default { kit: { adapter: adapter(), }, }; ``` ```ts title="src/routes/+layout.ts" export const prerender = true; ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. SvelteKit's static adapter outputs to `build/` by default. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'build', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your SvelteKit site first, then deploy with CDK. CDK uploads the static files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## SvelteKit Containerized Deployment with Fargate Run your SvelteKit app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server Install the Node adapter and configure SvelteKit to use it. This produces a standard Node.js HTTP server in `build/` that can run inside a container. ```sh bun add -D @sveltejs/adapter-node ``` ```sh npm install -D @sveltejs/adapter-node ``` ```sh pnpm add -D @sveltejs/adapter-node ``` ```js title="svelte.config.js" export default { kit: { adapter: adapter(), }, }; ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/build ./build COPY --from=builder /app/package.json ./ EXPOSE 3000 CMD ["bun", "./build/index.js"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## SvelteKit Serverless Fullstack Deployment Deploy SvelteKit with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Install Adapter for Lambda The `@foladayo/sveltekit-adapter-lambda` package adapts SvelteKit's build output to the [Lambda function handler](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) format expected by API Gateway. ```sh bun add -D @foladayo/sveltekit-adapter-lambda ``` ```sh npm install -D @foladayo/sveltekit-adapter-lambda ``` ```sh pnpm add -D @foladayo/sveltekit-adapter-lambda ``` ### Configure SvelteKit for AWS Lambda Replace the adapter in your SvelteKit config. The `serveStatic: true` option is required — it tells the adapter to serve prerendered pages directly from the Lambda handler rather than expecting a separate static file server. ```js title="svelte.config.js" export default { kit: { adapter: adapter({ serveStatic: true, }), }, }; ``` The build will produce a flat `build/` directory (Lambda handler) and `build/client/` (static assets for S3). ### Stack (Zip mode) The `SvelteKit` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: SvelteKitProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new SvelteKit(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: SvelteKitProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: SvelteKitProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your SvelteKit app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy TanStack Start on AWS description: Deploy TanStack Start applications on AWS using static site generation with S3 and CloudFront, serverless SSR with Lambda, or containerized SSR with ECS Fargate. --- # Deploy TanStack Start on AWS Deploy your [TanStack Start](https://tanstack.com/start) applications to AWS using Thunder. Choose the pattern that fits your app's needs. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new TanStack Start project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bunx create-tanstack-start my-app cd my-app ``` ```sh npx create-tanstack-start my-app cd my-app ``` ```sh pnpm create @tanstack/start@latest my-app cd my-app ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## TanStack Start Static Site Deployment Deploy a fully pre-rendered TanStack Start site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. Every page is generated at build time and served as static files — no server required. ### Configure Set the Nitro preset to `static` in your app config. This tells TanStack Start to pre-render all routes at build time and output them to `.output/public/`. ```ts title="app.config.ts" export default defineConfig({ server: { preset: 'static', }, }); ``` ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: '.output/public', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your app first to generate the static files, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your static site is live. --- ## TanStack Start Containerized Deployment with Fargate Run your TanStack Start app as a Node.js server inside a Docker container on [ECS Fargate](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). This pattern supports full SSR, API routes, and any server-side logic. ### Configure for Node Server TanStack Start uses [Nitro](https://nitro.unjs.io/) as its server engine. Set the preset to `node-server` so the build output is a standard Node.js HTTP server that can run inside a container. ```ts title="app.config.ts" export default defineConfig({ server: { preset: 'node-server', }, }); ``` ### Stack The `Fargate` construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer. ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Dockerfile Create a `Dockerfile` in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime. ```dockerfile title="Dockerfile" FROM oven/bun:latest AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM oven/bun:latest AS runner WORKDIR /app ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=3000 COPY --from=builder /app/.output ./ EXPOSE 3000 CMD ["bun", "run", "server/index.mjs"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy CDK builds the Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed. ```sh npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs the **Load Balancer DNS** for your application. --- ## TanStack Start Serverless Fullstack Deployment Deploy TanStack Start with SSR using [AWS Lambda](https://aws.amazon.com/lambda/) for server-side rendering, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) to unify both behind a single domain. This pattern scales to zero and charges only for actual requests. ### Configure TanStack Start for AWS Lambda TanStack Start uses [Nitro](https://nitro.unjs.io/) as its server engine. You must explicitly set the `aws-lambda` preset — the default `node-server` preset outputs an HTTP server, not a Lambda handler, and will not work on Lambda. ```ts title="app.config.ts" export default defineConfig({ vite: { plugins: [ nitro({ preset: 'aws-lambda' }), ], }, }); ``` The build will produce `.output/server/` (Lambda handler) and `.output/public/` (static assets for S3). ### Stack (Zip mode) The `TanStackStart` construct wires up Lambda, API Gateway, S3, and CloudFront automatically. By default, Thunder packages your Lambda handler as a Zip deployment — the fastest option for most apps. ```ts title="stack/prod.ts" const config: TanStackStartProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', }; new TanStackStart(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies — native modules, ML libraries, or heavy assets — switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB. #### Stack (Container mode) Add `dockerFile` to `serverProps` to enable container mode. ```ts title="stack/prod.ts" const config: TanStackStartProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` #### Dockerfile ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Environment Variables and Secrets Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically. ```ts title="stack/prod.ts" const config: TanStackStartProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` ### Deploy Build your app first to generate the Lambda handler and static assets, then deploy with CDK. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** that serves both your SSR responses and static assets. --- --- title: Deploy Vite on AWS description: Deploy Vite-based single-page applications on AWS using S3 and CloudFront. --- # Deploy Vite on AWS Deploy your [Vite](https://vitejs.dev/) single-page applications to AWS using Thunder. Vite apps are fully client-rendered at runtime, making static hosting on [S3](https://aws.amazon.com/s3/) and [CloudFront](https://aws.amazon.com/cloudfront/) the natural fit. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new Vite project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bun create vite my-vite-app --template vanilla-ts cd my-vite-app bun install ``` ```sh npm create vite@latest my-vite-app -- --template vanilla-ts cd my-vite-app npm install ``` ```sh pnpm create vite my-vite-app --template vanilla-ts cd my-vite-app pnpm install ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## Vite Static Site Deployment Deploy your Vite SPA to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. Vite outputs all compiled assets to `dist/` by default — Thunder uploads these directly to S3 and provisions the CloudFront distribution in front of them. ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', // e.g. 'frontend' for monorepos outputDir: 'dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your Vite app first to compile and bundle all assets into `dist/`, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your app is live. --- --- title: Deploy VitePress on AWS description: Deploy VitePress documentation sites on AWS using S3 and CloudFront. --- # Deploy VitePress on AWS Deploy your [VitePress](https://vitepress.dev/) documentation sites to AWS using Thunder. VitePress generates a fully static site at build time, making [S3](https://aws.amazon.com/s3/) and [CloudFront](https://aws.amazon.com/cloudfront/) the ideal hosting target. ## Available Patterns ## Prerequisites ## Getting Started ### Create Project Scaffold a new VitePress project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development. ```sh bun create vitepress my-vitepress-site cd my-vitepress-site bun install ``` ```sh npm create vitepress@latest my-vitepress-site cd my-vitepress-site npm install ``` ```sh pnpm create vitepress my-vitepress-site cd my-vitepress-site pnpm install ``` ### Install Thunder Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure. ```sh bun add @thunder-so/thunder --development ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` --- ## VitePress Static Site Deployment Deploy your VitePress site to [S3](https://aws.amazon.com/s3/) with [CloudFront](https://aws.amazon.com/cloudfront/) as the CDN. VitePress outputs the built site to `docs/.vitepress/dist/` by default — Thunder uploads these files to S3 and provisions the CloudFront distribution in front of them. ### Stack The `Static` construct provisions an S3 bucket, a CloudFront distribution, and optionally a Route53 DNS record. ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'docs', environment: 'prod', rootDir: '.', // e.g. 'frontend' for monorepos outputDir: 'docs/.vitepress/dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy Build your VitePress site first to generate the static output, then deploy with CDK. CDK uploads the files to S3 and provisions the CloudFront distribution. ```sh bun run docs:build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` ```sh npm run docs:build npx cdk deploy --app "npx tsx stack/prod.ts" --profile default ``` ```sh pnpm run docs:build pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default ``` After deployment, CDK outputs a **CloudFront URL** where your documentation site is live. --- --- title: GitHub Integration description: Connect and configure GitHub accounts with Thunder for CI/CD deployment to AWS --- # GitHub Integration Thunder integrates with GitHub to enable seamless CI/CD pipeline deployment to your AWS account. The platform handles GitHub integration through three core workflows. ## Thunder GitHub App The first step is to install the Thunder GitHub App on your GitHub account or organization. Click the "Import Repositories" button in Thunder. This will redirect you to the GitHub App installation flow where you can authorize Thunder to access your repositories. Import Repositories Follow the on-screen instructions to complete the installation. After successful installation, you will be automatically redirected back to Thunder and can begin importing repositories. **Related:** [GitHub App documentation](https://docs.github.com/en/apps/using-github-apps/about-apps) | [Thunder GitHub App](https://github.com/apps/thunder-so/) ## Permissions Thunder supports installing the app on both personal and organization GitHub accounts, with granular control over which repositories the app can access. Github app installation ### Account Types - **Personal Account:** Install on your personal GitHub account to manage repositories you own. - **Organization Account:** Install on an organization account to manage repositories shared across your team. ### Configuring Repository Access When installing the app, you can choose to grant access to: - All repositories (current and future) - Selected repositories only You can update these permissions at any time through your GitHub account settings. **Related:** [Managing GitHub App installations](https://docs.github.com/en/apps/using-github-apps/managing-the-installation-of-your-github-app) | [Organization app permissions](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/managing-organization-installed-github-apps) ## User Access Token Thunder uses GitHub's OAuth flow to generate a personal access token for CI/CD pipeline configuration on your AWS account. During the OAuth flow, GitHub returns an authorization code. Thunder exchanges this code with GitHub's API to generate a secure access token. This token is then used to configure your CI/CD pipelines, enabling Thunder to automatically deploy your application to AWS when you push code changes. ### The OAuth Flow 1. You are prompted to authorize Thunder with your GitHub account 2. GitHub generates an authorization code 3. Thunder exchanges this code for a personal access token 4. The token is securely stored and used to configure AWS CI/CD pipelines **Related:** [GitHub OAuth documentation](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps) | [AWS CodePipeline integration](https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-webhooks.html) --- --- title: Deploy a Github repo to AWS description: How to import a Github repository and deploy on AWS using Thunder --- # Deploy a Github repo to AWS When you have successfully added an AWS account to your workspace and installed the Thunder.so Github App, you can create new applications. Click on `+ Project` button to start. ## Import a repository You will now see a list of repositories. Select the repository you want to deploy. ## Select AWS Account and Region Select your account and the region where you want to deploy your application. Select AWS account and region ## Configure your build Thunder will automatically detect the framework and any necessary build settings. However, you can configure the build settings. You can also add Environment Variables. ## Generate User Access Token Thunder will configure your AWS CodePipeline and Github repository for autodeploys. Authenticate the Github App to generate a long-term server-to-server token from Github. Thunder will create a AWS Secrets Manager `secret` and store your user token safely. The ARN of the secret resource will be used to configure your build pipeline. ## Install your application Press the `Deploy` button to deploy the CDK stack to your AWS account. --- --- title: Getting Started with Thunder description: Get started with Thunder, the open source platform-as-a-service for AWS --- # Getting Started with Thunder Thunder is an open-source CDK library and CLI for deploying modern web applications on your own AWS account — no vendor lock-in, no black boxes. ## The Thunder Library The `@thunder-so/thunder` package provides CDK constructs for every deployment pattern: static sites, serverless functions, containers on Fargate, and full-stack SSR frameworks. ### Install ```sh bun add -d @thunder-so/thunder ``` ```sh npm install @thunder-so/thunder --save-dev ``` ```sh pnpm add -D @thunder-so/thunder ``` ### Bootstrap AWS If you haven't bootstrapped your AWS environment yet: ```sh cdk bootstrap aws://YOUR_ACCOUNT_ID/us-east-1 ``` ### Create a Stack Create a stack file (e.g. `stack/dev.ts`) and pick the construct that matches your app: ```ts title="stack/dev.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', outputDir: 'dist', }; new Static(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ```ts title="stack/dev.ts" const config: LambdaProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, codeDir: 'src', handler: 'index.handler', memorySize: 512, timeout: 30, }, }; new Lambda(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ```ts title="stack/dev.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', serviceProps: { port: 3000, cpu: 512, memorySize: 1024, dockerFile: 'Dockerfile', }, }; new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ```ts title="stack/dev.ts" const config: NuxtProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serverProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, keepWarm: true, }, }; new Nuxt(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ### Deploy ```sh npx cdk deploy --app="npx tsx stack/dev.ts" --profile default ``` After deployment you'll receive a CloudFront URL for your application. --- ## Framework Guides Pick your framework for a step-by-step deployment guide. --- ## Thunder Console Prefer a UI over writing CDK stacks? [Thunder Console](https://console.thunder.so) is a hosted dashboard that connects to your AWS account and manages deployments for you.

Connect your AWS account

Thunder creates a scoped IAM role in your account — you stay in control of your infrastructure.

Import from GitHub

Install the Thunder GitHub App and import any repository. Automatic build detection included.

Manage environments

Configure environment variables, custom domains, redirects, and response headers from the UI.

Real-time build logs

Watch builds and deployments stream live. Get notified on success or failure via email or Slack.

--- --- title: Hosting Architecture Patterns description: AWS CDK Construct Libraries that provide simple configuration to deploy modern web apps. --- # Hosting Architecture Patterns Thunder provides production-ready AWS CDK construct libraries for deploying modern web applications. Choose the deployment pattern that best fits your use case. ## Static Deploy client-side SPAs and static site generators on AWS S3 with CloudFront CDN. Supports Astro, Next.js, Vite, Gatsby, React Router, and any static site generator. **Use Static for:** - Client-side rendered applications - Static site generators (Astro, Next.js static export) - Portfolio and documentation sites - Global CDN distribution - High-performance edge caching [Learn more →](/docs/patterns/static) --- ## Lambda Deploy serverless APIs and backend functions on AWS Lambda with modern web frameworks like Express.js, Hono, NestJS, and Fastify. Perfect for microservices, APIs, and event-driven workloads. **Use Lambda for:** - RESTful and GraphQL APIs - Microservices and backend services - Event-driven processing - Lightweight workloads with variable traffic - Pay-per-request pricing model [Learn more →](/docs/patterns/lambda) --- ## Fargate Deploy containerized full-stack applications on AWS Fargate with Application Load Balancer. Supports Next.js, Nuxt, TanStack Start, SvelteKit, Astro, and any containerized application. **Use Fargate for:** - Full-stack applications (Next.js, Nuxt) - Server-rendered applications - Persistent server state - Database connections and background jobs - Automatic scaling and high availability [Learn more →](/docs/patterns/fargate) --- ## Serverless Fullstack Deploy full-stack SSR frameworks using Lambda for server-side rendering, S3 for static assets, and CloudFront as the global CDN. Supports Nuxt, Astro, TanStack Start, SvelteKit, Solid Start, and AnalogJS. **Use Serverless for:** - Server-side rendered applications - Full-stack frameworks (Nuxt, Astro, SvelteKit) - API routes and server functions - Scales to zero with pay-per-request pricing - Global edge distribution [Learn more →](/docs/patterns/serverless) --- --- title: Fargate description: Deploy containerized applications using ECS Fargate and ALB --- # Fargate Deploy containerized web services on [AWS ECS Fargate](https://aws.amazon.com/fargate/) with an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/) for scalable, managed container hosting. No EC2 instances, no cluster management — automatic health checks and rolling updates. ## Supported Frameworks - [Next.js](https://nextjs.org/) - [Nuxt](https://nuxt.com/) - [TanStack Start](https://tanstack.com/start/latest) - [SvelteKit](https://kit.svelte.dev/) - [Astro](https://astro.build/) - [NestJS](https://nestjs.com/) - [Hono](https://hono.dev/) - Any containerized web application ## AWS Resources | Resource | Purpose | | --- | --- | | [ECS Cluster](https://aws.amazon.com/ecs/) | Container orchestration | | [Fargate Task](https://aws.amazon.com/fargate/) | Serverless container runtime | | [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/) | Public HTTP/HTTPS endpoint, health checks | | [VPC](https://aws.amazon.com/vpc/) | Network isolation (created automatically if not provided) | | [ECR Repository](https://aws.amazon.com/ecr/) | Container image registry (via CodePipeline) | | [CloudWatch Logs](https://aws.amazon.com/cloudwatch/) | Container logs, retained for 1 week | | [ACM Certificate](https://aws.amazon.com/certificate-manager/) | SSL for custom domain (optional) | | [Route53](https://aws.amazon.com/route53/) | DNS A record (optional) | ## Container Service Architecture ## Quick Start ### Installation ```bash bun add -D @thunder-so/thunder ``` ### Configuration ```ts title="stack/prod.ts" const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1', }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, desiredCount: 1, cpu: 512, memorySize: 1024, port: 3000, healthCheckPath: '/', }, }; new Fargate( new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config ); ``` ### Deploy ```bash npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` CDK builds your Docker image, pushes it to ECR, and deploys the service: ``` Outputs: myapp-web-prod-stack.LoadBalancerDNS = myapp-web-prod-1234567890.us-east-1.elb.amazonaws.com ``` ## Custom Domain Connect your service to a custom domain. The certificate must be issued in the **same region as your Fargate service**. ```ts title="stack/prod.ts" const config: FargateProps = { // ... domain: 'app.example.com', hostedZoneId: 'Z1D633PJN98FT9', regionalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123', }; ``` When a domain is configured: HTTPS listener is added on port 443, HTTP on port 80 redirects to HTTPS, and a Route53 A record is created. ## Service Configuration ### Environment Variables and Secrets ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, { PUBLIC_API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` Secrets are injected as environment variables at container startup. The task role is automatically granted read access. ### CPU and Memory Fargate uses fixed CPU/memory combinations. ARM64 (`Cdk.aws_ecs.CpuArchitecture.ARM64`) is cheaper and often faster for Node.js workloads. | CPU (units) | vCPU | Valid Memory (MB) | | --- | --- | --- | | 256 | 0.25 | 512, 1024, 2048 | | 512 | 0.5 | 1024–4096 | | 1024 | 1 | 2048–8192 | | 2048 | 2 | 4096–16384 | | 4096 | 4 | 8192–30720 | ## Nixpacks Integration Use [Nixpacks](https://nixpacks.com/) to automatically generate a container image without writing a Dockerfile. When no `dockerFile` is set in `serviceProps`, Thunder runs Nixpacks during `cdk synth` to detect your runtime and generate an optimized build. Nixpacks auto-detects Node.js, Python, Go, Ruby, Rust, PHP, Java, Deno, and more. ```ts title="stack/prod.ts" const config: FargateProps = { // ... serviceProps: { // no dockerFile — Nixpacks takes over port: 3000, cpu: 512, memorySize: 1024, }, buildProps: { runtime_version: '22', // Node.js version installcmd: 'bun install', buildcmd: 'bun run build', startcmd: 'bun start', }, }; ``` For advanced control, add a `nixpacks.toml` to your project root: ```toml title="nixpacks.toml" [phases.install] cmds = ["bun install --frozen-lockfile"] [phases.build] cmds = ["bun run build"] [start] cmd = "node dist/server.js" ``` ## Custom Dockerfile For full control over the build environment, provide your own Dockerfile. The entire `rootDir` is used as the Docker build context. ```dockerfile title="Dockerfile" FROM public.ecr.aws/docker/library/node:22-alpine AS builder WORKDIR /app COPY package.json bun.lockb ./ RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH" RUN bun install --frozen-lockfile COPY . . RUN bun run build FROM public.ecr.aws/docker/library/node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"] ``` ## Estimated Cost | Scenario | Monthly (us-east-1, no free tier) | | --- | --- | | 1 task (0.25 vCPU / 512 MB) | ~$33 | | 2 tasks (0.5 vCPU / 1 GB each) | ~$47 | The ALB (~$22/month) is the dominant fixed cost. See [Fargate pricing](https://aws.amazon.com/fargate/pricing/) and [ALB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/). ## CI/CD Pipeline ### Nixpacks Pipeline ```ts title="stack/prod.ts" const config: FargateProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, buildProps: { installcmd: 'bun install', buildcmd: 'bun run build', startcmd: 'bun start', }, }; ``` ### Custom Dockerfile Pipeline ```ts title="stack/prod.ts" const config: FargateProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, serviceProps: { dockerFile: 'Dockerfile', port: 3000, }, }; ``` ## Stack Outputs | Output | Description | | --- | --- | | `LoadBalancerDNS` | ALB DNS name | | `Route53Domain` | Custom domain URL (only if `domain` is configured) | ## Destroy ```bash npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default ``` --- --- title: Lambda description: Deploy serverless functions using AWS Lambda and API Gateway --- # Lambda Deploy serverless APIs and backend functions on [AWS Lambda](https://aws.amazon.com/lambda/) with [API Gateway](https://aws.amazon.com/api-gateway/) as the public HTTP endpoint. Scales to zero when idle — pay only for what you use. ## Supported Frameworks - [Hono](https://hono.dev/) - [NestJS](https://nestjs.com/) - [Fastify](https://www.fastify.io/) - [Koa](https://koajs.com/) - [AdonisJS](https://adonisjs.com/) - [Feathers](https://feathersjs.com/) - Any framework that exports a Lambda handler ## AWS Resources | Resource | Purpose | | --- | --- | | [Lambda Function](https://aws.amazon.com/lambda/) | Runs your server code | | [API Gateway HTTP API](https://aws.amazon.com/api-gateway/) | Public HTTP endpoint, routes all traffic to Lambda | | [CloudWatch Logs](https://aws.amazon.com/cloudwatch/) | Function logs, retained for 1 month | | [ACM Certificate](https://aws.amazon.com/certificate-manager/) | SSL for custom domain (optional) | | [Route53](https://aws.amazon.com/route53/) | DNS A + AAAA records (optional) | ## Serverless API Architecture ## Quick Start ### Installation ```bash bun add -D @thunder-so/thunder ``` ### Configuration ```ts title="stack/prod.ts" const config: LambdaProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1', }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, codeDir: 'dist', handler: 'index.handler', memorySize: 512, timeout: 10, }, }; new Lambda( new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config ); ``` ### Deploy ```bash bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` CDK outputs the API Gateway URL: ``` Outputs: myapp-api-prod-stack.ApiGatewayUrl = https://abc123.execute-api.us-east-1.amazonaws.com ``` ## Custom Domain Connect your API to a custom domain. The certificate must be issued in the **same region as your Lambda function** — unlike Static, Lambda uses a regional (not global) certificate. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... domain: 'api.example.com', hostedZoneId: 'Z1D633PJN98FT9', regionalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123', }; ``` ## Advanced Configuration ### Performance and Concurrency Fine-tune your function's performance with memory, timeout, and concurrency settings. Memory also controls proportional CPU allocation. ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, // more memory = more CPU timeout: 10, tracing: true, // enable AWS X-Ray keepWarm: true, // ping every 5 min to prevent cold starts reservedConcurrency: 10, // hard cap on simultaneous executions provisionedConcurrency: 2, // pre-warmed instances, eliminates cold starts url: true, // also expose a direct Lambda Function URL }, }; ``` ### Environment Variables and Secrets ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { variables: [ { NODE_ENV: 'production' }, { API_URL: 'https://api.example.com' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], }, }; ``` Thunder automatically grants the Lambda execution role `secretsmanager:GetSecretValue` on each referenced secret. ## Container Deployments Deploy Lambda functions as container images to use custom runtimes, package large dependencies, or exceed the 250 MB zip limit. Container images support up to 10 GB. ### Node.js Container ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 AS builder WORKDIR ${LAMBDA_TASK_ROOT} COPY . . RUN npm ci RUN npm run build FROM public.ecr.aws/lambda/nodejs:22 WORKDIR ${LAMBDA_TASK_ROOT} COPY --from=builder /var/task/dist/* ./ COPY --from=builder /var/task/node_modules ./node_modules CMD ["index.handler"] ``` ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { dockerFile: 'Dockerfile', memorySize: 1792, keepWarm: true, }, }; ``` ### Bun Runtime Container [Bun](https://bun.sh/) is not a managed Lambda runtime, so it runs as a container. The bootstrap binary handles the Lambda runtime protocol and forwards requests to your Bun fetch handler. ```dockerfile title="Dockerfile.bun" # Stage 1: Build the Bun Lambda bootstrap FROM oven/bun:latest AS bun WORKDIR /tmp RUN apt-get update && apt-get install -y curl RUN curl -fsSL https://raw.githubusercontent.com/oven-sh/bun/main/packages/bun-lambda/runtime.ts -o runtime.ts RUN bun install aws4fetch RUN bun build --compile runtime.ts --outfile bootstrap # Stage 2: Build your app FROM oven/bun:latest AS builder WORKDIR /tmp COPY . . RUN bun install RUN bun run build # Stage 3: Runtime image FROM public.ecr.aws/lambda/provided:al2023 WORKDIR ${LAMBDA_TASK_ROOT} COPY --from=bun /tmp/bootstrap ${LAMBDA_RUNTIME_DIR} COPY --from=builder /tmp/dist/ ./ COPY --from=builder /tmp/node_modules ./node_modules CMD ["lambda-bun.fetch"] ``` ```ts title="stack/prod.ts" const config: LambdaProps = { // ... functionProps: { dockerFile: 'Dockerfile.bun', memorySize: 512, keepWarm: true, }, }; ``` ## CI/CD Pipeline ### Zip Deployment Pipeline ```ts title="stack/prod.ts" const config: LambdaProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, buildProps: { runtime: 'nodejs', runtime_version: '22', installcmd: 'bun install', buildcmd: 'bun run build', outputDir: 'dist', }, }; ``` ### Container Deployment Pipeline ```ts title="stack/prod.ts" const config: LambdaProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, functionProps: { dockerFile: 'Dockerfile', }, buildProps: { installcmd: 'bun install', buildcmd: 'bun run build', }, }; ``` ## Stack Outputs | Output | Description | | --- | --- | | `ApiGatewayUrl` | API Gateway endpoint URL | | `LambdaFunction` | Lambda function name | | `LambdaFunctionUrl` | Direct Lambda URL (only if `url: true`) | | `Route53Domain` | Custom domain URL (only if `domain` is configured) | ## Destroy ```bash npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default ``` --- --- title: Serverless Fullstack description: Deploy full-stack SSR frameworks using Lambda, S3, and CloudFront --- # Serverless Fullstack Deploy full-stack server-side rendered applications on AWS using [Lambda](https://aws.amazon.com/lambda/) for SSR, [S3](https://aws.amazon.com/s3/) for static assets, and [CloudFront](https://aws.amazon.com/cloudfront/) as the global CDN. Scales to zero — pay only for actual requests. ## Supported Frameworks - [Nuxt](https://nuxt.com/) - [Astro](https://astro.build/) - [TanStack Start](https://tanstack.com/start/latest) - [SvelteKit](https://kit.svelte.dev/) - [Solid Start](https://start.solidjs.com/) - [AnalogJS](https://analogjs.org/) - Any Nitro or Vite-based SSR framework via the generic `Serverless` construct ## AWS Resources | Resource | Purpose | | --- | --- | | [Lambda Function](https://aws.amazon.com/lambda/) | Runs SSR and API routes | | [S3 Bucket](https://aws.amazon.com/s3/) | Hosts static assets (JS, CSS, images) | | [CloudFront Distribution](https://aws.amazon.com/cloudfront/) | Global CDN with origin routing | | [Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) | Secures S3 — no public bucket access | | [ACM Certificate](https://aws.amazon.com/certificate-manager/) | SSL/TLS for custom domain (optional) | | [Route53](https://aws.amazon.com/route53/) | DNS management (optional) | ## Architecture CloudFront routes requests at the edge using this logic: | Request pattern | Routed to | Notes | | --- | --- | --- | | `*.*` (any file extension) | S3 | JS, CSS, images, fonts — long-term cached | | `/api/*` (or custom `paths`) | Lambda | API routes, mutations | | Everything else `/*` | Lambda | SSR page rendering | ## Quick Start ### Installation ```bash bun add -D @thunder-so/thunder ``` ### Configuration Use the framework-specific construct for best defaults. Each construct knows the expected build output paths for its framework and configures Lambda, S3, and CloudFront accordingly. ```ts title="stack/prod.ts" const config: NuxtProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1', }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serverProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, keepWarm: true, }, }; new Nuxt( new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config ); ``` ### Deploy ```bash bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` CDK outputs the CloudFront URL: ``` Outputs: myapp-web-prod-stack.CloudFrontUrl = https://d1234abcd.cloudfront.net ``` ## Custom Domain A certificate in `us-east-1` is required for CloudFront: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... domain: 'app.example.com', hostedZoneId: 'Z1D633PJN98FT9', certificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123', }; ``` ## Advanced Configuration ### Server Runtime Fine-tune Lambda performance with memory, timeout, concurrency, and warm-up settings. `streaming` enables response streaming for frameworks that support it (Nuxt/Nitro). ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, tracing: true, // enable AWS X-Ray keepWarm: true, // ping every 5 min to prevent cold starts streaming: true, // enable response streaming (Nitro) reservedConcurrency: 10, // hard cap on simultaneous executions provisionedConcurrency: 2, // pre-warmed instances, eliminates cold starts }, }; ``` ### Custom API Paths By default, CloudFront routes `/api/*` to Lambda and everything else either to S3 (static files) or Lambda (SSR). Use `paths` to customize which URL patterns are treated as API routes: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { paths: ['/api/*', '/trpc/*', '/auth/*'], }, }; ``` ### Environment Variables and Secrets ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123' }, ], }, }; ``` ### CloudFront Cache Behavior Control what gets included in the CloudFront cache key for SSR responses: ```ts title="stack/prod.ts" const config: NuxtProps = { // ... allowHeaders: ['Accept-Language'], allowCookies: ['session-*'], allowQueryParams: ['lang', 'theme'], // denyQueryParams: ['utm_source', 'fbclid'], // mutually exclusive with allowQueryParams }; ``` ### Container Mode Zip deployments have a 250 MB unzipped size limit. For apps with large dependencies, switch to container mode. Thunder builds a Docker image, pushes it to [ECR](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html) (up to 10 GB). ```ts title="stack/prod.ts" const config: NuxtProps = { // ... serverProps: { dockerFile: 'Dockerfile', memorySize: 2048, }, }; ``` ```dockerfile title="Dockerfile" FROM public.ecr.aws/lambda/nodejs:22 # Copy all lambda files COPY . ./ CMD ["index.handler"] ``` ### Generic Serverless Construct For any Vite/Nitro-based framework not explicitly supported, use the generic `Serverless` construct and specify the server output paths manually: ```ts title="stack/prod.ts" const config: ServerlessProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', serverProps: { codeDir: '.output/server', handler: 'index.handler', runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, memorySize: 1792, timeout: 10, }, clientProps: { outputDir: '.output/public', }, }; new Serverless(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config); ``` ## CI/CD Pipeline ### AWS CodePipeline Integration ```ts title="stack/prod.ts" const config: NuxtProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, buildProps: { runtime: 'nodejs', runtime_version: '22', installcmd: 'bun install', buildcmd: 'bun run build', }, }; ``` ## Stack Outputs | Output | Description | | --- | --- | | `CloudFrontUrl` | CloudFront distribution URL | | `Route53Domain` | Custom domain URL (only if `domain` is configured) | ## Destroy ```bash npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default ``` --- --- title: Static description: Deploy SPAs and static sites using AWS S3 and CloudFront --- # Static Deploy any client-side Single Page Application (SPA) or static site generator (SSG) on [AWS S3](https://aws.amazon.com/s3/) and [CloudFront](https://aws.amazon.com/cloudfront/). Thunder's `Static` construct ships with HTTPS, HTTP/3, Brotli compression, and security headers out of the box. ## Supported Frameworks - [Astro (SSG mode)](https://astro.build/) - [Next.js (static export)](https://nextjs.org/) - [Vite](https://vite.dev/) — React, Vue, Svelte, Preact, Solid, Lit - [VitePress](https://vitepress.dev/) - [Gatsby (static)](https://www.gatsbyjs.com/) - [React Router (client-side / SSG)](https://reactrouter.com/start/framework/rendering) - Any framework that produces a static output folder ## AWS Resources | Resource | Purpose | | --- | --- | | [S3 Bucket](https://aws.amazon.com/s3/) | Stores your build output. Private, accessed only via CloudFront OAC. | | [CloudFront Distribution](https://aws.amazon.com/cloudfront/) | Global CDN. HTTP/3, TLS 1.2+, Brotli/Gzip compression. | | [Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) | Secures S3 — no public bucket access. | | [ACM Certificate](https://aws.amazon.com/certificate-manager/) | SSL/TLS for custom domain (optional). | | [Route53](https://aws.amazon.com/route53/) | DNS A + AAAA records for IPv4/IPv6 (optional). | ## Hosting Architecture ## Quick Start ### Installation ```bash bun add -D @thunder-so/thunder ``` ### Configuration ```ts title="stack/prod.ts" const config: StaticProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'web', environment: 'prod', rootDir: '.', // monorepo: e.g. 'apps/web' outputDir: 'dist', }; new Static( new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config ); ``` ### Deploy ```bash bun run build npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default ``` CDK outputs the CloudFront URL: ``` Outputs: myapp-web-prod-stack.DistributionUrl = https://d1234abcd.cloudfront.net ``` ## Custom Domain Connect your own domain using [Route53](https://aws.amazon.com/route53/) and an [ACM certificate](https://aws.amazon.com/certificate-manager/). The certificate must be issued in `us-east-1` — CloudFront is a global service and requires it there regardless of your app's region. ```ts title="stack/prod.ts" const config: StaticProps = { // ... domain: 'app.example.com', hostedZoneId: 'Z1D633PJN98FT9', globalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abc-123', }; ``` ## CloudFront Cache Behavior Control what gets included in the cache key and forwarded to the origin: ```ts title="stack/prod.ts" const config: StaticProps = { // ... errorPagePath: '/404.html', // custom 404 page (default: /index.html) allowHeaders: ['Accept-Language'], allowCookies: ['session-*'], allowQueryParams: ['lang', 'theme'], // denyQueryParams: ['utm_source', 'fbclid'], // mutually exclusive with allowQueryParams }; ``` ## Lambda@Edge ### URL Redirects and Rewrites Configure URL redirects and rewrites using [Lambda@Edge](https://aws.amazon.com/lambda/edge/) functions that run at CloudFront edge locations worldwide. Redirects return HTTP 301 responses to the client; rewrites transparently serve different content without changing the URL. ```ts title="stack/prod.ts" const config: StaticProps = { // ... redirects: [ { source: '/home', destination: '/' }, { source: '/blog/:slug', destination: '/posts/:slug' }, ], rewrites: [ { source: '/app/*', destination: '/index.html' }, // SPA fallback ], }; ``` **Pattern syntax:** | Pattern | Matches | | --- | --- | | `/about` | Exact path `/about` | | `/blog/*` | Any path starting with `/blog/` | | `/user/:id` | `/user/123`, `/user/abc`, etc. | | `/a/:x/b/:y` | `/a/foo/b/bar` → `x=foo`, `y=bar` | ### Custom Headers Add custom HTTP response headers per path pattern. Headers run at the `viewer-response` stage and apply to both cached and uncached responses. ```ts title="stack/prod.ts" const config: StaticProps = { // ... headers: [ { path: '/assets/*', name: 'Cache-Control', value: 'public, max-age=31536000, immutable' }, { path: '/**', name: 'X-Frame-Options', value: 'SAMEORIGIN' }, ], }; ``` **Path syntax:** | Path | Matches | | --- | --- | | `/*` | Root-level paths only | | `/**` | All paths including nested | | `/blog/*` | All paths under `/blog/` | ## CI/CD Pipeline ### AWS CodePipeline Integration ### Configuration ```ts title="stack/prod.ts" const config: StaticProps = { // ... accessTokenSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:github-token-XXXXXX', sourceProps: { owner: 'your-username', repo: 'your-repo', branchOrRef: 'main', }, buildProps: { runtime: 'nodejs', runtime_version: '22', installcmd: 'bun install', buildcmd: 'bun run build', }, }; ``` ## Stack Outputs | Output | Description | | --- | --- | | `DistributionId` | CloudFront distribution ID | | `DistributionUrl` | CloudFront URL (`https://xxxx.cloudfront.net`) | | `Route53Domain` | Custom domain URL (only if `domain` is configured) | ## Destroy ```bash npx cdk destroy --app "bunx tsx stack/prod.ts" --profile default ``` --- --- title: Contact Support description: Get help and support for Thunder --- # Contact Support ## Getting Help We're here to help you get the most out of Thunder. Below are several ways to reach us and get support. ## Discord Server **[Join Thunder Discord](https://discord.gg/uNbrp6QYZ6)** — Our primary support hub ## GitHub Issues Found a bug or have a feature request? - **[Thunder GitHub Issues](https://github.com/thunder-so/thunder/issues)** — Report bugs and request features - Check existing issues before creating a new one to avoid duplicates - Provide detailed information including steps to reproduce for bugs - Include relevant pattern version and config details ## Library Support Support for the [@thunder-so/thunder](https://github.com/thunder-so/thunder) CDK library and its patterns: - **Static** — S3 + CloudFront deployment for single-page applications - **Lambda** — Lambda + API Gateway serverless functions - **Fargate** — ECS Fargate containerized web services Report issues or request features on the **[Thunder GitHub Issues](https://github.com/thunder-so/thunder/issues)** tracker. ## Email Support For urgent or sensitive issues, you can reach out via email: - Support: support@thunder.so - Billing: billing@thunder.so ## Response Times - **Community Support:** Typically 24-48 hours - **GitHub Issues:** Active maintenance and triaging - **Email:** 24-72 hours depending on complexity ---