見出し画像

Astro × AWS(s3,CloudFront)でドキュメントルート以外に遷移した際、AccessDeniedになりハマった件

この記事は「株式会社メンバーズ Jamstack研究会主催 Advent Calendar 2023」の22日目の記事です。


はじめに

タイトルの件で少し詰まったので、同じ問題で困っている方への一助となれば幸いです。

状況

最近流行りのSSGであるAstro(今回利用したテンプレートはastro-paper(https://github.com/satnaing/astro-paper))をs3にデプロイした際に、s3で指定したドキュメントルート(index.html)ではページが表示できているのですが、他のページに移動した際にAccessDeniedとなり、期待する動作ができませんでした。

原因…遷移した先のパスが違いました。

そのため別途設定が必要でした。

Astroではbuildした際に「dist」配下に静的ページ表示用のhtmlファイルが出力されるのですが、そちらのディレクトリ構成を把握できていませんでした。

「/dist」以下のディレクトリ構成は以下のようになります。

/dist
├── index.html
└── posts   
    ├── hazimemashite
    │   └── index.html
    └── index.html

今回意図する動作として最上位の階層の「index.html」から「hazimemashite」ディレクトリ内の「index.html」のページへと遷移したいといった感じです。

実際に遷移した際のパスは「任意のドメイン名(またはCloudFrontより払い出されたURL)/posts/hazimemashite/」となっており、「hazimemashite」ディレクトリ内の「index.html」を参照できていませんでした。

実際に「任意のドメイン名(またはCloudFrontのドメイン)/posts/hazimemashite/index.html」

とすると期待するページを表示できました。

しかし遷移するたびに手動で「index.html」を付け足すのはユーザビリティが最悪なので(笑)、、、

上記のAstroのドキュメントに以下の方法がありました。

解決方法…CloudFront Functionsの実装でした。

<astrofunction.js>

function handler(event) {
  var request = event.request;
  var uri = request.uri;

  // Check whether the URI is missing a file name.
  if (uri.endsWith('/')) {
    request.uri += 'index.html';
  } 
  // Check whether the URI is missing a file extension.
  else if (!uri.includes('.')) {
    request.uri += '/index.html';
  }

  return request;
}

※ ドキュメントより引用

コードに関しては説明するまでもないですが、ユーザより「/」または「.」でパスのリクエストがあった際に参照するファイルは「/」または「.」以降の「index.html」のファイルにするよとういうものです。

私はTerraFormでAWSリソースを管理していたため以下のように実装を進めました。

<cloudfront.tf>

# cloudfunctionのリソースを定義し関数のファイルの場所等を指定
resource "aws_cloudfront_function" "redirect" {
  name    = "redirect"
  runtime = "cloudfront-js-1.0"
  comment = "redirect function"
  publish = true
  code    = file("./src/redirect.js")
}

resource "aws_cloudfront_distribution" "cozycozy-blog" {
  origin {
    domain_name = aws_s3_bucket.bucket.bucket_regional_domain_name
    origin_id   = aws_s3_bucket.bucket.id
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.cozycozy-blog.cloudfront_access_identity_path
    }
  }

  enabled = true

  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = aws_s3_bucket.bucket.id

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }
        
        # 定義した関数のリソースをdistributionに設定
    function_association {
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.redirect.arn
    }

    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "whitelist"
      locations        = ["JP"]
    }
  }

  aliases = ["${var.site_domain}"]

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate_validation.acm_cert.certificate_arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1"
  }

  custom_error_response {
    error_code            = "404"
    response_code         = "200"
    response_page_path    = "/index.html"
    error_caching_min_ttl = "300"
  }
}

以上のコードを記述後「terraform apply」を実行し問題なくリソースが更新されていることを確認し、「任意のドメイン名(またはCloudFrontのドメイン)/posts/hazimemashite/」のパスで無事、サブディレクトリのページに遷移できました!

終わりに

Astroの公式ドキュメントは各クラウドサービスやプラットフォームごとにデプロイ方法が丁寧に書かれており、幅広いプラットフォームに対応できる点が優しいなと感じました。

(実際に出来上がったものはスタイルとかレスポンシブがイマイチな気がするので勉強して直したい、、、)

最後まで読んでいただきありがとうございました!

参考

#Jamtack #メンバーズ #Astro #AWS #CloudFront #S3


この記事が気に入ったらサポートをしてみませんか?