Skip to content

Static Website

This example shows how to mix structured TSX props with Terraform body text when a configuration has deeply nested blocks.

What To Notice

  • Simple Terraform blocks stay as JSX props.
  • Deeply nested CloudFront and IAM policy bodies stay as HCL body text.
  • Refs can still be used inside body text.
  • A data source ref can feed a later resource.

Simple Blocks Stay As Props

Variables, locals, and the S3 bucket are straightforward TSX:

tsx
const bucketRef = useRef();
const oacRef = useRef();
const distributionRef = useRef();
const policyDocRef = useRef();

<Variable
  label="domain_name"
  type="string"
  description="Domain name for the website"
/>

<Variable
  label="default_root_object"
  type="string"
  default="index.html"
/>

<Locals s3_origin_id={tf.raw('"${var.domain_name}-origin-id"')} />

<Resource
  type="aws_s3_bucket"
  label="this"
  ref={bucketRef}
  bucket={tf.var("domain_name")}
/>

Generated HCL:

hcl
variable "domain_name" {
  type        = string
  description = "Domain name for the website"
}

variable "default_root_object" {
  type    = string
  default = "index.html"
}

locals {
  s3_origin_id = "${var.domain_name}-origin-id"
}

resource "aws_s3_bucket" "this" {
  bucket = var.domain_name
}

Nested Blocks Can Keep HCL Shape

The CloudFront distribution has nested blocks where native HCL is easier to scan. Refs and helpers can be interpolated into that body text:

tsx
<Resource
  type="aws_cloudfront_origin_access_control"
  label="this"
  ref={oacRef}
>
  {`
    name                              = ${tf.var("domain_name")}
    origin_access_control_origin_type = "s3"
    signing_behavior                  = "always"
    signing_protocol                  = "sigv4"
  `}
</Resource>

<Resource
  type="aws_cloudfront_distribution"
  label="this"
  ref={distributionRef}
>
  {`
    origin {
      domain_name              = ${bucketRef.bucket_regional_domain_name}
      origin_id                = local.s3_origin_id
      origin_access_control_id = ${oacRef.id}
    }

    enabled             = true
    default_root_object = ${tf.var("default_root_object")}
  `}
</Resource>

Generated HCL:

hcl
resource "aws_cloudfront_origin_access_control" "this" {
  name                              = var.domain_name
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_distribution" "this" {
  origin {
    domain_name              = aws_s3_bucket.this.bucket_regional_domain_name
    origin_id                = local.s3_origin_id
    origin_access_control_id = aws_cloudfront_origin_access_control.this.id
  }

  enabled             = true
  default_root_object = var.default_root_object
}

Data Sources Can Feed Resources

The policy document is a data source with body text. Its generated json attribute is then passed to the bucket policy resource:

tsx
<Data type="aws_iam_policy_document" label="this" ref={policyDocRef}>
  {`
    statement {
      actions   = ["s3:GetObject"]
      resources = ["\${${bucketRef.arn}}/*"]

      condition {
        test     = "StringEquals"
        variable = "AWS:SourceArn"
        values   = [${distributionRef.arn}]
      }

      principals {
        type        = "Service"
        identifiers = ["cloudfront.amazonaws.com"]
      }
    }
  `}
</Data>

<Resource
  type="aws_s3_bucket_policy"
  label="this"
  bucket={bucketRef.id}
  policy={policyDocRef.json}
/>

Generated HCL:

hcl
data "aws_iam_policy_document" "this" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.this.arn}/*"]

    condition {
      test     = "StringEquals"
      variable = "AWS:SourceArn"
      values   = [aws_cloudfront_distribution.this.arn]
    }

    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
  }
}

resource "aws_s3_bucket_policy" "this" {
  bucket = aws_s3_bucket.this.id
  policy = data.aws_iam_policy_document.this.json
}

Takeaway

Use JSX props for the parts that benefit from structure and type help. Use body text when Terraform's native nested syntax is clearer. Refs work in both places, so the generated HCL still contains normal Terraform references.