Technote

by sizuhiko

React 共通パッケージで SVG を賢く管理する —— SVGR を活用したコンポーネント化戦略

デザインシステムの共通化などで React コンポーネントをプロジェクト横断で使うことはよくあると思います。 もしくはモノレポ構成で複数のWebアプリがあったとき、その間で利用する共通コンポーネントだったりするかもしれません。

そんなとき SVG 画像をどうやって管理したら 気持ちいいか を考えてみたので、記事にしました。

SVG のままインポートすれば良いのでは?

いや、まぁそうなんです。

Vite React (TypeScript) vite-plugin-svgr で SVG をコンポーネントとして扱う のようにプラグインを使ったり、直接 .svg をインポートできるように tsconfig を設定したりすればできなくはないです。

アプリケーション側のアセット管理であれば、それで良いと思っています。

でも共通コンポーネントパッケージとしたとき、どうやって import するかという話になってきます。 もちろん package.json の import で拡張子が .svg だったとき public フォルダを参照させるとか方法はいくらでもあります。

今回の記事では、共通コンポーネント側で JavaScript ファイルにバンドルしたときに含まれてくれることをゴールにしたいと思います。 なので、アセット参照で良いや、という方はゴメンなさい。

SVG をどうやってコンポーネント化するか

SVGR というツールを使います。これがとても便利です。

モノレポだと以下のようなディレクトリ構成になっているとします。

└── packages/
    └── components/
        ├── icons/    <-- ここにFigmaなどから取得したsvgファイルを保存
        │     └── warning.svg
        └── src/
              ├── Button/    <-- ボタンコンポーネント
              └── Icons/    <-- ここに React コンポーネント化された svg が生成される

package.json の scriptsgen:icons を設定します。

  "scripts": {
    "gen:icons": "npx @svgr/cli -d src/Icons icons"
  },

icons ディレクトリにある svg 画像がすべて React コンポーネントになって src/Icons の下に変換されます。 warning.svgWarning.tsx に変換されます。

import * as React from 'react'
import type { SVGProps } from 'react'
const SvgWarning = (props: SVGProps<SVGSVGElement>) => (
    // 実際のSVGタグが入る
)
export default SvgWarning

あとは src/Icons/index.ts を作って export すれば良いだけです。簡単ですね

export { default as Warning } from './Warning'

アプリケーション側では、共通コンポーネントを npm i してから以下のように利用します。

// 使う側のイメージ
import { Warning } from '@common/components';

const App = () => <Warning color="red" />;

この方法の最大のメリットは、SVG がただの画像ではなく「コード」として扱えるようになることです。 これにより、Tree Shaking の恩恵を受けられたり、型定義によって props の補完が効くようになったりと、開発体験が劇的に向上します。

SVG コンポーネント化すると良いこと

Custom Templates が使える。これですね。CLIのパラメータでもある程度カスタマイズできますが、テンプレートを修正する方が見通しが良いです。

たとえばアプリケーションがテーマを変更できるようになっていたとき、SVGの色を変えたいことがあります。もちろん CSS で変更できる場合もありますが、SVG側でかなり考慮されていないと難しいです。 カスタムテンプレートを使えば fill の値を props にするよう定義することもできるので、そういった需要にも柔軟に対応できます。 これは 共通コンポーネント という共通化では重要になってくるのではないでしょうか?

さらに共通コンポーネント集を作る際、悩ましいのが「各プロジェクトで微妙に異なる命名規則や型定義」です。 Custom Template を使えば、例えば IconProps という独自の型を自動で付与したり、aria-label などのアクセシビリティ属性を必須にするような出力も自由自在です。

単なる変換ツールとしてではなく、「SVG の形をした React コンポーネントを量産する工場」を定義できるのが SVGR の真価だと言えます。

さいごに

今回はSVGR というツールの紹介でした。 実際使ってみてとても便利なので、みなさんも使ってみてくださいね。

モノレポ構成の Next.js を standalone ビルドして Docker で動かす(404エラー対策)

このブログは NPM workspaces を使ったモノレポ構成のリポジトリ内にある Next.js アプリを standalone するときに注意すべき点についてまとめた記事です。

NPM workspaces を使ったモノレポ構成

たとえば以下のようなディレクトリ階層を想定しています。

  • apps/webapp : Next.js の web アプリのワークスペース
  • infra : AWS CDK など IaC コードのワークスペース
  • packages/components : React コンポーネントのワークスペース
  • packages/functions/xxxxx : AWS Lambda 関数ごとのワークスペース

これ以外にも packages の下にいろいろなパッケージを入れるような構成はよくあると思います。

Next.js アプリを standalone ビルドする

next.config.ts が以下のような感じで standalone ビルドする想定です。

import type { NextConfig } from 'next'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))

const nextConfig: NextConfig = {
  reactStrictMode: true,
  transpilePackages: ['@packages/components'],
  images: { unoptimized: true },
  output: 'standalone',
  poweredByHeader: false,
  typescript: {
    ignoreBuildErrors: true,
  },
  experimental: {
    webpackMemoryOptimizations: true,
  },
}

一般的に Dockerfile は Next.js の公式サンプルからもってきます。

コンテナイメージをビルドしてみる

はい、いきなりビルドが通りません。 どうやら、モノレポの上位階層にある node_modules(依存パッケージ)を standalone ディレクトリにコピー対象として含めたいのですが、apps/webapp ディレクトリにないのが原因なようです。

これは next.config.ts が以下のような感じで outputFileTracingRoot を追加します。それによりルートディレクトリの node_modules を参照してくれます(これがないと、Next.js は自分のディレクトリ(apps/webapp)内しかトレースしてくれません)。

const nextConfig: NextConfig = {
  reactStrictMode: true,
  transpilePackages: ['@packages/components'],
  images: { unoptimized: true },
  outputFileTracingRoot: join(__dirname, '../../'),
  output: 'standalone',
  poweredByHeader: false,
  typescript: {
    ignoreBuildErrors: true,
  },
  experimental: {
    webpackMemoryOptimizations: true,
  },
}

これでビルドは成功するようになります。

動かしてみる

なんか全部のページが404エラーになります。

イメージビルド結果に public ディレクトリが入ってないことがまずわかりました。

ビルド方法を疑って調べてみてわかったこと

ここで Gemini とか生成AIを使って解決策を壁打ちしてたんですが、適切な回答はまったく得られませんでした。 で、それっぽいキーワードでネットを検索していると、良い記事を見つけました。モノレポ構成のNext.jsプロジェクトをDocker化する方法

大事なのは、記事の中でビルド結果のファイルをコピーしているとことです。

COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public

で、元の公式サンプルでは以下のようになっていました。

COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/public ./public

差分がわかりやすいですね。つまりモノレポの場合は、モノレポのパスを含めた状態で .next/staticpublic のフォルダをコピーする必要があるということです。

今回は .next/standalone の中身をこうすることになります:

└── apps/
    └── webapp/
        ├── server.js  <-- これを起動する
        ├── public/    <-- ここにコピーが必要
        └── .next/
            └── static/ <-- ここにコピーが必要

これで解決

結果としてできあがった Dockerfile は以下のような感じです

# syntax=docker.io/docker/dockerfile:1

ARG NODE_VERSION=24.13.0-alpine
FROM node:${NODE_VERSION} AS base

FROM base AS deps
RUN apk update && \
  apk upgrade && \
  apk add --no-cache libc6-compat make gcc g++ python3
WORKDIR /app

COPY . .
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi

FROM base AS builder
WORKDIR /app
# モノレポのため、全階層の node_modules が必要。webapp から参照するワークスペースについてはコピーする
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/webapp/node_modules* ./apps/webapp/node_modules/
COPY --from=deps /app/functions/command/node_modules* ./functions/command/node_modules/
COPY --from=deps /app/packages/components/node_modules* ./packages/components/node_modules/
COPY . .

RUN \
  if [ -f yarn.lock ]; then yarn run build; \
  elif [ -f package-lock.json ]; then npm run -w apps/webapp build; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
  else echo "Lockfile not found." && exit 1; \
  fi

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# モノレポのディレクトリパスを含める
COPY --from=builder --chown=nextjs:nodejs /app/apps/webapp/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/webapp/.next/static ./apps/webapp/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/webapp/public ./apps/webapp/public

USER nextjs

EXPOSE 3000

ENV PORT=3000

ENV HOSTNAME="0.0.0.0"
# モノレポのディレクトリパスを含める
CMD ["node",  "apps/webapp/server.js"]

COPY でモノレポの node_modules が必要になる理由

  • シンボリックリンクの解決: NPM workspaces は、packages/components などを node_modules 内にシンボリックリンクとして配置します。Docker の COPY プロセスでこのリンク関係が崩れたり、リンク先のパスが正しく参照できなかったりすることがあります。
  • 依存関係の分離: 一部のビルドツールや Next.js の outputFileTracing は、各ワークスペース直下の node_modules を探しに行く挙動をすることがあります。

server.js にモノレポのディレクトリパスを含める理由

Next.js の standalone モードは、デフォルトでは server.js をカレントディレクトリとして動作しようとしますが、モノレポだと outputFileTracingRoot の影響で apps/webapp/server.js のような深い階層に生成されるので、実行パスにも注意が必要です。

さいごに

意外とよくあるシーンだと思うのですが、意外と情報に辿り着くまでに時間がかかりました。 僕の記事も追加することで、同じ問題で困っている人の解決になればと思います。

AWS 環境の Next.js アプリから Lambda 呼び出しトレースを X-Rayで追跡できるようにする

Next.js アプリを AWS にデプロイしたとき、SSR などでサーバーサイドで実行した処理から Lambda など別の AWS サービスを呼び出すことがあると思います。 アプリケーションのオブザーバビリティを確保するため、AWS 上のアプリケーションでは X-RAY を使ったログ集積をやることが多いと思います。

ここでは Lambda など別のAWSサービスを呼び出したとき、トレースを追跡できるようにする方法について記録していきます。

また本記事での Next.js のバージョンは v15 を想定しています。それ以前のバージョンでは instrumentationexperimental になっていますのでご注意ください。

Next.js で X-RAY に出力できるように設定する

instrumentation.ts で以下のようにします。

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await require('pino')
    await require('next-logger')
    await import('./instrumentation.node')
  }
}

ログが JSON で保持されていると X-RAY と相性が良いので、ロガーを pino にしておくと吉でお勧めです。

重要なのは NEXT_RUNTIMEnodejs の場合に instrumentation.node というファイルをダイナミックインポートすることです。 つまりサーバーサイドの処理のときの計測コードを使うようにします。

instrumentation.node.ts の内容は以下のようになります。

import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
import { AWSXRayIdGenerator } from '@opentelemetry/id-generator-aws-xray'
import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'
import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray'
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  idGenerator: new AWSXRayIdGenerator(),
  textMapPropagator: new AWSXRayPropagator(),
  resource: defaultResource().merge(resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'your-app-service' })),
  instrumentations: [
    new AwsInstrumentation({ suppressInternalInstrumentation: true }),
  ],
  spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
})

sdk.start()

OTEL 系のライブラリを使って計測の設定を行いますが、X-RAY を使うときは @opentelemetry/id-generator-aws-xray@opentelemetry/propagator-aws-xray が重要になります。 あと、aws-sdk を使った呼び出しを計測できるようにラッパーである @opentelemetry/instrumentation-aws-sdk を入れておくと楽になります。

この例では Next.js を App Runner(新規申込が終了してしまいました。残念ですね)にデプロイしているのでリソース定義も defaultResource().merge(resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'your-app-service' })) のようにしています。ここは Next.js をデプロイしたリソースによって書き方が変わります。

あとはいい感じに AWS のサービスを SDK で呼び出すと、Next.js(App Runner)から DynamoDB へのリクエストとか、S3 へのアクセスとか全部 X-RAY 上で線がつながって表示されるので、システムトレースの追跡がしやすくなります。

なぜか Lambda だけ線がつながらない?

さて、DynamoDBとかS3などのマネージドサービスは線が接続されていて問題がなさそうに思うのですが、Lambdaの呼び出し(InvokeFunction)だけは、うまく線が接続されません。Lambdaから外部のAPIサーバーを実行していたり、Aurora DBを実行するトレースなどの情報が AppRunner とは結合されておらず、App Runner から Lambda の呼び出し、Lambda から HTTP(API) 呼び出しみたいな別の線が出来上がっていて、システムトレースの追跡ができません。

調べてみた

そうすると X-Amzn-Trace-Id がセグメントドキュメントの http.request に記録されておらず、Lambdaへ正しく伝播されてないことがわかりました。 X-Amzn-Trace-Id が X-RAY で線を結びつけるための値です。

OpenTelemetry の仕様を調べてみる

公式ドキュメント 手動コンテキスト伝搬 などが参考になります。

propagation.inject(context.active(), output);

上記のようにアクティブコンテキストに追加すれば良さそうです。 さらに AWS-SDK の Lambda クライアントでコマンドが送信されたときにフックするにはミドルウェアを使います。

公式ドキュメント ミドルウェアを使用してリクエストをログに記録する

上記サイトの例は DynamoDB クライアントですが、他のクライアントでも同様に実装できます。

メモ

通常、AwsInstrumentation は多くの AWS サービスをカバーしていますが、Lambda の Invoke においては、ペイロードや呼び出し形式の都合上、自動でのコンテキスト注入が期待通りに動かないケースがあります。 これを解決するために、AWS SDK v3 の Middleware Stack を利用して、リクエストが送出される直前に OpenTelemetry のアクティブコンテキストからトレース情報を抽出し、HTTP ヘッダーに明示的にねじ込む処理を追加します。

やってみた

手動伝搬するコードの追加

まず x-ray.ts を作ります。

import { FinalizeHandlerArguments, HttpRequest, MiddlewareStack } from '@aws-sdk/types'
import { context, propagation } from '@opentelemetry/api'

interface HasMiddlewareStack {
  middlewareStack: MiddlewareStack<any, any>
}

const headerSetter = {
  set: (carrier: Record<string, unknown>, key: string, value: string) => {
    carrier[key] = value
  },
}

export const addXRayInjector = <T extends HasMiddlewareStack>(client: T): void => {
  client.middlewareStack.add(
    (next) => async (args) => {
      const finalizeArgs = args as FinalizeHandlerArguments<object>
      const request = finalizeArgs.request as HttpRequest

      if (request?.headers) {
        const activeContext = context.active()
        const carrier = request.headers as Record<string, string>
        propagation.inject(activeContext, carrier, headerSetter)
      }

      return next(args)
    },
    {
      step: 'serialize',
      name: 'xRayContextInjector',
      override: true,
    }
  )
}

このコードでは AWS-SDK のクライアントから X-RAY のコンテキストにリクエストヘッダーを手動伝搬しています。

トレースコンテキスト $C$ は、スパン ID $S$ とトレース ID $T$、およびフラグ $F$ の集合として定義されます。

$$ C = \{T, S, F\} $$

この $C$ を X-Amzn-Trace-Id ヘッダーとして $C_{AppRunner} \rightarrow C_{Lambda}$ へ手動で注入(Inject)することが、今回のコードのキモです。

Lambda クライアントのインスタンスに設定する

そして LambdaClient インスタンスを生成したら、 addXRayInjector で手動伝搬するためのミドルウェアを設定するようにします。

  const client = new LambdaClient()
  addXRayInjector(client)

最後に

今回は Lambda クライアントについて手動伝搬できるような記事となっていますが、S3やDynamoDBのようにさらにその先がない場合は伝搬を気にしなくて良いのですが、Lambdaと同様に以下のサービスについても手動伝搬は必要になります。

  • StepFunctions
  • SQS

つまりリクエストが伝搬して、さらに別のサービスまでトレースを伝搬するようなサービスを利用するときは、リクエストを受け取ったサービス(今回の例でいうと Next.js)から、さきほどの addXRayInjector を設定して X-Amzn-Trace-Id を伝搬できるようにしましょう。

これで良きオブザーバビリティが実現できますように!

PHPカンファレンス福岡2025に参加した

PHPカンファレンス10周年おめでとうございます。

過去の参加ブログのリンクから:

ほとんど参加していると思っていたら、そうでもなかった。

前日入り

今回も前日入りしましたが、いきなり飛行機が遅延するというトラブルに。

こちらは昨年と同じ書き出しを1行コピペしてきましたが、今年も遅延から始まりました。 お昼ぐらいの来福者は時間どおりだったそうなので、朝だけだったようです。

福岡に到着したら WeWork 天神ブリッククロスへ。永和システムマネジメントでは東京支社がWeWork京橋内になったため、今回のように福岡に前日入りしてもWeWork拠点があれば作業できるのでとても便利。

こちらも昨年からのコピペに見えますが、WeWork の場所が変更となっています。昨年訪問した大名は新しい天神ブリッククロスに引越したようです。こちらは8月にオープンしたということで、まだ3ヶ月ぐらいの新しい拠点で、すごい新築の香りがしていました。

プロジェクトが多忙なこともあり、始発に近い時間に東京を出て、普通の勤務時間に仕事を開始してました。

お昼はWeWork近くにあった能古うどんへ。

夕方からは昨年と同じくWeWorkに知っている人たちが続々来て【非公式】PHPカンファレンス福岡2025・前日Meetup が行われました。昨年は袋詰めに参加していたのですが、今年はこちらに参加(偶然居合わせ?w)ていました。

そのあとは懇親会/2次会にも混ざって、そのあとは Rummy 行って美味しいラムをいただいて、締めにラーメンを食べて3時ちょいに寝ました。なんと気づくとほぼ昨年と同じパターン。

当日

ちゃんと朝起きれて今年は無事会場へ到着。

今回は多くの時間セッションに参加してました。PHP養分がたくさん補充されました。アンカンもなかったので、大量のインプットです。

技術面もエモさもたくさんあって、とても充実した1日となりました。来週からの仕事のモチベにもなります(PHP書いてないけど)。

AIというキーワードが多くあって、やっぱりそういう時流というか避けては通れない何かみたいなのは感じますね。とはいえ、その中で僕ら技術者がやるべきことというか、やることが変わっていく中での模索が続いているというのは、こういうリアルなコミュニティという現場で得られるなぁと強く感じました。

ちなみにお昼は、隣のウェストで。店中が同じようなイベントTシャツを着た人で溢れていて、お店の人も「何?」って後で話したりしてるんでしょうねぇw(そういえばランチの写真はFacebookにしかupしてなかった

そのあとはそのあとは懇親会 → 2次会 → 3次会 の流れで4時過ぎに解散となりました。 初めましての方、お久しぶりの方とたくさん話せました。楽しかった!(そして飲みすぎた!

※3次会の始まりのポストより

後日

今年はアフターハックがなかったので、ホテルの隣のエンジニアカフェにてこのブログを書いています。

2019年の記事の最後は

PHPカンファレンス福岡、来年も参加できると良いなー。

2024年の記事の最後は

そしてまた参加できるように願っています。

で終えていました。

PHPカンファレンス福岡は現体制での開催は今年で最後ということが事前に告知されております。 最後の赤瀬さんの挨拶を思い出して、ウルウルしながらこの1行を書いています。

福岡のコミュニティとのつながりでいうと、PHPMatsuri Fukuoka (ブログ記事: PHP Matsuri2012を終えて 〜 Retrospective of PHP Matsuri 2012 in Fukuoka)からで、最初にもリンクした過去の自分の記事を含めて見ながら懐かしいなぁというのと、少し寂しさも感じていました。

ところが懇親会で @kis さんがコミュニティの継続を熱く願って(少し無茶振り?)、fukuoka.php としての活動は新しい世代で始めるという流れを作ってくれました。

そうPHPカンファレンス福岡の始まりも、あのツイートからでしたからね

もちろん各地のPHPカンファレンスなどで福岡のコミュニティのみなさまと再開できることはあると思いますが、また福岡の地での再会があるかもしれません。

最後に Kenichiro トリオ写真を撮っていただいたので入れておきます。

Step Functions の HTTP タスクでプライベート API にアクセスする

Step Functions には HTTP タスクという便利な統合機能があり、ワークフローで HTTPS API を呼び出すことができるようになっています。 そうすることで Step Functions から単に HTTP の API を呼び出すだけなら Lambda を作らなくても良くなりました。便利ですね。

2024 Re:Invent の発表でプライベートAPIへのアクセスがサポートされた

ただ、これには制限があって、インターネットからアクセス可能なパブリックな API にしかアクセスできませんでした。つまり VPC 経由でアクセスする必要がある API には依然として VPC Lambda が必要だったのです。 しかし2024年の Re:Invent でプライベートAPIへのアクセスがサポートされることになり、益々利用シーンが増えそうです。

参考記事:

CDK では?

さっそく使ってみたいと思ったのですが、当然すぐにCDK ではゴニョゴニョできないのかな?とインターネットを調べてみたところ、さっそくサンプルがあるようです。

参考記事:

L2コンストラクタはまだないので、 CfnResourceGatewayCfnResourceConfiguration を使って作っていくようです。

具体的なCDKのコードはこちらです。

やってみた

とりあえず、あっさりできました。

注意点としてはプライベートAPIといいつつ、EventBridge Connection は HTTPs しか使えないので、 HTTP のプライベートAPIにアクセスしたい!という要望がある場合は工夫が必要です。まぁ VPC の中に LB をおいて、そこにプライベートAPIにアクセスするHTTPsエンドポイントを置いて、HTTPに変換して通すみたいな感じですかね。

めっちゃハマること

プライベートAPIのドメイン名を変更したい

0 -> 1 で作るときは問題ないんですけどね。

たとえば仮のドメイン名とかで構築していて、いざ実際の名前が決まったら置き換えたい、みたいなこと普通にあるじゃないですか。

    resourceConfig.addPropertyOverride('ResourceConfigurationDefinition.DnsResource', {
      DomainName: props.apiDomainName,
      IpAddressType: 'IPV4',
    })

たとえばリソースコンフィグでプライベートAPIのドメイン名を設定しておく必要があるんですが、このプロパティの値を変えるみたいなケースです。

変更するとき cdk diff するとちゃんと差分になって変更できそうに見えます。

Resources
[~] AWS::VpcLattice::ResourceConfiguration Api/ResourceConfig ApiResourceConfig50DED18C
 └─ [~] ResourceConfigurationDefinition
     └─ [~] .DnsResource:
         └─ [~] .DomainName:
             ├─ [-] 変更前のドメイン名
             └─ [+] 変更後のドメイン名
✨  Number of stacks with differences: 1

なんで、そのまま cdk deploy するじゃないですか。

DeployStack | 0/4 | 10:07:54 AM | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack                 | DeployStack User Initiated
DeployStack | 0/4 | 10:08:00 AM | UPDATE_IN_PROGRESS   | AWS::VpcLattice::ResourceConfiguration     | Api/ResourceConfig (ApiResourceConfig50DED18C) 
DeployStack | 1/4 | 10:08:02 AM | UPDATE_COMPLETE      | AWS::VpcLattice::ResourceConfiguration     | Api/ResourceConfig (ApiResourceConfig50DED18C) 
DeployStack | 2/4 | 10:08:04 AM | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack                 | DeployStack 
DeployStack | 3/4 | 10:08:05 AM | UPDATE_COMPLETE      | AWS::CloudFormation::Stack                 | DeployStack 
 ✅  DeployStack
✨  Deployment time: 38s

成功しているように見えるじゃないですか。

でも、AWSのマネコンから確認すると….. 変わってません!!!!(衝撃、マジか

原因は不明です…

プライベートAPIのドメイン名を変更したいときの解決方法

一度 EventBridge Connection をパブリックAPIに戻します。 下のプライベートAPIの設定や、 CfnResourceGatewayCfnResourceConfiguration も全部削除します(ソースコード的にはコメントアウトでも良いけど)。

    const connection = new Connection(this, 'ApiConnection', {
      authorization: Authorization.apiKey('Authorization', props.secret.secretValue),
      connectionName: 'api-connection',
      description: 'プライベートAPIコネクション',
    })
    // ここから
    const cfnConnection: CfnConnection = connection.node.children[0] as unknown as CfnConnection
    cfnConnection.addPropertyOverride('InvocationConnectivityParameters', {
      ResourceParameters: {
        ResourceConfigurationArn: resourceConfig.attrArn,
      },
    })
    // ここまでを削除

して、一度デプロイしてリソースを削除して、再度元に戻してドメイン名の変更を適用して 0 -> 1 の状態で作り直します。

ちなみに、AWSのマネコンからドメイン名を変更しようと思っても GUI からもできないので、そういうものなのかもしれません。(diff と deploy は何をもって成功したのかはわからん…

まとめ

ということで、あまりスマートな解決策ではありませんが、現時点ではこういうやり方しかないのかなと思います。 いい感じに CDK からでも変更できるようになると良いなーと願っています(時間できたら issue 書こうかな