【開発ブログ】Procom バナー機能の実装とハマりポイント全まとめ

― Firestore保存失敗、useState同期ミス、名前解決エラーまで ―

こんにちは。Procom開発チームです。
今回は新たに実装した**「バナーリンク機能」**について、実装の流れとその中で遭遇した複数のトラブル・エラー、そしてそれをどう解決していったのかを詳しくご紹介します。

Procomでは各ユーザーが最大3つのバナー画像をアップロードし、それぞれに外部リンクを設定できるようにしました。たとえば「公式ショップ」や「作品ポートフォリオ」などを掲載する目的です。

しかし、機能自体はシンプルであるにも関わらず、以下のような複雑なエラー群に悩まされました:


✅ 今回の構成

  • フロントエンド:Next.js App Router / TypeScript / Tailwind CSS
  • サーバーサイドAPI:/app/api/banner-links/route.ts
  • データベース:Firebase Firestore
  • 画像保存:Firebase Storage

🔥 発生したエラー一覧

以下が、実際に発生したエラーです:

  1. Cannot find name 'showBanners'.ts(2304)
  2. Module '"./SnsVisibilityToggle"' has no exported member 'SnsVisibilityToggle'.
  3. Firestoreに保存されない(POSTリクエストは成功、だが保存内容が反映されない)
  4. バナーが非表示にならない
  5. ×ボタン(バナー削除)を追加したいがうまく動作しない
  6. 保存ボタンのデザイン統一が崩れる

以下、これらのエラーをそれぞれ詳細に解説していきます。


1. Cannot find name 'showBanners'.ts(2304)

エラー内容:
showBanners」という状態変数を使っているのに、型チェックで未定義の変数として警告が出る。

原因:
useStateshowBannersを宣言するコードが定義されていなかった、または順序が前後していた。

解決策:

const [showBanners, setShowBanners] = useState(true);

この一文を冒頭でしっかり定義することで解決しました。


2. Module '"./SnsVisibilityToggle"' has no exported member 'SnsVisibilityToggle'.

エラー内容:
SnsVisibilityToggleというコンポーネントを「名前付きimport」していたが、エクスポートの形式が異なっていた。

原因:
ファイル側が export default SnsVisibilityToggle だったのに、呼び出し側が import { SnsVisibilityToggle } from ... としていた。

解決策:
次のように書き換える:

import SnsVisibilityToggle from './SnsVisibilityToggle';

このような小さなミスが、意外と時間を取られます。


3. Firestoreに保存されない(POSTリクエストは成功、だが保存内容が反映されない)

エラー内容:
POSTリクエストの送信は成功(status: 200)しているのに、Firestore側にデータが保存されない。

原因:
Firestoreの set() メソッドの対象である profile フィールドの構造が不正だった、またはmerge: trueしていなかったことで、上書きが発生していたケースも。

解決策:
以下のように、既存の profilesettings をマージするように変更:

const doc = await profileRef.get();
const existing = doc.data() || {};
const existingProfile = existing.profile || {};
const existingSettings = existingProfile.settings || {};

await profileRef.set(
{
profile: {
...existingProfile,
bannerLinks,
settings: {
...existingSettings,
showBanners: showBanners ?? true,
},
},
},
{ merge: true }
);

merge: trueを忘れると他のフィールドが消えてしまう点も注意です。


4. バナーが非表示にならない

現象:
「表示・非表示」のトグルをオフにして保存しても、ページ読み込み時に反映されない。

原因:
GET /api/banner-links?uid=xxxsettings.showBanners を返していなかった。

解決策:
APIのレスポンスに settings.showBanners を含めるように修正。

const data = doc.data();
const banners = data?.profile?.bannerLinks ?? [];
const settings = data?.profile?.settings ?? {};

return NextResponse.json({ bannerLinks: banners, settings });

また、クライアント側の useEffect でそれを読み込む処理も以下のように追加:

setShowBanners(data.settings?.showBanners ?? true);

5. ×ボタン(バナー削除)を追加したい

要望:
「画像を削除して空にしたい」機能が必要だが、単純に画像だけを消してもstateがうまく反映されなかった。

解決策:

handleDeleteBanner を以下のように実装:

const handleDeleteBanner = (index: number) => {
const newBanners = [...banners];
newBanners[index] = { imageUrl: '', linkUrl: '', visible: true };
setBanners(newBanners);
};

そして、各バナー入力欄に削除ボタンを追加:

<button
onClick={() => handleDeleteBanner(index)}
style={{
background: 'red',
color: 'white',
border: 'none',
borderRadius: '50%',
width: '24px',
height: '24px',
position: 'absolute',
top: '0',
right: '0',
cursor: 'pointer',
}}
>
×
</button>

親要素を position: relative にするのを忘れずに。


6. 保存ボタンのデザインが統一されない

課題:
他のボタン(Facebookブロックなど)と見た目が違い、デザイン的に浮いてしまう。

解決策:
CSSを統一:

<button
onClick={saveBannersToServer}
style={{
background: '#4e73df',
color: 'white',
border: 'none',
borderRadius: '4px',
padding: '4px 8px',
cursor: 'pointer',
}}
>
保存する
</button>

Tailwindで書くなら:

<button className="bg-blue-600 text-white px-3 py-1 rounded hover:bg-blue-700">
保存する
</button>

📌 学びと教訓

今回のバナー機能追加では、以下の学びがありました:

  • useState と API レスポンスの整合性が非常に重要(特にbooleanフラグ)
  • Firestoreへの書き込みは merge: true を基本とすべき
  • UIの状態(表示・非表示・削除)に対して柔軟な状態管理を構築する必要がある
  • コンポーネントの分離(SnsVisibilityToggle など)も読みやすさと保守性向上に寄与する
  • 名前付きエクスポートとデフォルトエクスポートは意識して統一すべき

🎉 まとめ

今回の「バナー機能」は、Procomユーザーが自分のショップや作品ページをより自由にアピールできるようにする大きなアップデートです。

その裏では、一見単純な見た目の機能でも保存ロジック・状態管理・UI連携の多層的な対応が必要でした。エラーのひとつひとつが学びとなり、より堅牢なコードに近づいたと感じます。

Procomでは今後も「簡単に使えて、自分を魅せられる」ツールを目指して進化を続けていきます。今回の技術メモが、同じようにNext.jsやFirebaseで開発している方のお役に立てれば幸いです!

おすすめの記事