
React v19 + React Router v7 の SPA モードを CloudFront + S3 に AWS CDK でデプロイしてみた
こんにちは、製造ビジネステクノロジー部の若槻です。
前回のブログで React v19 + React Router v7 のアプリケーションを create-react-app
で作成してみました。
さて、この React Router v7 のアプリケーションを Amazon CloudFront + S3 にデプロイする場合は、アプリケーションのビルド時に SPA モードを有効にする必要があります。デフォルトでは SSR モードになるためです。
今回は、React v19 + React Router v7 の SPA モードを CloudFront + S3 に AWS CDK でデプロイしてみました。
試してみた
React アプリのビルド
まずは、React アプリケーションを SPA モードを有効にしてビルドします。React アプリケーションのソースコードは、前回のブログで作成したものを使用します。
create-react-app
実行により既定で作成される react-router.config.ts
ファイルで ssr
オプションを false
に設定します。これにより、React Router v7 の SPA モードが有効になります。
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: false, // SPA モードを有効化する
} satisfies Config;
アプリケーションをビルドすると、build/client
ディレクトリにビルド成果物が生成されます。SPA モードなので index.html
ファイルが生成され、サーバーサイドのビルド成果物は生成されません。
$ npm run build -w my-react-router-app
> build
> react-router build
vite v6.3.5 building for production...
✓ 48 modules transformed.
build/client/.vite/manifest.json 2.25 kB │ gzip: 0.48 kB
build/client/assets/logo-dark-pX2395Y0.svg 6.10 kB │ gzip: 2.51 kB
build/client/assets/logo-light-CVbx2LBR.svg 6.13 kB │ gzip: 2.52 kB
build/client/assets/root-D5iR8oEg.css 8.32 kB │ gzip: 2.53 kB
build/client/assets/with-props-Cr-61YSs.js 0.35 kB │ gzip: 0.21 kB
build/client/assets/not-found-BcTa9wDJ.js 0.54 kB │ gzip: 0.35 kB
build/client/assets/home-DDivdm-S.js 0.56 kB │ gzip: 0.35 kB
build/client/assets/root-BWNiOyUc.js 1.13 kB │ gzip: 0.64 kB
build/client/assets/welcome-udj-J-nq.js 3.73 kB │ gzip: 1.72 kB
build/client/assets/chunk-D4RADZKF-Dgj59hC6.js 116.84 kB │ gzip: 39.41 kB
build/client/assets/entry.client-B8EFnelc.js 181.52 kB │ gzip: 57.24 kB
✓ built in 822ms
vite v6.3.5 building SSR bundle for production...
✓ 6 modules transformed.
build/server/.vite/manifest.json 0.23 kB
build/server/assets/server-build-D5iR8oEg.css 8.32 kB
build/server/index.js 7.98 kB
SPA Mode: Generated build/client/index.html
Removing the server build in /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/cdk_sample_app/my-react-router-app/build/server due to ssr:false
✓ built in 82ms
CDK デプロイ
続いて、AWS CDK を使用して CloudFront + S3 にデプロイします。
import * as cdk from "aws-cdk-lib";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as cloudfront_origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3_deployment from "aws-cdk-lib/aws-s3-deployment";
import { Construct } from "constructs";
import * as path from "path";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
// S3 バケット
const bucket = new s3.Bucket(this, "Bucket", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// CloudFront Distribution
const distribution = new cloudfront.Distribution(this, "Distribution", {
defaultBehavior: {
origin:
cloudfront_origins.S3BucketOrigin.withOriginAccessControl(bucket),
},
errorResponses: [
// SPA のページ遷移を正しく処理するための設定
// @see https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-a-react-based-single-page-application-to-amazon-s3-and-cloudfront.html#deploy-a-react-based-single-page-application-to-amazon-s3-and-cloudfront-additional
{
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: "/index.html",
},
],
});
// Distribution のドメイン名を出力
new cdk.CfnOutput(this, "DistributionDomainName", {
value: distribution.distributionDomainName,
});
// S3 バケットへのコンテンツのデプロイ
new s3_deployment.BucketDeployment(this, "BucketDeploy", {
sources: [
s3_deployment.Source.asset(
path.join(__dirname, "../my-react-router-app/build/client")
),
],
destinationBucket: bucket,
distribution,
});
}
}
BucketDeployment
の sources
で、先ほどビルドした React アプリケーションの成果物を指定しています。
上記のコードを CDK でデプロイします。
動作確認
デプロイが完了したら、CloudFront のドメイン名を確認してブラウザでアクセスします。
/
にアクセスすると、React アプリケーションのトップページが表示されます。
トップページの Go to Welcome Page
リンクをクリックすると、/welcome
ページに遷移します。
存在しないパスのページにアクセスすると、あらかじめ作成していた Not Found
ページが表示されます。
React Router の SPA モードでのページ遷移が正しく動作していることを確認できました。
うまく動かないパターン
SPA モードを有効にしなかった場合
次のように、react-router.config.ts
ファイルで ssr
オプションを true
に設定したままの場合、サーバーサイドレンダリングが有効になります。
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true, // SPA モードを無効化する
} satisfies Config;
このままビルドを行うと、サーバーサイドとクライアントサイドの両方のビルド成果物が生成されます。一方で index.html
ファイルは生成されないので、CloudFront + S3 にデプロイしてもうまく動作しない成果物になります。
$ npm run build -w my-react-router-app
> build
> react-router build
vite v6.3.5 building for production...
✓ 48 modules transformed.
build/client/.vite/manifest.json 2.25 kB │ gzip: 0.48 kB
build/client/assets/logo-dark-pX2395Y0.svg 6.10 kB │ gzip: 2.51 kB
build/client/assets/logo-light-CVbx2LBR.svg 6.13 kB │ gzip: 2.52 kB
build/client/assets/root-D5iR8oEg.css 8.32 kB │ gzip: 2.53 kB
build/client/assets/with-props-Cr-61YSs.js 0.35 kB │ gzip: 0.21 kB
build/client/assets/not-found-BcTa9wDJ.js 0.54 kB │ gzip: 0.35 kB
build/client/assets/home-DDivdm-S.js 0.56 kB │ gzip: 0.35 kB
build/client/assets/root-BWNiOyUc.js 1.13 kB │ gzip: 0.64 kB
build/client/assets/welcome-udj-J-nq.js 3.73 kB │ gzip: 1.72 kB
build/client/assets/chunk-D4RADZKF-Dgj59hC6.js 116.84 kB │ gzip: 39.41 kB
build/client/assets/entry.client-B8EFnelc.js 181.52 kB │ gzip: 57.24 kB
✓ built in 714ms
vite v6.3.5 building SSR bundle for production...
✓ 12 modules transformed.
build/server/.vite/manifest.json 0.58 kB
build/server/assets/logo-dark-pX2395Y0.svg 6.10 kB
build/server/assets/logo-light-CVbx2LBR.svg 6.13 kB
build/server/assets/server-build-D5iR8oEg.css 8.32 kB
build/server/index.js 14.63 kB
✓ built in 54ms
403 errorResponses の設定が未追加の場合
AWS CDK で errorResponses
の設定を追加しないまま CloudFront + S3 にデプロイすると、React Router の SPA モードでのページ遷移が正しく動作しません。
$ git diff
diff --git a/lib/main-stack.ts b/lib/main-stack.ts
index e09f5c9..be5da0e 100644
--- a/lib/main-stack.ts
+++ b/lib/main-stack.ts
@@ -22,15 +22,6 @@ export class MainStack extends cdk.Stack {
origin:
cloudfront_origins.S3BucketOrigin.withOriginAccessControl(bucket),
},
- errorResponses: [
- // SPA のページ遷移を正しく処理するための設定
- // @see https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-a-react-based-single-page-application-to-amazon-s3-and-cloudfront.html#deploy-a-react-based-single-page-application-to-amazon-s3-and-cloudfront-additional
- {
- httpStatus: 403,
- responseHttpStatus: 200,
- responsePagePath: "/index.html",
- },
- ],
});
// Distribution のドメイン名を出力
/
に アクセスすると Access Denied
エラーが発生しました。
/index.html
にアクセスすると、デフォルトパスのページにリダイレクトされました。
このように、SPA モードでのページ遷移が正しく動作しないため、errorResponses
の設定を追加する必要があります。
おわりに
React v19 + React Router v7 の SPA モードを CloudFront + S3 に AWS CDK でデプロイする方法を紹介しました。
どなかたの参考になれば幸いです。
以上