Publishing a static website
This post describes the journey how to publish a static website for our employees. This could include documentation, API Specs, ...
Where did we came from
Previously, our documentation hub lived on GitHub Pages, and publishing was straightforward through a standard GitHub Actions workflow (an automated process that publishes updates).

The downside was access: only people with a GitHub license could view it, which excluded many colleagues. We wanted to keep the simple publishing experience while making the site available to every employee without needing a GitHub account.
Architecture at a glance
- S3 bucket (private) as origin; CloudFront Origin Access Control (or Origin Access Identity) limits direct access
- CloudFront distribution with:
- Managed cache policy
- Brotli/Gzip compression
- SSL certificate and Route 53 DNS for the custom domain
- Okta-backed authentication at the edge (Lambda@Edge) - tokens validated, access gated by cookies
This keeps the origin locked down, serves content quickly worldwide, and adds auth without changing how teams build their static sites.
CI/CD: from push to publish
A standardized GitHub Actions workflow:
- Builds the site (any static generator)
For the documentation hub, we use React Router, but any static page would work - Deploys infra changes via CDK
- Syncs build artifacts to S3
- Performs targeted CloudFront invalidations when needed
Teams integrate the construct and provide a small config block. Publishing is then triggered from their repository pipeline.
The StaticWebsite construct
To make this repeatable, we packaged the pattern in a CDK construct. It wires CloudFront, S3, Route 53, ACM, cache/security headers, and Okta authentication.
Example usage (TypeScript):
const website = new StaticWebsite(this, "website", {
name: "website-name",
domainName: "example.statista.dev",
hostedZoneName: "example.statista.dev",
sourceFolder: "/path/to/static/pages",
});
new route53.ARecord(this, "websiteAlias", {
zone: website.hostedZone,
target: website.recordTarget,
recordName: "example.statista.dev",
});
An example CDK setup
The documentation hub is there as a reference if you want to publish a static page. The setup adds a staging/production environment setup which makes it slightly more complex that just a static page without additional environments.
Have a look at the repository and it's sources.
Semi-static pages
The construct allows to extend from it to change CDN configuration or add additional origins to the CDN.
A rough example how to do that would look like this:
class CustomStaticWebsite extends StaticWebsite {
protected getDistributionProps(): cloudfront.DistributionProps {
return {
...super.getDistributionProps(),
additionalBehaviors: {
origin: ... // e.g. an HTTP origin or an ALB
edgeLambdas: [{
// add these to ensure your origin is also protected with Okta
{
functionVersion: this.oktaAuthenticator.lambda.currentVersion,
eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
},
}]
}
}
}
}
The result
- The construct is the product: a paved path for internal static sites
- Teams get consistent security, caching, and auth by default
- Publishing is fast, repeatable, and doesnโt require bespoke infra work
If your team has a static site to publish internally, use the shared workflow, configure the construct, and ship.