0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フロントエンドとバックエンドを同一ドメインで構成したとき、CloudFrontのカスタムエラーページ機能に気をつけたい

Last updated at Posted at 2025-01-16

はじめに

フロントエンドはReactなどのフレームワークを使い、バックエンドはAPI化するというアーキテクチャはよく見ると思います。基本的にフロントエンドとバックエンドを別のドメインで構成するように設計されるかもしれませんが、やむを得ず同一のドメインで構成することもあると思います。同一ドメインでCloudFrontを使ったときにカスタムエラーページ設定でバックエンドのエラー応答をCloudFrontで上書きしてちょっとハマったので気をつけたいポイントとして残しておきます。

構成

今回は簡単に以下の構成で説明してみます。
overview.png
検証目的なのでwww.hoge.com部分はCloudFrontがネイティブに提供するFQDNを使います。

検証してみた

こちらのコードで検証していきます。
今回は以下の順番で進めます。

  1. 検証環境のデプロイ
  2. バックエンドAPIの応答を200から404に書き換え
  3. CloudFrontのカスタムエラーページの追加
  4. CloudFront Functionsによるエラーレスポンスのカスタマイズ
  5. Lambda@Edgeによるエラーレスポンスのカスタマイズ
  6. 【おまけ】 CloudFront Functionsによるメンテナンスページの表示

1. 検証環境のデプロイ

対象のリポジトリをCloneした後、フロントエンドアプリのビルドを行います。※この手順ではDocker上でビルドします。

cd cloudfront-custom-error/files/frontend-app
docker run -it --rm -v $PWD:/app -w /app node:alpine3.21 npm run build 

続いてterraformを実行します。※AWSのクレデンシャルの設定は省略します。

cd ../..
terraform init
terraform apply

ルートパスにアクセスすると以下のようなフロントエンドの画面が返されるようにしています。
image.png

Submitボタンを押すとバックエンドに通信が走り、レスポンスBody内のMessageが以下のように応答結果が表示されます。
image.png

2. バックエンドAPIの応答を200から404に書き換え

バックエンドから404を返すように設定した場合、以下のような表示になります。尚、今回のサンプルではAPI GatewayのMockを使って固定で返すようにしています。terraformのvariable backend_status_code を404に設定してterraform applyすることで返却するステータスコードを変更します。
image.png
変更後アクセスすると404応答が返され、レスポンスBody内のMessageが表示されました。

処理イメージ
ここまでの処理イメージは以下の通りです。

3. CloudFrontのカスタムエラーページの追加

よく使われるであろうCloudFrontの機能のカスタムエラーページで404エラーの設定をしてみます。terraformのmain.pyに記載している以下のコメントアウトを解除して再度terraform applyします。※CloudFrontの更新なので少し時間がかかります。

  custom_error_response = [
    {
      error_code         = 404
      response_code      = 404
      response_page_path = "/404.html"
    },
    {
      error_code         = 503
      response_code      = 503
      response_page_path = "/error500.html"
    }
  ]

更新完了後再度Submitボタンを押下すると以下のような画面表示になりました。フロントエンドがAPIからJSONで返されることを期待した作りとしているため、HTMLが返ってきたことによって例外が発生してしまっています。フロントエンドとバックエンドAPIが同一のドメインであるが故にCloudFrontのカスタムページの仕組みで応答が変わってしまってしまいました。
image.png

フロントエンド上に存在しないリソース({cloudfrontのFQDN}/sample)にアクセスしてみると以下のような画面が表示されます。
image.png

処理イメージ
ここまでの処理イメージは以下の通りです。404のレスポンス部分でAPIGWから返ってくるjsonをCloudFrontがhtmlに変えてBrowserに返してしまいます。

4. CloudFront Functionsによるエラーレスポンスのカスタマイズ

APIからの応答はJSONのまま、フロントエンドのエラーはhtmlで返したいので他の方法を考えてみます。
まずはCloudFrontのリクエスト・レスポンスをカスタムする方法として簡単に実装できるCloudFront Functionsを使ってみます。

またこちらをコメントアウトして

  #custom_error_response = [
  #  {
  #    error_code         = 404
  #    response_code      = 404
  #    response_page_path = "/error404.html"
  #  },
  #  {
  #    error_code         = 503
  #    response_code      = 503
  #    response_page_path = "/error500.html"
  #  }
  #]

以下のコメントアウト・インを行い、terraform applyを実行します。
before

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      #"viewer-request" = {
      #  function_arn = aws_cloudfront_function.maintenance_request.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.maintenance_response.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.error_custom_response.arn
      #}
    }

after

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      #"viewer-request" = {
      #  function_arn = aws_cloudfront_function.maintenance_request.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.maintenance_response.arn
      #}
      "viewer-response" = {
        function_arn = aws_cloudfront_function.error_custom_response.arn
      }
    }

viewer-responseに定義したfunctionではレスポンスコードを判定して固定のhtmlを返すスクリプトを組んでいます。これが上手く動けば「ページがみつかりません。」というメッセージが表示されます。設定し、改めて先ほど404エラーになったページへアクセスすると以下のような味気ない画面が返されました。
スクリーンショット 2024-12-17 15.21.22.png
こちらの記事の通り残念ながらCloudFront Functionsはステータスコードが400以上の時発動しないようでレスポンスボディの書き換えはできないので、フロントエンドの404等標準的なエラーページ表示には使えなさそうです。

処理イメージ
今回やろうとしたことは以下の通りです。※フロントエンド部分のみ抜粋。

5. Lambda@Edgeによるエラーレスポンスのカスタマイズ

次にCloudFront Functionsよりも少し設定が面倒な分、凝ったことができるLambda@Edgeを試してみます。
まず、先程の変更を元に戻しつつ、Lambda@Edgeを紐づける設定も併せて行います。変更後、terraform applyを実行してください。
before

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      #"viewer-request" = {
      #  function_arn = aws_cloudfront_function.maintenance_request.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.maintenance_response.arn
      #}
      "viewer-response" = {
        function_arn = aws_cloudfront_function.error_custom_response.arn
      }
    }
    #lambda_function_association = {
    #  "origin-response" = {
    #    lambda_arn = module.lambda_at_edge.lambda_function_qualified_arn
    #  }
    #}

after

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      #"viewer-request" = {
      #  function_arn = aws_cloudfront_function.maintenance_request.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.maintenance_response.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.error_custom_response.arn
      #}
    }
    lambda_function_association = {
      "origin-response" = {
        lambda_arn = module.lambda_at_edge.lambda_function_qualified_arn
      }
    }

反映後、改めて{cloudfrontのFQDN}/sampleにアクセスしてみると想定どおり「ページがみつかりません。」というページが表示されました。(味気ない表示であることはスルーでお願いします。)Lambda@Edgeであればレスポンスをカスタマイズできることが確認できました。これを使えばCloudFrontのカスタムエラーページの代わりにフロントエンドのエラーレスポンスを設定しつつ、バックエンドAPIのエラーを上書きしてしまうのを回避できそうです。

image.png

処理イメージ
今回行ったことは以下の通りです。※フロントエンド部分のみ抜粋。

6. 【おまけ】 CloudFront Functionsによるメンテナンスページの表示

フロントエンドにパスルーティングされるところでリクエスト・レスポンスを書き換えてエラーページを出すように変えてみます。想定されるケースとしてはフロントエンドを閉鎖するメンテナンスページを表示したい時などです。

以下のコメントアウト・インを行います。

before

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      #"viewer-request" = {
      #  function_arn = aws_cloudfront_function.maintenance_request.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.maintenance_response.arn
      #}
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.error_custom_response.arn
      #}
    }
    lambda_function_association = {
      "origin-response" = {
        lambda_arn = module.lambda_at_edge.lambda_function_qualified_arn
      }
    }

after

    function_association = {
      # 通常用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.main.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.viewer_response.arn
      #}
      # エラーページ用
      "viewer-request" = {
        function_arn = aws_cloudfront_function.maintenance_request.arn
      }
      "viewer-response" = {
        function_arn = aws_cloudfront_function.maintenance_response.arn
      }
      #"viewer-response" = {
      #  function_arn = aws_cloudfront_function.error_custom_response.arn
      #}
    }
    #lambda_function_association = {
    #  "origin-response" = {
    #    lambda_arn = module.lambda_at_edge.lambda_function_qualified_arn
    #  }
    #}

リロードすると以下の画面になりました。CloudFront Functionsでviewer-requestとviewer-responseをカスタムすることでメンテナンス画面表示は実現できそうです。
image.png

処理イメージ

一通り検証が完了しました。料金節約の為、最後に今回作った環境をterraform destroyで削除して完了です。

所感

今回のようにフロントエンドをCSR、バックエンドをAPIで構成するケースは多いかと思いますが、エラーページの制御がツラいので、できればCORSなどの設定等面倒はあれど、別のFQDNで構成するのが良さそうだと思いました。

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?