This is a common pattern, but I would like to share some of the practices that I have arrived at.
Conclusion first
- SPA: Change path with extension to /index.html using Cloudfront Functions
- API: Point /api to API Gateway with Behavior
Background
Until now, when publishing SPA applications such as Vue or Angular with Cloudfront + S3
S3 returns 403 or 404 by making a request to a path that does not exist.
Therefore, we had to create a custom error page on the Cloudfront side and configure it to redirect to /index.html.
Common problem 1 (SPA path issue)
Even if there is a 404 that should be caught, it is redirected without care.
For example, it is hard to notice if there is a CSS or JS upload error.
Common Problem 2 (Cloudfront problem of assigning specific paths)
I want to assign /api paths to API Gateway.
This method seems to be good from a security point of view, as it eliminates the need to configure CORS.
SPA path problem
Using Cloudfront Functions
, I can distribute requests like /users/xxx
to /index.html
in a good way without waiting for error pages.
-
Cloudfront Functions
will be the ones you code to handle events at the edge. - There is a
lambda@edge
that is similar, but you can read more about the differences here for the difference. - It is better to understand that
lambda@edge
has limited functionality and can only perform simple processing.
Also, if you want to try Cloudfront Functions quickly, you can try this.
Cloudfront Functions tutorial
terraform example
Configuring Cloudfront Functions
Define resources like this. (Parts not directly related to AWS Provider settings, etc., are omitted.)
See documentation for a description of the parameters.
- Write the actual process in
code
. Here, it is read from a separate file. - You can make it public by setting
publish = true
.
resource "aws_cloudfront_function" "spa_redirect" {
name = "${var.product_name}-${var.env}-spa-redirect"
runtime = "cloudfront-js-1.0"
comment = "${var.product_name}-${var.env}-spa-redirect"
publish = true
code = file("cloudfront_functions/spa-redirect.js")
}
function part
It is written in a very old fashioned way, but for some reason it needs to be ECMAScript 5.1 compliant
.
I would be very grateful if you could improve on this.
- The presence or absence of extensions is determined by the presence or absence of a dot.
if(request.uri.indexOf(".")) === -1)
var index = '/index.html';
function handler(event) {
var request = event.request;
// if extension not found (access not real file)
if(request.uri.indexOf(".")) === -1) {
request.uri = index;
}
return request;
}
Configuring Cloudfront Functions to adapt to Cloudfront
This is a long list, but all you need to focus on is the function_association
at the bottom.
- You can set it for each behavior!
- The event_type is at the time of request, so we'll write
viewer-request
. - For more information on event_type, please refer to here for more information about event_type.
Cloudfront Functions supports only
viewer-request
andviewer-response
.
resource "aws_cloudfront_distribution" "front" {
origin {
domain_name = aws_s3_bucket.front.bucket_regional_domain_name
origin_id = local.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.origin.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
comment = "${var.product_name}-${var.env}"
default_root_object = "index.html"
aliases = ["test.${data.terraform_remote_state.network.outputs.domain}"]
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]]
cached_methods = ["GET", "HEAD"].
target_origin_id = local.s3_origin_id
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.spa_redirect.arn
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
... Omitted.
}
I want to separate /api
As mentioned above, you can use cloudfront's behavior to separate them.
Many people create API Gateways using the serverless framework or CDK.
It is convenient to get values from Cloudformation.
Point.
- Get the endpoint and stage from the cloudformation in the
domain_name
oforigin
. - Specify
api/*
inpath_pattern
ofbehavior
. - Since
/api
is not needed to access the APIGateway, it is removed by lambda@edge (this could also be replaced by cloudfront functions).
resource "aws_cloudfront_distribution" "front" {
...
origin {
custom_origin_config {
http_port = "80"
https_port = "443"
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
# Extract xxx.execute-api.ap-northeast-1.amazonaws.com and v1 from ServiceEndpoint
domain_name = split("/", data.aws_cloudformation_stack.api_test.outputs["ServiceEndpoint"])[2])
origin_id = var.product_name
}
...
ordered_cache_behavior {
path_pattern = "api/*"
allowed_methods = ["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"].
cached_methods = ["GET", "HEAD"].
target_origin_id = var.product_name
forwarded_values {
headers = ["Authorization"].
query_string = true
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 10
max_ttl = 10
viewer_protocol_policy = "https-only"
lambda_function_association {
event_type = "origin-request"
lambda_arn = aws_lambda_function.redirect_trim_context.qualified_arn
}
}
...
}
That's all, obvious! Some of you may think it is, but we hope it will help you if you have any doubts when configuring your infrastructure.
Reference.
SPA routing process with AWS CloudFront Functions
Top comments (0)