@sitblueprint/website-construct
v0.1.8
Published
A reusable AWS CDK construct for deploying static websites with optional custom domain support.
Maintainers
Readme
Website CDK Construct
A reusable AWS CDK construct to deploy a website via S3 and CloudFont.
Features
- CDN caching via CloudFont
- Deployment via S3
- Dual domain support (e.g., deploy to both
www.example.comandexample.comsimultaneously) - Hardened S3 bucket defaults with bucket-owner-only ACLs and automatic SSE
- Direct access to the underlying S3 bucket and CloudFront distribution for advanced customization
Bucket security hardening
The construct keeps the S3 bucket accessible for static website hosting while enforcing safer defaults:
- Bucket ACLs are blocked and ownership is enforced so only the account owner controls access.
- Objects are encrypted at rest with S3 managed keys.
- CloudFront OAI access is granted explicitly via a bucket policy instead of broad public access.
Installation
npm i @sitblueprint/website-constructUsage
export class MyWebsiteStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const website = new Website(this, "MyWebsite", {
bucketName: "my-static-site-bucket",
indexFile: "index.html",
errorFile: "error.html",
notFoundResponsePagePath: "/404.html",
domainConfig: {
domainName: "example.com",
subdomainName: "www",
certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc123",
includeRootDomain: true, // Optional: also deploy to example.com
},
});
website.bucket; // Underlying S3 bucket
website.distribution; // CloudFront distribution serving the site
}
}Pull Request Preview Environments
Use previewConfig on Website to create a pool of preview buckets (default: 2). CI can claim a slot using LRU, deploy artifacts to the assigned bucket, and release the slot when the pull request closes.
export class WebsiteWithPreviewStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const website = new Website(this, "MyWebsite", {
bucketName: "my-static-site-bucket",
indexFile: "index.html",
errorFile: "error.html",
previewConfig: {
bucketPrefix: "my-frontend-preview",
bucketCount: 2, // default
maxLeaseHours: 24,
},
});
new cdk.CfnOutput(this, "PreviewClaimEndpoint", {
value: website.previewEnvironment!.claimEndpoint,
});
new cdk.CfnOutput(this, "PreviewReleaseEndpoint", {
value: website.previewEnvironment!.releaseEndpoint,
});
}
}API contract for CI
POST /claimwith body{"repo":"owner/repo","prNumber":123,"commitSha":"abc"}POST /heartbeatwith body{"repo":"owner/repo","prNumber":123,"commitSha":"abc"}POST /releasewith body{"repo":"owner/repo","prNumber":123}
claim and heartbeat return:
{
"slotId": 0,
"bucketName": "my-frontend-preview-0",
"previewUrl": "https://....cloudfront.net"
}GitHub Actions shape
name: preview
on:
pull_request:
types: [opened, reopened, synchronize, closed]
jobs:
preview:
runs-on: ubuntu-latest
concurrency: preview-${{ github.event.pull_request.number }}
steps:
- uses: actions/checkout@v4
- if: github.event.action != 'closed'
run: npm ci && npm run build
- name: Claim or release slot
env:
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
SHA: ${{ github.sha }}
CLAIM_URL: ${{ secrets.PREVIEW_CLAIM_ENDPOINT }}
RELEASE_URL: ${{ secrets.PREVIEW_RELEASE_ENDPOINT }}
run: |
if [ "${{ github.event.action }}" = "closed" ]; then
curl -sS -X POST "$RELEASE_URL" -H "content-type: application/json" -d "{\"repo\":\"$REPO\",\"prNumber\":$PR}"
exit 0
fi
RESPONSE=$(curl -sS -X POST "$CLAIM_URL" -H "content-type: application/json" -d "{\"repo\":\"$REPO\",\"prNumber\":$PR,\"commitSha\":\"$SHA\"}")
echo "$RESPONSE" > preview-slot.json
BUCKET=$(jq -r '.bucketName' preview-slot.json)
URL=$(jq -r '.previewUrl' preview-slot.json)
aws s3 sync ./dist "s3://$BUCKET" --delete
echo "Preview URL: $URL"Development
- Build:
npm run build - Test:
npm run test
License
MIT
