npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aws-amplify/hosting

v1.0.1

Published

Deploy static sites, SPAs, and SSR applications (Next.js, Nuxt, Astro, SvelteKit) to AWS using CloudFront + S3 + Lambda.

Downloads

68,512

Readme

@aws-amplify/hosting

Deploy static sites, SPAs, and SSR applications (Next.js, Nuxt, Astro, SvelteKit) to AWS using CloudFront + S3 + Lambda.

⚠️ Important: Separate File Required

Hosting must be defined in amplify/hosting.ts — a separate file from amplify/backend.ts. This is because hosting deploys as an independent CloudFormation stack, allowing you to deploy frontend and backend independently.

amplify/
├── backend.ts      ← defineBackend() — auth, data, storage
├── hosting.ts      ← defineHosting() — CloudFront, S3, Lambda
└── ...

Quick Start

SPA (React, Vue, etc.)

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  framework: 'spa',
  buildCommand: 'npm run build',
});

Note: defineHosting() is designed to be invoked by the Amplify CLI (ampx deploy). It synthesizes a CloudFormation template only when it receives an internal 'amplifySynth' IPC message from the CLI. Running amplify/hosting.ts directly with npx cdk synth or node will not produce a CloudFormation template. If you need to use the hosting construct in a standalone CDK app, use AmplifyHostingConstruct from @aws-amplify/hosting/constructs instead — see Standalone CDK Usage below.

Next.js (SSR)

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  framework: 'nextjs',
  buildCommand: 'npm run build',
});

Note: You do not need to configure output: 'standalone' in next.config.js. The adapter uses @opennextjs/aws internally, which handles the build transformation automatically.

Deploy

# Deploy everything (backend + frontend)
npx ampx deploy --identifier prod

# Deploy only backend (auth, data, storage)
npx ampx deploy --identifier prod --backend

# Deploy only frontend (hosting) — requires backend deployed first
npx ampx deploy --identifier prod --frontend

Common Mistakes

❌ WRONG — Do NOT add hosting to defineBackend:

// amplify/backend.ts — THIS IS WRONG
import { defineBackend } from '@aws-amplify/backend';
import { hosting } from './hosting/resource';

defineBackend({ hosting }); // ❌ Will not work

✅ CORRECT — Use a separate amplify/hosting.ts file:

// amplify/hosting.ts — THIS IS CORRECT
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  framework: 'spa',
  buildCommand: 'npm run build',
});

Hosting is a standalone CDK entry point. The CLI discovers amplify/hosting.ts automatically and deploys it as a separate CloudFormation stack.

Architecture

The hosting package uses a two-layer architecture:

  1. Framework adapters — transform framework-specific build output into a generic DeployManifest
  2. L3 CDK construct — reads the manifest and provisions AWS resources (S3, CloudFront, Lambda, etc.)

The construct is completely framework-agnostic. It never knows whether the manifest came from Next.js, Astro, or a custom adapter.

Implementation note: The framework adapters and the L3 CDK construct are provided by @aws-blocks/hosting. @aws-amplify/hosting re-exports them (the construct under the AmplifyHostingConstruct alias) and adds the Amplify-specific glue — defineHosting(), definePipeline(), and the backend-output integration.

Adapter Pipeline

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────────┐
│  Framework      │     │  Deploy Manifest  │     │  AWS Resources      │
│  Build Output   │ ──► │  (generic JSON)   │ ──► │  (CDK L3 Construct) │
└─────────────────┘     └──────────────────┘     └─────────────────────┘
     adapter()                                      AmplifyHostingConstruct

Built-in Adapters

| Adapter | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Next.js | Uses @opennextjs/aws to process Next.js build output. Supports App Router, Pages Router, ISR, middleware, image optimization, and response streaming. | | Nitro | Nitro-based SSR output (.output/). The engine behind Nuxt and Astro's Node/Lambda targets. | | Nuxt | Nuxt 3 apps (built on the Nitro adapter). | | Astro | Astro SSR apps using the Node/Lambda adapter (built on the Nitro adapter). | | SvelteKit | SvelteKit apps via @sveltejs/adapter-node (SSR) plus static/prerendered output. | | SPA | Static single-page apps (React, Vue, Angular, etc.). All routes serve index.html with client-side routing. |

All of the above are exported from @aws-amplify/hosting/adapters (nextjsAdapter, nitroAdapter, nuxtAdapter, astroAdapter, sveltekitAdapter, spaAdapter) and auto-selected by framework detection; you only need a custom adapter for a framework not listed here.

Infrastructure

  • S3: Private bucket with OAC (Origin Access Control). All public access blocked.
  • CloudFront: HTTP/2 + HTTP/3, TLS 1.2+, gzip/brotli compression, security headers (HSTS, CSP, X-Frame-Options).
  • Lambda (SSR): Native Lambda handlers with response streaming, IAM-authenticated Function URL, least-privilege IAM role.
  • Lambda@Edge (Middleware): Edge functions for request/response transformation.
  • Image Optimization: Separate Lambda with sharp for on-demand image resizing and format conversion.
  • ISR Cache: S3 cache bucket + DynamoDB (tag-based revalidation) + SQS (async revalidation queue). Auto-provisioned when the adapter declares cache config.
  • Atomic deployments: Each deploy creates a new build ID prefix in S3. CloudFront Function rewrites URLs. Zero-downtime cutover.

Features

ISR (Incremental Static Regeneration)

Next.js ISR with revalidateTag() and revalidatePath() is fully supported. The adapter automatically provisions:

  • S3 cache bucket — stores pre-rendered pages
  • DynamoDB table — maps cache tags to paths for revalidateTag()
  • SQS queue — handles async revalidation requests

No configuration required — if your Next.js app uses ISR, the infrastructure is provisioned automatically.

Image Optimization

Next.js <Image> component and next/image optimization work out of the box. A dedicated Lambda function handles:

  • On-demand resizing to configured sizes
  • Format conversion (WebP, AVIF)
  • Caching optimized images in S3

Middleware

Next.js middleware runs as a Lambda@Edge function, supporting:

  • Request/response header manipulation
  • Redirects and rewrites
  • Authentication checks at the edge
  • Geolocation-based routing

Response Streaming

SSR responses are streamed using Lambda response streaming (via Function URLs), reducing Time to First Byte (TTFB) for server-rendered pages.

What Happens During Deploy?

  1. Build — your build command runs (e.g., npm run build)
  2. Adapter — the framework adapter transforms build output into a DeployManifest (Next.js uses OpenNext internally)
  3. CDK Synth — CDK synthesizes a CloudFormation template based on the manifest
  4. CloudFormation Deploy — AWS provisions/updates all resources

Timelines:

  • First deploy: ~15-20 minutes (CloudFront distribution provisioning is the bottleneck)
  • Subsequent deploys: ~5 minutes (asset upload + cache invalidation)
  • Stack deletion: ~20-40 minutes (CloudFront must be fully disabled before removal)

Configuration

| Prop | Type | Default | Description | | ----------------------------- | ---------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | framework | 'nextjs' \| 'nitro' \| 'nuxt' \| 'astro' \| 'sveltekit' \| 'spa' \| 'static' \| string | auto-detected | Framework type. Auto-detected from package.json. | | buildCommand | string | - | Build command to run before deployment. | | buildOutputDir | string | auto-detected | Path to pre-built output (SPA/static). Ignored for Next.js (OpenNext builds). | | environment | Record<string, string \| ByoValue> | - | Env vars injected into SSR compute. Use secret()/config()/byoSecret()/byoConfig() for sensitive or rotatable values — see Secrets & Environment Variables. | | secretStore / configStore | { prefix?, stage? } | per-project default | Override the store path/namespace for secret() / config() values. | | monitoring | { enabled?, snsTopicArn? } | { enabled: true } | CloudWatch alarms (5xx, Lambda errors/throttles, revalidation DLQ). On by default; a few cents/month per alarm. Set { enabled: false } to opt out. | | skewProtection | { enabled, maxAge? } | { enabled: true } | Cookie-based version skew protection during rolling deploys. On by default. | | domain | { domainName, hostedZone } | - | Custom domain with SSL. Requires Route53 hosted zone. | | waf | { enabled, rateLimit? } | - | Enable AWS WAF with managed rules + rate limiting. Adds ~$5/month. | | customAdapter | FrameworkAdapterFn | - | Custom framework adapter for unsupported frameworks. | | compute | { memorySize?, timeout?, logRetention?, reservedConcurrency? } | 1024MB, 30s | Lambda configuration for SSR. | | cdn.priceClass | PriceClass | PRICE_CLASS_100 | CloudFront price class. Use PRICE_CLASS_ALL for global distribution. | | cdn.contentSecurityPolicy | string | restrictive default | Custom CSP header value. | | cdn.geoRestriction | { type, countries } | - | Geo-restriction for CloudFront distribution. | | storage.retainOnDelete | boolean | false | Retain S3 bucket on stack deletion. | | storage.encryption | 'S3_MANAGED' \| 'KMS' | 'S3_MANAGED' | Encryption type for the hosting bucket. | | logging.enabled | boolean | false | Enable CloudFront access logging to S3. | | logging.retentionDays | number | 90 | Days to retain access logs. |

⚠️ Production warning: By default storage.retainOnDelete is false, which means the S3 bucket and all hosted assets are permanently deleted when the CloudFormation stack is destroyed. This is convenient for dev/test but risky for production. For production stacks, set storage: { retainOnDelete: true } to preserve the bucket on stack deletion. In standalone CDK usage, you can also set removalPolicy: RemovalPolicy.RETAIN on the construct's bucket directly.

Custom Domains

Requires a Route53 hosted zone in the same AWS account. ACM certificate is automatically created and validated via DNS.

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  domain: {
    domainName: 'app.example.com',
    hostedZone: 'example.com',
  },
});

First deploy with a custom domain takes longer (2-5 extra minutes with Route53, potentially much longer with external DNS) because ACM certificate validation blocks the CloudFormation stack until the certificate is issued.

External DNS users: You must manually create a CNAME record for ACM validation. CloudFormation will wait up to 72 hours for certificate validation before timing out and rolling back.

Recommendation: Keep your Route53 hosted zone in the same AWS account for the smoothest experience. DNS validation records are created automatically.

Changing Your Custom Domain

Changing domainName after initial deploy causes 5-30 minutes of downtime while the certificate is replaced and CloudFront is reconfigured. For zero-downtime domain migration:

  1. Create a new stack with the new domain
  2. Verify the new site works
  3. Update DNS to point to the new CloudFront distribution
  4. Delete the old stack

WAF (Web Application Firewall)

Enables AWS Managed Rules (Common Rule Set + Known Bad Inputs) and IP-based rate limiting (1000 req/5min/IP default).

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  waf: { enabled: true, rateLimit: 1000 },
});

Cost: ~$5/month base + $1/million requests. Use for production apps with security requirements.

WAF Region Requirement

WAF with CloudFront scope requires deployment in us-east-1. Deploying with WAF enabled in other regions will fail with a clear error message.

Two-Phase Deploy

ampx deploy uses a two-phase deployment model:

  1. Backend phase (ampx deploy --backend): Deploys auth, data, and storage resources. Generates amplify_outputs.json.
  2. Frontend phase (ampx deploy --frontend): Runs your build command (with amplify_outputs.json available), then deploys hosting resources.

Running ampx deploy without flags deploys both phases sequentially.

Flags

| Flag | Behavior | | ------------ | ------------------------------------------------------------ | | (none) | Deploy backend + frontend | | --backend | Deploy backend only (skip hosting) | | --frontend | Deploy frontend only (requires backend to be deployed first) |

Note: --backend and --frontend are mutually exclusive. Specifying both is an error.

Secrets & Environment Variables

The environment prop injects values into your SSR compute. Three kinds are supported:

  • Plain env vars — non-sensitive literals, baked into the CloudFormation template (never put a secret here):

    defineHosting({ environment: { APP_REGION: 'us-east-1' } });
    // read at runtime with process.env.APP_REGION
  • Secrets (secret()) — sensitive values stored in AWS Secrets Manager. Only a store locator is injected; the value never enters the template.

  • Config (config()) — non-sensitive but rotatable values stored in SSM Parameter Store.

// amplify/hosting.ts
import { defineHosting, secret, config } from '@aws-amplify/hosting';

defineHosting({
  environment: {
    STRIPE_SECRET_KEY: secret('STRIPE_SECRET_KEY'), // → AWS Secrets Manager
    FEATURE_FLAGS: config('FEATURE_FLAGS'), // → SSM Parameter Store
  },
});

Set the values out of band with the CLI (both support set / get / list / remove):

npx ampx secret set STRIPE_SECRET_KEY sk_live_xxx
npx ampx config set FEATURE_FLAGS '{"newCheckout":true}'

Read them at runtime from the CDK-free runtime entry (@aws-amplify/hosting/runtime — keep it out of your CDK/build code):

import { getSecret, getConfig } from '@aws-amplify/hosting/runtime';

const key = await getSecret('STRIPE_SECRET_KEY');
const flags = JSON.parse(await getConfig('FEATURE_FLAGS'));

Reference existing resources — point at a secret/parameter you already manage (no CLI set needed) with byoSecret() (name or ARN) and byoConfig() (SSM parameter name):

import { defineHosting, byoSecret, byoConfig } from '@aws-amplify/hosting';

defineHosting({
  environment: {
    DB_PASSWORD: byoSecret(
      'arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-AbCdEf',
    ),
    LD_ENV: byoConfig('/my-org/launchdarkly/prod'),
  },
});

By default, stores are namespaced per project: secrets at /amplify/hosting/<project>/secrets/<KEY>, config at /amplify/hosting/<project>/config/<KEY>. Override the prefix or add a per-stage segment via secretStore / configStore.

CI/CD with definePipeline()

definePipeline() provisions a self-mutating AWS CodePipeline (V2) — one pipeline per branch — that deploys your app on every push, with multi-stage rollouts, approval gates, bake times, and typed per-stage config. Define it in amplify/pipeline.ts and deploy the pipeline once with npx ampx deploy --pipeline (this flag cannot be combined with --identifier).

// amplify/pipeline.ts
import { definePipeline } from '@aws-amplify/hosting/pipeline';
import { Duration } from 'aws-cdk-lib';

export const pipeline = definePipeline({
  source: {
    repo: 'my-org/my-app',
    connectionArn:
      'arn:aws:codeconnections:us-east-1:123456789012:connection/abc-123',
    triggerOnPush: true,
    // triggerFilters: ['src/**', 'amplify/**'], // optional monorepo path filters
  },
  crossAccountKeys: true, // needed for cross-account stage targets
  branches: [
    {
      branch: 'main',
      stages: [
        {
          name: 'staging',
          config: { domain: 'staging.example.com' },
        },
        {
          name: 'prod',
          requireApproval: true,
          bakeTime: Duration.minutes(30),
          env: { account: '222222222222', region: 'us-west-2' },
          config: { domain: 'app.example.com' },
        },
      ],
    },
  ],
});
  • source — repo (owner/repo), a CodeConnections connectionArn (plain string), optional triggerOnPush / triggerFilters.
  • branches[].stages[] — name, optional env ({ account, region }), requireApproval, bakeTime (a Duration), and typed per-stage config.
  • synth — override the build: commands, installCommands, computeType (ComputeType enum), env, primaryOutputDirectory.
  • crossAccountKeys / selfMutation — cross-account artifact keys, and whether the pipeline updates itself on amplify/pipeline.ts changes (default true).

Read the active stage's config inside amplify/hosting.ts with getStageConfig():

import { defineHosting } from '@aws-amplify/hosting';
import { getStageConfig } from '@aws-amplify/hosting/pipeline';

const stage = getStageConfig<{ domain: string }>();
defineHosting({
  domain: stage?.config?.domain
    ? { domainName: stage.config.domain, hostedZone: 'example.com' }
    : undefined,
});

getStageConfig() returns undefined outside a pipeline (e.g. a local ampx deploy), so guard the access.

Limitations

Sandbox

defineHosting is not supported in ampx sandbox. Hosting resources are silently skipped during sandbox development. Use ampx deploy --identifier <name> for full hosting deployment.

Managed ampx pipeline-deploy

defineHosting is not supported with the managed ampx pipeline-deploy (Amplify Hosting branch deployments). For CI/CD, either run ampx deploy from your own pipeline or use the built-in self-managed CodePipeline via definePipeline().

HEAD request Content-Length parity

A HEAD request to an SSR Lambda returns Content-Length: 0 instead of the would-be GET body length. This is a Nitro / Lambda Function URL upstream limitation: the framework's HTTP server doesn't pre-compute the body for a HEAD request, and AWS's response-streaming wrapper passes through whatever the framework sets.

Affected: download managers, CDN HEAD pre-flights, RFC 9110 §9.3.2 strict clients.

Workaround: clients that need an exact length should issue GET with Range: bytes=0-0 (returns the first byte + Content-Range) instead of HEAD.

Multi-zone cross-zone navigation cache

When two Amplify Hosting deployments front the same domain (e.g. shop on / + blog on /blog/* via cross-zone rewrites), navigations between zones currently emit Cache-Control: private, no-cache and re-roundtrip both Lambdas on every navigation.

Workaround: set explicit s-maxage=N headers on the cross-zone routes via your framework's headers() config. Future versions will surface a per-route adapter knob.

Range requests on streaming endpoints

Streaming SSR routes (Nitro nitro.awsLambda.streaming: true, Astro 5, Next.js RSC) silently ignore the Range header — Lambda Function URL streaming buffers the full body before sending. Use a non-streaming endpoint or fall back to a static Range-capable origin (S3 directly, separate file-server Lambda).

Custom Framework Adapters

For frameworks not built in (Remix, etc. — note Nuxt, Astro, and SvelteKit are already built in), provide a custom adapter that returns a DeployManifest:

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';
import type { FrameworkAdapterFn } from '@aws-amplify/hosting/adapters';
import type { DeployManifest } from '@aws-amplify/hosting';
import * as path from 'path';

const myAdapter: FrameworkAdapterFn = (projectDir): DeployManifest => ({
  version: 1,
  compute: {},
  staticAssets: { directory: path.join(projectDir, 'dist') },
  routes: [{ pattern: '/*', target: 'static' }],
});

defineHosting({
  customAdapter: myAdapter,
  buildCommand: 'npm run build',
});

SSR Custom Adapter Example

import type { DeployManifest } from '@aws-amplify/hosting';
import type { FrameworkAdapterFn } from '@aws-amplify/hosting/adapters';
import * as path from 'path';

const astroSSRAdapter: FrameworkAdapterFn = (projectDir): DeployManifest => ({
  version: 1,
  compute: {
    server: {
      type: 'handler',
      bundle: path.join(projectDir, 'dist/server'),
      handler: 'entry.handler',
      placement: 'regional',
      streaming: true,
      runtime: 'nodejs20.x',
      memorySize: 1024,
      timeout: 30,
    },
  },
  staticAssets: {
    directory: path.join(projectDir, 'dist/client'),
    cacheControl: 'public, max-age=31536000, immutable',
  },
  routes: [
    { pattern: '/_astro/*', target: 'static' },
    { pattern: '/favicon.ico', target: 'static' },
    { pattern: '/*', target: 'server' },
  ],
});

Deploy Manifest Schema

The DeployManifest is the contract between framework adapters and the L3 construct. Custom adapters must return this shape:

type DeployManifest = {
  version: 1;

  /** Named compute resources */
  compute: Record<string, ComputeResource>;

  /** Static asset configuration */
  staticAssets: {
    directory: string;
    cacheControl?: string;
  };

  /** Route behaviors — maps URL patterns to compute or static */
  routes: RouteBehavior[];

  /** Cache infrastructure (auto-provisions S3 + DynamoDB + SQS) */
  cache?: CacheConfig;

  /** Image optimization (auto-provisions a separate Lambda) */
  imageOptimization?: ImageConfig;

  /** Middleware (deploys to Lambda@Edge) */
  middleware?: MiddlewareConfig;

  /** Redirects, rewrites, custom headers */
  redirects?: Redirect[];
  rewrites?: Rewrite[];
  headers?: CustomHeader[];

  /** Build ID for atomic deployments (auto-generated if omitted) */
  buildId?: string;
};

type ComputeResource = {
  type: 'handler' | 'http-server' | 'edge';
  bundle: string;
  handler?: string; // for type: 'handler'
  entrypoint?: string; // for type: 'http-server'
  port?: number; // for type: 'http-server'
  placement: 'regional' | 'global';
  streaming?: boolean;
  runtime?: string;
  memorySize?: number;
  timeout?: number;
  environment?: Record<string, string>;
};

type RouteBehavior = {
  pattern: string; // URL pattern (glob only — CloudFront wildcards * and ?)
  target: string; // compute resource name, or 'static'
  fallback?: string; // fallback target on error
};

type CacheConfig = {
  computeResource: string;
  tagRevalidation: boolean; // provisions DynamoDB
  revalidationQueue: boolean; // provisions SQS
};

type ImageConfig = {
  bundle: string;
  handler: string;
  formats: string[];
  sizes: number[];
};

type MiddlewareConfig = {
  bundle: string;
  handler: string;
  matchers: string[];
};

Compute Types

| Type | Description | AWS Resource | | ------------- | ---------------------------------------------- | -------------------------- | | handler | Native Lambda handler (fastest cold start) | Lambda with Function URL | | http-server | HTTP server wrapped with Lambda Web Adapter | Lambda + Web Adapter layer | | edge | Edge function for low-latency global execution | Lambda@Edge |

Lambda Configuration

Customize SSR Lambda settings:

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  framework: 'nextjs',
  compute: {
    memorySize: 1024, // MB (default: 1024)
    timeout: 60, // seconds (default: 30)
    reservedConcurrency: 50, // concurrent executions (default: none)
  },
});

Content Security Policy (CSP)

The default CSP is intentionally restrictive:

default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; frame-ancestors 'self'

unsafe-eval is NOT included in the default policy. This was a deliberate security decision — eval() and new Function() are common XSS vectors. Most modern frameworks (including Next.js) work without unsafe-eval.

However, some libraries (e.g., certain template engines, older chart libraries) require eval() at runtime. If your app needs it, use the cdn.contentSecurityPolicy prop to override:

// amplify/hosting.ts
import { defineHosting } from '@aws-amplify/hosting';

defineHosting({
  cdn: {
    contentSecurityPolicy:
      "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; frame-ancestors 'self'",
  },
});

Standalone CDK Usage (No Amplify CLI)

AmplifyHostingConstruct works as a standard CDK L3 construct in any CDK project — no Amplify CLI, no defineHosting(), no amplify/ directory required.

Sub-path Imports

Use sub-path imports to pull in only what you need:

// The construct itself
import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs';

// Adapters for framework detection and build output transformation
import {
  spaAdapter,
  nextjsAdapter,
  detectFramework,
  getAdapter,
} from '@aws-amplify/hosting/adapters';

// Error type for catch clauses
import { HostingError } from '@aws-amplify/hosting/error';

// Manifest types for custom adapters
import type {
  DeployManifest,
  RouteBehavior,
  ComputeResource,
} from '@aws-amplify/hosting';

Dependency note: The sub-path imports (/constructs, /adapters, /error) do not import any @aws-amplify/* packages at runtime. Only the main entry point (@aws-amplify/hosting) re-exports the defineHosting() factory which depends on @aws-amplify/plugin-types and other Amplify packages. If you only use sub-path imports, your project does not need any Amplify packages installed.

SPA Example (Vanilla CDK)

import { App, Stack } from 'aws-cdk-lib';
import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs';
import type { DeployManifest } from '@aws-amplify/hosting';
import * as path from 'path';

const app = new App();
const stack = new Stack(app, 'MySpaStack', {
  env: { account: '123456789012', region: 'us-east-1' },
});

const manifest: DeployManifest = {
  version: 1,
  compute: {},
  staticAssets: { directory: './dist' },
  routes: [{ pattern: '/*', target: 'static' }],
};

const hosting = new AmplifyHostingConstruct(stack, 'Hosting', {
  manifest,
});

// Access created resources for composition with other constructs
console.log(hosting.distributionUrl); // https://d111111abcdef8.cloudfront.net
console.log(hosting.bucket.bucketName); // auto-generated bucket name

Next.js SSR Example (Vanilla CDK)

import { App, Stack } from 'aws-cdk-lib';
import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs';
import { nextjsAdapter } from '@aws-amplify/hosting/adapters';

const app = new App();
const stack = new Stack(app, 'MyNextjsStack', {
  env: { account: '123456789012', region: 'us-east-1' },
});

// Run the OpenNext adapter to produce a DeployManifest
const manifest = nextjsAdapter({ projectDir: process.cwd() });

new AmplifyHostingConstruct(stack, 'Hosting', {
  manifest,
  compute: {
    memorySize: 1024,
    timeout: 60,
  },
});

The Next.js adapter uses @opennextjs/aws internally to process the build output and produces a manifest with:

  • Native Lambda handlers (no Web Adapter wrapper needed)
  • ISR cache configuration (S3 + DynamoDB + SQS)
  • Image optimization Lambda
  • Middleware edge function (if applicable)

Custom Domain with BYO Certificate

import { Certificate } from 'aws-cdk-lib/aws-certificatemanager';
import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs';

// Certificate MUST be in us-east-1 — CloudFront requirement
const cert = Certificate.fromCertificateArn(
  stack,
  'MyCert',
  'arn:aws:acm:us-east-1:123456789012:certificate/abc-123',
);

new AmplifyHostingConstruct(stack, 'Hosting', {
  manifest,
  domain: {
    domainName: 'app.example.com',
    hostedZone: 'example.com',
    certificate: cert, // BYO cert — skips deprecated DnsValidatedCertificate
  },
});

Important: CloudFront requires ACM certificates to be in us-east-1. If you provide a certificate from another region, the construct throws an InvalidCertificateRegionError at synth time (for concrete ARNs). For cross-stack token ARNs, CloudFront will reject the certificate at deploy time.

Writing a Custom Adapter

The construct is driven by a DeployManifest. Built-in adapters (spaAdapter, nextjsAdapter, nitroAdapter, nuxtAdapter, astroAdapter, sveltekitAdapter) process framework build output and produce this manifest. You can write your own adapter for any framework not covered above (Remix, etc.).

Skeleton adapter for a custom framework:

import * as fs from 'fs';
import * as path from 'path';
import type { DeployManifest, RouteBehavior } from '@aws-amplify/hosting';

/**
 * Custom adapter for Astro (example).
 * Scans Astro's build output and returns a DeployManifest.
 */
export const astroAdapter = (projectDir: string): DeployManifest => {
  const buildOutputDir = path.join(projectDir, 'dist');
  if (!fs.existsSync(buildOutputDir)) {
    throw new Error(`Build output not found at ${buildOutputDir}`);
  }

  const hasServerDir = fs.existsSync(path.join(buildOutputDir, 'server'));
  const routes: RouteBehavior[] = [];

  // Static assets with aggressive caching
  routes.push({
    pattern: '/_astro/*',
    target: 'static',
  });

  if (hasServerDir) {
    // SSR: catch-all goes to compute
    routes.push({ pattern: '/*', target: 'server' });

    return {
      version: 1,
      compute: {
        server: {
          type: 'handler',
          bundle: path.join(buildOutputDir, 'server'),
          handler: 'entry.handler',
          placement: 'regional',
          streaming: true,
          runtime: 'nodejs20.x',
        },
      },
      staticAssets: {
        directory: path.join(buildOutputDir, 'client'),
        cacheControl: 'public, max-age=31536000, immutable',
      },
      routes,
    };
  }

  // Static-only: all routes from S3
  routes.push({ pattern: '/*', target: 'static' });

  return {
    version: 1,
    compute: {},
    staticAssets: { directory: path.join(buildOutputDir, 'client') },
    routes,
  };
};

Using a custom adapter with the construct:

import { AmplifyHostingConstruct } from '@aws-amplify/hosting/constructs';
import { astroAdapter } from './astro-adapter';

const manifest = astroAdapter(process.cwd());

new AmplifyHostingConstruct(stack, 'Hosting', {
  manifest,
});

The key insight is that any framework can be supported by writing an adapter that returns a DeployManifest. The construct handles all AWS resource creation (S3, CloudFront, Lambda, OAC, etc.) based on the manifest.

Troubleshooting

Build fails but error message is unclear

Run your build command locally (npm run build) to see full output. The deploy shows first 1000 + last 1000 characters.

CloudFront returns 403

This should not happen with the OAC bucket policy. If it does, check that the S3 bucket policy includes the CloudFront distribution ARN.

Deploy takes very long

First deploy creates a CloudFront distribution (~15-20 min). Subsequent deploys are faster (~5 min).

ISR pages are not revalidating

Ensure your Next.js app uses the App Router with revalidateTag() or revalidatePath(). Pages Router ISR (revalidate: N in getStaticProps) is also supported via time-based revalidation.

Image optimization returns 500

Check that the image optimization Lambda has sufficient memory (default: 1024MB). Large images may require 1024MB+. Increase via the compute configuration.