はじめに
Next.jsを使用して、インタラクティブなアドベントカレンダーUIを実装してみました。
3Dアニメーションや雪のエフェクトを取り入れつつ、TypeScriptの型安全性も確保しています。
完成イメージ
- 24個のドアが7日ごとにグリッド表示
- ドアをクリックすると3Dアニメーションで開く
- 雪が降るエフェクト
- 中にランダムな絵文字とメッセージ
 https://adbent-yusukekoides-projects.vercel.app/
実装で工夫した点
1. ハイドレーションエラーの解決
App Routerでの最大の課題は、サーバーサイドレンダリング(SSR)とクライアントサイドレンダリング(CSR)の整合性でした。
// クライアントサイドのみの処理を制御
const [isClient, setIsClient] = useState(false);
useEffect(() => {
  setIsClient(true);
}, []);
// ランダムな要素はクライアントサイドでのみ実行
const [currentSurprises] = useState(() => {
  const initialSurprises: SurpriseMap = {};
  if (typeof window !== 'undefined') {
    const shuffled = [...surprises].sort(() => Math.random() - 0.5);
    // ...
  }
  return initialSurprises;
});
2. TypeScriptの型安全性確保
オブジェクトのインデックスアクセスに関する型エラーに悩まされました。
インターフェースを定義することで解決しました:
interface Surprise {
  emoji: string;
  message: string;
}
interface SurpriseMap {
  [key: number]: Surprise;
}
const [currentSurprises] = useState<SurpriseMap>(() => {
  // ...
});
3. 3Dアニメーションの実装
CSSの3D変換を使用して、本物のドアのような開閉アニメーションを実現:
.door {
  transform-style: preserve-3d;
  transition: transform 0.5s;
}
.door.open {
  transform: rotateY(-105deg);
}
4. カレンダーグリッドの実装
7日ごとの改行を実現するために、配列を週単位で分割:
const renderCalendarGrid = () => {
  const weeks = [];
  let currentWeek = [];
  
  for (let i = 1; i <= 24; i++) {
    currentWeek.push(<Door key={i} number={i} />);
    
    if (currentWeek.length === 7 || i === 24) {
      weeks.push(
        <div key={weeks.length} className="flex justify-center w-full">
          {currentWeek}
        </div>
      );
      currentWeek = [];
    }
  }
  
  return weeks;
};
5. 雪のエフェクト
複数の雪の結晶文字を使い、ランダムな動きを実現:
const snowflakes = ["❄", "❅", "❆", "✻", "✺", "*"];
const getSnowflakes = () => {
  return Array.from({ length: 8 }).map((_, i) => ({
    flake: snowflakes[Math.floor(Math.random() * snowflakes.length)],
    delay: Math.random() * 5,
    duration: 2 + Math.random() * 3,
    position: Math.random() * 100
  }));
};
困った点と解決策
1. SSRとCSRの不一致
問題:
- 
Math.random()を使用した要素の生成
- クライアントサイドでのみ必要なアニメーション
解決策:
- 
isClientフラグでレンダリングを制御
- 
useEffectでクライアントサイド処理を分離
2. TypeScriptのエラー
問題:
- インデックスシグネチャの欠落
- 暗黙的なany型
解決策:
- 適切なインターフェースの定義
- 型アノテーションの追加
3. モバイル対応
問題:
- 小さい画面でのグリッドのはみ出し
- タッチデバイスでの3Dエフェクト
解決策:
- レスポンシブなグリッドレイアウト
- 
hover状態の適切な処理
<div className="max-w-7xl mx-auto p-4 sm:p-6">
  <div className="flex flex-col gap-4">
    {/* グリッドコンテンツ */}
  </div>
</div>
デプロイ方法
Vercelを使用して簡単にデプロイできます:
- GitHubにプッシュ
- Vercelでプロジェクトをインポート
- 自動デプロイの設定
# デプロイコマンド
vercel
まとめ
Next.js を使用したインタラクティブUIの実装は、
いくつかの課題がありましたが、適切な設計と型定義により解決できました。
特に以下の点が重要でした:
- SSR/CSRの適切な使い分け
- TypeScriptの型安全性確保
- パフォーマンスとアニメーションの両立
