![[AWS CDK]S3 CloudFront OAI Route53 構成 で NextJSのSSG配信環境構築](/images/thumbnail/next-logo.png)
[AWS CDK]S3 CloudFront OAI Route53 構成 で NextJSのSSG配信環境構築
2022-05-2315 min read
目次
概要
AWS CDK で S3 CloudFront OAI Route53 を利用し SSGでビルドしたNextJSの配信環境を構築しました。
構成のポイント
CDKでは以下の構成を実装しています。
- NextJSで実装した静的コンテンツ・アプリケーションのホスティング環境としてS3を利用
- コンテンツを配信するCDNとしてCloudFrontを利用
- S3へのアクセス制限は OriginAccessIdentity を利用
- パブリック公開は行わずCloudFrontからのアクセスに制限
- CloudFront Functions でURL正規化
- ACM証明書を
us-east-1
に作成し、CloudFrontのディストリビューションに設定 - Route53のエリリアスレコードとしてドメインを設定する
CDK Stack
実装
以下、Stackのソースです。
bin/cdk.ts
#!/usr/bin/env node
/**
* ex:
* $ cdk deploy --all -c stage=dev
*/
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import {
WebDistributionStack,
WebDistributionStackProps,
} from '../lib/stacks/stack.ts';
const app = new cdk.App();
const env = {
account: 'XXXXXXXXXXXX',
region: 'ap-northeast-1',
};
new WebDistributionStack(app, `WebDistributionStack`, {
env,
bucketName: 's3.example.com',
rootDomain: 'example.com',
fqdn: [`www.example.com`, `example.com`, 'test.example.com'],
});
lib/stack.ts
import {
aws_certificatemanager as acm,
aws_cloudfront as cloudfront,
aws_cloudfront_origins as cloudfrontOrigins,
aws_iam as iam,
aws_route53 as route53,
aws_route53_targets as route53targets,
aws_s3 as s3,
Duration,
RemovalPolicy,
Stack,
StackProps,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export interface WebDistributionStackProps extends StackProps {
readonly bucketName: string;
readonly fqdn: string[];
readonly rootDomain: string;
}
export class WebDistributionStack extends Stack {
constructor(scope: Construct, id: string, props: WebDistributionStackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, 'StaticContentsBucket', {
bucketName: props.bucketName,
removalPolicy: RemovalPolicy.DESTROY,
});
bucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ['s3:GetObject'],
effect: iam.Effect.ALLOW,
resources: [`${bucket.bucketArn}/*`],
principals: [
new iam.CanonicalUserPrincipal(
new cloudfront.OriginAccessIdentity(
this,
'OriginAccessIdentity',
).cloudFrontOriginAccessIdentityS3CanonicalUserId,
),
],
}),
);
const addHeaderFunction = new cloudfront.Function(
this,
'CloudFrontFunctionsRedirectIndex',
{
functionName: `redirect`,
code: cloudfront.FunctionCode.fromFile({
filePath: 'src/cloudfront-function/redirect/index.js',
}),
},
);
const defaultBehavior = {
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin: new cloudfrontOrigins.S3Origin(bucket),
functionAssociations: [
{
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
function: addHeaderFunction,
},
],
};
const errorResponses = [
{
ttl: Duration.seconds(300),
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: '/index.html',
},
{
ttl: Duration.seconds(300),
httpStatus: 404,
responseHttpStatus: 404,
responsePagePath: '/error.html',
},
];
const hostedZone = route53.HostedZone.fromLookup(this, 'HostedZone', {
domainName: props.rootDomain,
});
// TLS証明書を作る
const domains = props.fqdn.slice();
const cert = new acm.DnsValidatedCertificate(this, 'Certificate', {
domainName: domains[0],
subjectAlternativeNames: domains,
hostedZone,
region: 'us-east-1',
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
defaultRootObject: 'index.html',
priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
defaultBehavior,
errorResponses,
domainNames: props.fqdn,
certificate: cert,
});
for (let i = 0; i < props.fqdn.length; i++) {
new route53.ARecord(this, `AliasRecord${i + 1}`, {
zone: hostedZone,
recordName: props.fqdn[i],
target: route53.RecordTarget.fromAlias(
new route53targets.CloudFrontTarget(distribution),
),
});
}
}
}
src/cloudfront-function/redirect/index.js
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.endsWith('/')) {
request.uri += 'index.html';
} else {
if (!uri.includes('.')) {
request.uri += '/index.html';
}
}
return request;
}
参考にしたサイト
Recommends
New Posts
Hot posts!
Date
Tags
Author