FaceTheory CDK And AWS Notes
FaceTheory’s paved AWS deployment path is AppTheory AppTheorySsrSite: CloudFront in front of S3 and a Lambda
Function URL, with FaceTheory rendering through a real FaceApp. The reference stacks in infra/ are synth/test proof
for that shape; they are not evidence that this checkout was deployed to a live AWS account.
Use this page when you want one path from hello world to a CloudFront URL. Use AWS Deployment Shape for the deeper routing, cache, CSP, OAC, and ISR rationale, and Operations Guide for runbook posture.
The end-to-end path
- Scaffold a real FaceTheory handler — create a
FaceApp, adapt it withcreateAppTheoryFaceHandler, and export an AppTheory Lambda Function URL streaming handler. - Build client/static outputs — place immutable assets under
assets/, the Vite manifest at.vite/manifest.json, and optional SSG HTML / hydration sidecars in the S3 upload tree. - Wrap it with
AppTheorySsrSite— let AppTheory own CloudFront, Function URL OAC, origin groups, request-id propagation, and runtime env wiring. - Synthesize and test — run the local stack tests/synth before touching AWS.
- Deploy deliberately — only an operator with the intended AWS account/region credentials should run
cdk deploy. - Curl the CloudFront URL — verify headers and route placement after the deploy. Do not claim live deployment proof unless those commands were actually run against the deployed distribution.
1. Scaffold the handler
The minimal SSR handler shape mirrors infra/apptheory-ssr-site/src/handler.ts:
import { createApp, createLambdaFunctionURLStreamingHandler } from "@theory-cloud/apptheory";
import { createFaceApp } from "@theory-cloud/facetheory";
import { createAppTheoryFaceHandler } from "@theory-cloud/facetheory/apptheory";
const faceApp = createFaceApp({
faces: [
{
route: "/",
mode: "ssr",
render: () => ({
headers: { "cache-control": "private, no-store" },
head: { title: "Hello FaceTheory" },
html: "<main><h1>Hello FaceTheory</h1></main>",
}),
},
],
});
const app = createApp();
const faceHandler = createAppTheoryFaceHandler({ app: faceApp });
app.get("/", faceHandler);
app.get("/{proxy+}", faceHandler);
app.handle("HEAD", "/", faceHandler);
app.handle("HEAD", "/{proxy+}", faceHandler);
export const handler = createLambdaFunctionURLStreamingHandler(app);
Keep consumer data fetching in load(ctx) or request handlers, and escape any caller-controlled HTML. Head tags go
through the FaceTheory head primitive (head, headTags, styleTags); do not emit ad-hoc document head strings in the
component tree.
2. Build assets and optional SSG output
A Vite-style build typically produces:
deploy/
assets/entry.<hash>.js
assets/entry.<hash>.css
.vite/manifest.json
ssg/index.html # optional SSG output
_facetheory/data/index.json # optional strict-CSP SSG hydration data
The FaceTheory reference stacks keep explicit BucketDeployment resources for immutable assets, the Vite manifest, and
SSG HTML. This preserves cache-control differences while AppTheorySsrSite owns the CloudFront distribution. AppTheory
v1.17.1 does not yet expose per-deployment distributionPaths invalidation controls for those uploads; invalidate changed
HTML and hydration JSON paths in your deployment pipeline until that AppTheory gap is closed.
Local reference validation before any deploy:
cd infra/apptheory-ssr-site
npm ci
npm test
cd ../apptheory-ssg-isr-site
npm ci
npm test
3. Wrap with AppTheorySsrSite
SSR-only hello world
import { CfnOutput, Duration, RemovalPolicy, Stack } from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import { AppTheorySsrSite } from "@theory-cloud/apptheory-cdk";
const assetsBucket = new s3.Bucket(this, "AssetsBucket", {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
removalPolicy: RemovalPolicy.RETAIN,
});
const ssrFunction = new NodejsFunction(this, "SsrFunction", {
runtime: lambda.Runtime.NODEJS_20_X,
entry: "src/handler.ts",
handler: "handler",
timeout: Duration.seconds(10),
memorySize: 512,
});
new s3deploy.BucketDeployment(this, "AssetsDeployment", {
sources: [s3deploy.Source.asset("deploy/assets")],
destinationBucket: assetsBucket,
destinationKeyPrefix: "assets",
});
const site = new AppTheorySsrSite(this, "Site", {
ssrFunction,
ssrUrlAuthType: lambda.FunctionUrlAuthType.AWS_IAM,
assetsBucket,
assetsKeyPrefix: "assets",
assetsManifestKey: ".vite/manifest.json",
// Optional production edge controls:
domainName: "app.example.com",
certificateArn: "arn:aws:acm:us-east-1:111122223333:certificate/00000000-0000-0000-0000-000000000000",
webAclId: "arn:aws:wafv2:us-east-1:111122223333:global/webacl/example/00000000-0000-0000-0000-000000000000",
});
new CfnOutput(this, "CloudFrontDomainName", {
value: site.distribution.distributionDomainName,
});
AWS_IAM is the default Function URL posture; AppTheory adds Lambda URL Origin Access Control so viewers reach Lambda
through CloudFront, not through a public unauthenticated Function URL.
SSG + blocking ISR
Use SSG_ISR mode when S3 should be the primary HTML origin and Lambda should handle SSR/ISR fallback on S3 403/404
misses:
import { AppTheoryDynamoTable, AppTheorySsrSite, AppTheorySsrSiteMode } from "@theory-cloud/apptheory-cdk";
const htmlStoreBucket = new s3.Bucket(this, "IsrBucket", {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
removalPolicy: RemovalPolicy.RETAIN,
});
const cacheTable = new AppTheoryDynamoTable(this, "CacheTable", {
partitionKeyName: "pk",
sortKeyName: "sk",
timeToLiveAttribute: "ttl",
removalPolicy: RemovalPolicy.RETAIN,
});
const site = new AppTheorySsrSite(this, "Site", {
mode: AppTheorySsrSiteMode.SSG_ISR,
ssrFunction,
ssrUrlAuthType: lambda.FunctionUrlAuthType.AWS_IAM,
assetsBucket,
assetsKeyPrefix: "assets",
assetsManifestKey: ".vite/manifest.json",
htmlStoreBucket,
htmlStoreKeyPrefix: "isr",
isrMetadataTable: cacheTable.table,
directS3PathPatterns: ["/.vite/*"],
ssrPathPatterns: ["/actions/*"], // form actions, auth callbacks, and other dynamic routes
domainName: "app.example.com",
certificateArn: "arn:aws:acm:us-east-1:111122223333:certificate/00000000-0000-0000-0000-000000000000",
webAclId: "arn:aws:wafv2:us-east-1:111122223333:global/webacl/example/00000000-0000-0000-0000-000000000000",
});
In SSG_ISR mode AppTheory automatically adds:
- an S3-primary / Lambda-fallback origin group for HTML routes;
- generated viewer-request rewrite logic for extensionless SSG paths;
- generated request-id propagation and viewer-response echo;
- direct S3 routing for
/_facetheory/data/*SSG hydration sidecars; - direct Lambda routing for
/_facetheory/ssr-data/*SSR hydration sidecars; APPTHEORY_ASSETS_*,FACETHEORY_ISR_*, and ISR metadata table aliases on the SSR Lambda when those resources are supplied.
4. Synthesize, deploy, curl
Synthesize first:
npm run synth
# or your app's CDK command, for example:
npx cdk synth
npx cdk diff
Deploy only from the intended AWS account/region with reviewed credentials:
npx cdk deploy FaceTheoryHelloWorld --outputs-file cdk-outputs.json
After deployment, read the output and verify through CloudFront:
CLOUDFRONT_DOMAIN=$(jq -r '.FaceTheoryHelloWorld.CloudFrontDomainName' cdk-outputs.json)
curl -I "https://${CLOUDFRONT_DOMAIN}/"
curl -sS "https://${CLOUDFRONT_DOMAIN}/" | grep "Hello FaceTheory"
curl -I "https://${CLOUDFRONT_DOMAIN}/assets/entry.css"
For SSG_ISR stacks, add route-placement checks that match your app:
curl -I "https://${CLOUDFRONT_DOMAIN}/ssg-demo" # expected S3/static cache headers when the SSG key exists
curl -I "https://${CLOUDFRONT_DOMAIN}/isr-demo" # expected FaceTheory ISR headers from Lambda
curl -I "https://${CLOUDFRONT_DOMAIN}/_facetheory/data/index.json" # expected S3 strict-CSP SSG sidecar, if emitted
These commands are operator validation steps, not proof performed by this repository. A docs/stacks PR should report only local synth/tests/docs-build proof unless it actually deployed with authorized AWS credentials.
Reference stacks
infra/apptheory-ssr-site/— SSR-only CloudFront + S3 assets + Lambda Function URL using a real FaceApp handler.infra/apptheory-ssg-isr-site/—AppTheorySsrSiteMode.SSG_ISR, S3-primary HTML origin group, Lambda fallback, and TableTheory-backed ISR metadata wiring.