0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【S3 + CloudFront】でLIFFアプリを公開する(AWSコンソールから手動)

0
Posted at

はじめに

今回は、LIFFアプリ(Nuxt)を手動でAWS上で公開するまでの手順をまとめてみました。
(現在はインフラ環境構築はコード化されることがほとんどですが、AWSコンソール操作の勉強になるのでおすすめです)
以下、使用したAWSリソースになります。

・Amazon S3(Simple Storage Service)
・Amazon CloudFront

Nuxtを使用したLIFFアプリの構築手順に関しては、こちらをご参照ください。

インフラ構成図

tmp.png

操作の流れ

  1. ローカルで静的ビルド
  2. S3バケット作成
  3. CloudFrontディストリビューション作成
  4. SPAフォールバック設定
  5. 静的ファイルをアップロード
  6. 動作確認
  7. 更新操作
  8. リソース削除操作

1. ローカルで静的ビルド

AWSコンソールで作業をおこなう前に、アップロードする成果物を作ります。
まずはプロジェクトのルートディレクトリに移動し、以下のコマンドを実行して依存関係をインストールします。

zsh
pnpm install --frozen-lockfile

pnpm install: package.json(プロジェクトで使用したいパッケージと、そのバージョンのリスト)の依存関係をインストール(実体をnode_modules/にインストール)します。

--frozen-lockfile: pnpm-lock.yamlを一切更新せず、ロックファイル通りに厳密にインストールするフラグです。ロックファイルとpackage.jsonの整合性が取れない場合はエラーで停止します。

zsh
NUXT_PUBLIC_LIFF_ID=<LIFF_ID> pnpm generate

package.jsonscripts.generateを実行します。

NUXT_PUBLIC_プレフィックス: Nuxtの規約で、これが付いた環境変数はクライアント側(ブラウザ)にも公開されます。pnpm generateビルド時のみ環境変数を読見込むためこのタイミングで渡します。

pnpm generate: 静的サイトコンテンツ(.output/public)を生成します。(サーバー側も生成する場合はpnpm build

package.json
"scripts": {
  "generate": "nuxt generate",

※直接nuxt generateを実行すると、バージョンの異なるNuxtコマンドが実行される可能性があるため非推奨です

プロジェクトルートに以下が生成されます。
※過去バージョンや外部ツールとの連携/互換性のために、dist/ = .output/public のエイリアスも作成されます。

.output
  ├── nitro.json                    ← Nitro のビルドメタ情報
  └── public                        ← 静的ホスティングに置く中身ぜんぶ
      ├── _nuxt                     ← Nuxt が生成したクライアントJS/CSSのバンドルファイル群
      ├── _payload.json             ← SSGペイロード (hydration用データ)
      ├── 200.html                  ← SPA フォールバック
      ├── 404.html                  ← 404 用 HTML
      ├── favicon.ico
      ├── index.html                ← ルート"/"用 HTML
      └── robots.txt

nitro.json: Nuxtの内部サーバエンジンNitroが、このビルド成果物の「自己紹介書」として出力するメタデータです。

public/: 静的ホスティングに丸ごとアップロードする中身です。S3、Cloudflare Pages、Netlify、Nginx などに、このディレクトリの内容をそのまま置けば動くように設計されています。
ホスティング側でpublic/をWebルートにマウントするのが慣例となっており、
https://example.com/ ← public/index.html
https://example.com/_nuxt/* ← public/_nuxt/*
https://example.com/favicon.ico ← public/favicon.ico
のように対応します。

_nuxt/: ブラウザで実際に動作する JavaScript / CSS バンドルが全部入っています。Vite + Rollupapp/配下のソースとnode_modulesから生成した成果物です。

_payload.json: サーバ側で生成された静的HTMLは(動かないHTMLの見た目)に対して、ブラウザでVueが起動して「インタラクティブな状態を埋め直す」作業(=Hydration)で、「埋める内容」が_payload.jsonの中身です。「useState()の初期値」や、複数ページで共通の「ルート定義」などをJSONとして別ファイルに外だしすることでHTMLサイズを減らしたりキャッシュ効率を上げたりします。

200.html: 事前生成されていない動的ルート用に、ホスティングが「ファイル無いときに代わりに返す」HTML です。例:<p>読み込み中...</p> → コンポーネントを描画

404.html: 「ここには本当に何も無いですよ」と返す専用ページです。

favicon.ico: ブラウザのタブ・ブックマーク・履歴に表示されるアイコンです。

index.html: トップページです。

robots.txt: 検索エンジン(Googlebot 等)の クローラーに対する指示書です。

2. S3バケット作成

デプロイ対象のAWSアカウント/リージョンにアクセスします。

Amazon S3 >> バケット >> 汎用バケット画面を開き、「バケットを作成」を押します。

SCR-20260514-oatq.png

基本はデフォルト設定のまま進みます。

① バケット名を設定します。

バケット名は 3~63 文字で、グローバル名前空間内で一意である必要があります。また、バケット名の先頭と末尾は文字または数字である必要があります。有効な文字は a~z、0~9、ピリオド (.)、ハイフン (-) です。

② 「ACL無効」を選択します(デフォルト)
アクセス権を詳細に管理したい場合に使用しますが、現在はIAM Policy と Bucket Policyのみでのアクセス管理が主流となっています。

SCR-20260514-obui.png

① パブリックアクセスをすべてブロックします(デフォルト)
S3のWebsite Endpointはhttpsに対応していません。
セキュリティの観点から、CloudFront経由でのアクセスが主流となっています。

② 「バケットのバージョニング」を無効にします(デフォルト)
有効にすることで、誤削除・上書きからの復旧が可能になります。
ストレージコストが増加するため、今回はオフにします。(本番は有効推奨)

③ タグを設定します。
プロジェクト関連のAWSリソースすべてに共通のタグを付与することで、リソースの管理がしやすくなります。

SCR-20260514-oitb.png

①,② デフォルトの暗号化はデフォルト(S3 マネージドキー)のままにします。
③ 「バケットを作成」を押します。

SCR-20260514-ojyc.png

3. CloudFrontディストリビューション作成

CloudFront >> ディストリビューション画面を開き、「ディストリビューションを作成」を押します。

SCR-20260514-olnv.png

最初に「Choose a plan」画面が表示されますが、何も選択できないため、そのまま「Next」を押します。

① ディストリビューション名を設定します。
② S3と同様のタグをつけます。
③ Nextを押します。

SCR-20260514-omwp.png

① 「Origin type」はAmazon S3を選択します。
② 「Browse S3」を押して、
③ 先ほど作成したS3バケットを選択します。
④ 「Allow private S3 bucket access to CloudFront」を有効にします。
「自動でOAC作成 + S3バケットポリシーも自動更新」してくれます。

以下のようなバケットポリシーが自動でアタッチされます。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXAMPLE"
        }
      }
    }
  ]
}

OACの設定情報は、
CloudFront >> ディストリビューション >> <distribution ID> >> オリジン >> 対象オリジン >> 編集
もしくは、
CloudFront >> セキュリティ >> オリジンアクセスから確認できます。

⑤ Nextを押します。

SCR-20260514-onum.png

① Web Application Firewall (WAF) は今回は無効にします。
追加料金がかかりますが、本番環境では推奨されます。

② Nextを押します。

SCR-20260514-ophm.png

確認画面が表示されるので、Create distributionを押します。

SCR-20260514-opxl.png

OACへのタグ付け

今回の設定により、OACが自動作成されますが、CloudFront Distributionのタグとは別管理となっています。
現時点ではAWSコンソール上からタグを設定することができないため、CLIコマンドもしくはIaCでの設定が推奨されます。
(今回はタグ付けスキップします)

4. SPAフォールバック設定

なぜ必要か

SPA(Vue Router / Nuxt SSG / React Router など)は「存在しないパスでもindex.htmlを返して、ブラウザ側ルーターが画面を切り替える」前提で動作します。
しかし、「Amazon CloudFront + Amazon S3」は、本来「実ファイル配信」の仕組みなので、/aboutに対応する実ファイルが無いと403/404を返します。
そのため CloudFront に「403/404 の時は index.html を返す」というSPA用フォールバック設定を入れています。
「Default root object = index.html」は'/'アクセス時のトップページ解決用です。

Nuxt SSRでは不要

これは、/aboutをNodeサーバーが直接処理できるためです。


先ほど作成したディストリビューションの一般 >> 設定 >> 編集を押します。

SCR-20260514-oskx.png

「Default root object」の項目にindex.htmlを設定し、他はデフォルトのまま変更を保存を押します。

SCR-20260514-otot.png

① 「エラーページ」タブに移動します。
② 「カスタムエラーレスポンスを作成」を押します。

SCR-20260514-ounw.png

① 「HTTP error code」で403を選択します。
② 「Error caching minimum TTL」は0に設定します。
③ 「Customize error response」はYesを選択します。
④ 「Response page path」は/index.htmlに設定します。
⑤ 「HTTP Response code」は200を選択します。
⑥ 「カスタムエラーレスポンスを作成」を押します。

SCR-20260514-ovia.png

同様の内容/手順で404も設定します。

5. 静的ファイルをアップロード

長期キャッシュ用コンテンツ(主にファイル名にハッシュが含まれるもの)と、短期キャッシュ用コンテンツ(それ以外)を分けてアップロードしていきます。
_nuxt/配下とHTML群でメタデータが違うため、必ず2回に分けてアップロードします。

長期キャッシュ用コンテンツのアップロード

先ほど作成したS3バケット画面に移動し、「アップロード」を押します。

SCR-20260514-pfuy.png

フォルダの追加から、.output/public/_nuxtフォルダを選択します。
_nuxt/配下のハッシュ付きファイルは、内容が変わるとファイル名も変わるためキャッシュを長くした方が効率的です。

今回は_nuxt/をまとめてアップロードしてしまいましたが、
_nuxt/builds/latest.jsonのように、_nuxt/でもファイル名にハッシュがない場合はCache-Controlをno-cacheにします。
ファイル数が大きくなればその分、Cache-Controlは効いてくるので、小規模な場合は.output/public/をまとめてno-cacheでアップロードしてしまっていいと思います。

SCR-20260514-pjhf.png

その他の設定はデフォルトのままで、画面を下までスクロールします。
メタデータを追加します。
① 「タイプ」はシステム定義を選択します。
② 「キー」はCache-Controlを選択します。
③ 「値」はpublic, max-age=31536000, immutableを設定します。
public: 共有キャッシュでも保存してよい(=機密情報含まない)
max-age=31536000: 1年間cacheとして扱ってよい
immutable: 再確認不要だとブラウザに伝えて再検証すら省略させる

④ 「アップロード」を押します。

SCR-20260514-plzv.png

短期キャッシュ用コンテンツのアップロード

再度「アップロード」を押します。

フォルダの追加から、.output/public/から_nuxt以外のフォルダとファイルを選択します。
これらのファイルは、頻繁にアクセスされるため、常に最新の状態を配信したいためキャッシュさせないようにします。

SCR-20260514-poir.png

その他の設定はデフォルトのままで、画面を下までスクロールします。
メタデータを追加します。
① 「タイプ」はシステム定義を選択します。
② 「キー」はCache-Controlを選択します。
③ 「値」はno-cache, must-revalidateを設定します。
no-cache: 毎回最新を確認する
※no-store(毎回フルDL)との違いは、キャッシュを保存するかしないか
must-revalidate: オフライン/ネットワーク不調時でも古いcacheを利用させない
④ 「アップロード」を押します。

SCR-20260514-porx.png

以下のように、ソースコードでの.output/public/にあるフォルダ/ファイルが表示されていればOKです。
public/のように表示されてしまう場合は、「アクション >> 移動」で対応します。

SCR-20260514-pquc.png

6. 動作確認

ブラウザでアクセス

先ほど作成したCloudFront画面にある、「Domain name」(~.cloudfront.net) にアクセスします。

SCR-20260514-qxgr.png

LINEでアクセス

LINE Developers Console にアクセスし、LIFFアプリのエンドポイントURLに先ほどの「cloudfront url」を登録します。

SCR-20260514-qzfw.png

https://liff.line.me/<LIFF_ID>にアクセスします。

7. 更新操作

ソースコードを修正した後の更新(再デプロイ)の手順です。

① ローカルでpnpm generateを再実行します。
② S3 コンソール画面 → バケット → 既存のオブジェクトを選択 → 削除を行います。
(または上書きアップロードでも可)
③ 5. 静的ファイルをアップロードの手順に従って最新のコンテンツをアップロードします。
④ CloudFrontのキャッシュを削除(リセット=更新)します。

ディストリビューションの画面へ行き、「キャッシュ削除」タブから「キャッシュ削除を作成」を押します。
 
SCR-20260514-rcvl.png

① キャッシュ削除の対象ファイルを指定します。
② 「キャッシュ削除を作成」を押します。

小規模なアプリケーションの場合、基本は/*で大丈夫です。

SCR-20260514-rmgz.png

しばらく経って再度アプリにアクセスすれば更新された内容が反映されているかと思います。

8. リソース削除操作

今回作成したリソースを削除していきます。
削除する順番が重要です(CloudFront を先に消さないと OAC や S3
が消せません
→参照中エラーになる)

今回の削除対象リソース

・CloudFront Distribution
・Origin Access Control
・S3 Bucket

① ディストリビューションの削除

① CloudFrontのコンソール画面を開き、削除対象のディストリビューションを選択します。
② いきなり削除はできないため、まずは「無効」を押します。

SCR-20260515-kius.png

確認ダイアログが表示されたのち、「ステータス」が無効に変わります。

SCR-20260515-kjtn.png

「最終変更日」がデプロイに変わります。

SCR-20260515-kkof.png

「最終変更日」が日時(=デプロイ完了)に変わるのを待ちます。
SCR-20260515-klfq.png

デプロイが完了すると、「削除」ボタンが押せるようになるので、そのまま削除します。

SCR-20260515-klis.png

② Origin Access Control (OAC) の削除

CloudFront >> セキュリティ >> オリジンアクセス画面へ移動します。
①削除対象を選択し、 ②「削除」ボタンを押します。

SCR-20260515-kmev.png

※OACは無料リソースなので、放置しても課金影響はありません。

③ S3 Bucket の削除

S3 バケットは空(オブジェクトがない状態)でないと削除できません。
まずはバケットを空にします。

① 削除対象のS3 バケットを選択し、
②「空にする」を押します。確認画面に遷移するので、画面の指示に沿って削除を完了します。

SCR-20260515-kots.png

再度バケット一覧画面に戻り、削除対象のS3 バケットを選択し、「削除」ボタンを押します。

SCR-20260515-kpjk.png

今回作成したリソースのテンプレート

IaCジェネレーターという機能を使って、今回AWS上に手動で作成したリソースのテンプレートを作成してみました。
OACにタグを設定しませんでしたが、「関連するリソース」として無事に表示されていました。

静的ファイルをアップロードする際、.output/public/すべてのコンテンツに短期キャッシュ用のCache-Controlを設定しています。

実際に作成されたテンプレート(一部***でマスクしています)

---
Metadata:
  AWSToolsMetrics:
    IaC_Generator: "arn:aws:cloudformation:ap-northeast-1:***:generatedTemplate/f1c032f3-37d4-409d-87bc-97e52c78ae88"
Resources:
  CloudFrontDistribution:
    UpdateReplacePolicy: "Delete"
    Type: "AWS::CloudFront::Distribution"
    DeletionPolicy: "Delete"
    Properties:
      DistributionConfig:
        Logging:
          IncludeCookies: false
          Bucket: ""
          Prefix: ""
        Comment: ""
        DefaultRootObject: "index.html"
        Origins:
        - ConnectionTimeout: 10
          OriginAccessControlId: "***"
          ConnectionAttempts: 3
          OriginCustomHeaders: []
          DomainName: "***.s3.ap-northeast-1.amazonaws.com"
          OriginShield:
            Enabled: false
          S3OriginConfig:
            OriginReadTimeout: 30
            OriginAccessIdentity: ""
          OriginPath: ""
          Id: "***.s3.ap-northeast-1.amazonaws.com-***"
        ViewerCertificate:
          MinimumProtocolVersion: "TLSv1"
          SslSupportMethod: "vip"
          CloudFrontDefaultCertificate: true
        PriceClass: "PriceClass_All"
        DefaultCacheBehavior:
          Compress: true
          FunctionAssociations: []
          LambdaFunctionAssociations: []
          TargetOriginId: "***.s3.ap-northeast-1.amazonaws.com-***"
          ViewerProtocolPolicy: "redirect-to-https"
          GrpcConfig:
            Enabled: false
          TrustedSigners: []
          FieldLevelEncryptionId: ""
          TrustedKeyGroups: []
          AllowedMethods:
          - "HEAD"
          - "GET"
          CachedMethods:
          - "HEAD"
          - "GET"
          SmoothStreaming: false
          CachePolicyId: "***"
        Staging: false
        CustomErrorResponses:
        - ErrorCachingMinTTL: 0
          ResponseCode: 200
          ErrorCode: 403
          ResponsePagePath: "/index.html"
        - ErrorCachingMinTTL: 0
          ResponseCode: 200
          ErrorCode: 404
          ResponsePagePath: "/index.html"
        ContinuousDeploymentPolicyId: ""
        OriginGroups:
          Quantity: 0
          Items: []
        Enabled: true
        Aliases: []
        IPV6Enabled: true
        WebACLId: ""
        HttpVersion: "http2"
        Restrictions:
          GeoRestriction:
            Locations: []
            RestrictionType: "none"
        CacheBehaviors: []
      Tags:
      - Value: "dev"
        Key: "Environment"
      - Value: "test-distribution"
        Key: "Name"
  S3BucketPolicyTest20260515:
    UpdateReplacePolicy: "Delete"
    Type: "AWS::S3::BucketPolicy"
    DeletionPolicy: "Delete"
    Properties:
      Bucket: "test-2026-05-15"
      PolicyDocument:
        Version: "2008-10-17"
        Statement:
        - Condition:
            ArnLike:
              AWS:SourceArn: "arn:aws:cloudfront::***:distribution/EBFWN32LFX0TS"
          Resource: "arn:aws:s3:::test-2026-05-15/*"
          Action: "s3:GetObject"
          Effect: "Allow"
          Principal:
            Service: "cloudfront.amazonaws.com"
          Sid: "AllowCloudFrontServicePrincipal"
        Id: "PolicyForCloudFrontPrivateContent"
  CloudFrontOriginAccessControl:
    UpdateReplacePolicy: "Delete"
    Type: "AWS::CloudFront::OriginAccessControl"
    DeletionPolicy: "Delete"
    Properties:
      OriginAccessControlConfig:
        SigningBehavior: "always"
        Description: "Created by CloudFront"
        SigningProtocol: "sigv4"
        OriginAccessControlOriginType: "s3"
        Name: "oac-test-2026-05-15.s3.ap-northeast-1.amazonaws.com-***"
  S3BucketTest20260515:
    UpdateReplacePolicy: "Delete"
    Type: "AWS::S3::Bucket"
    DeletionPolicy: "Delete"
    Properties:
      AbacStatus: "Disabled"
      PublicAccessBlockConfiguration:
        RestrictPublicBuckets: true
        IgnorePublicAcls: true
        BlockPublicPolicy: true
        BlockPublicAcls: true
      BucketName: "test-2026-05-15"
      OwnershipControls:
        Rules:
        - ObjectOwnership: "BucketOwnerEnforced"
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - BucketKeyEnabled: true
          BlockedEncryptionTypes:
            EncryptionType:
            - "SSE-C"
          ServerSideEncryptionByDefault:
            SSEAlgorithm: "AES256"
      Tags:
      - Value: "dev"
        Key: "Environment"
  CloudFrontCachePolicy:
    UpdateReplacePolicy: "Delete"
    Type: "AWS::CloudFront::CachePolicy"
    DeletionPolicy: "Delete"
    Properties:
      CachePolicyConfig:
        Comment: "Policy with caching enabled. Supports Gzip and Brotli compression."
        MinTTL: 1
        MaxTTL: 3.1536E7
        ParametersInCacheKeyAndForwardedToOrigin:
          QueryStringsConfig:
            QueryStringBehavior: "none"
          EnableAcceptEncodingBrotli: true
          HeadersConfig:
            HeaderBehavior: "none"
          CookiesConfig:
            CookieBehavior: "none"
          EnableAcceptEncodingGzip: true
        DefaultTTL: 86400
        Name: "Managed-CachingOptimized"

※S3オブジェクトメタデータ等は、
・「個々のファイル」に紐づく
・デプロイ/アップロード時に設定する
ためテンプレートには反映されないようです

さいごに

今回はあえてAWSコンソールから手動で設定してみましたが、
・作業が複雑
・設定ミスが起こりやすい
・AWSコンソール上からでは設定できないもの(OACのタグなど)がある
といった運用の難しさを感じました。
次回以降の記事で、コードによる自動化を実装していきたいと思います!

0
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?