1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

lucide-react で絵文字アイコンを一掃する—App Router 統一アイコン設計 3 ステップ

1
Last updated at Posted at 2026-06-18

Cosoado Lab Blog 同時掲載予定: https://cosoado-lab.com/blog/lucide-react-replace-emoji-icons/

TL;DR

  • 絵文字アイコンは OS・端末フォント依存で文字化けする
  • lucide-react v1.20.0 に統一すれば崩れない。サイズ・色・太さを props で一元管理できる
  • インストール → 薄型ラッパー作成 → 全置換の 3 ステップ、1 日以内で完了
  • App Router の Server Component でもそのまま動く。next/dynamic を噛ませると逆にハイドレーションエラーになる

先月、知人から「ボタンが豆腐になってるんだけど」とスクリーンショットが届いた。自分の Mac と iPhone でしか動作確認していなかったツケで、Android 10 の端末では 📋 と 🔗 が □ に化けていた。絵文字は OS バージョンとインストールフォントに依存するため、古い Android や一部の Windows 環境では描画できないコードポイントが存在する。

その晩、全アイコンを lucide-react に置き換えた。

絵文字がアイコンに向かない 3 つの理由

  1. OS・フォント依存:同じ Unicode コードポイントでも Android 10 以前、Windows 10、macOS では見た目が異なる。描画できない環境では □(豆腐)になる
  2. CSS スタイルが効かないcolor プロパティで色を変えても反映が不安定。font-size の微調整も効きにくい
  3. ダークモード非対応:白地前提の絵文字が暗い背景に浮く。システムのカラースキームに自動追従する手段がない

SVG アイコンライブラリに移行すれば、これら 3 つがまとめて解消する。

Step 1. lucide-react をインストールする

npm install lucide-react
# または
pnpm add lucide-react

npm の lucide-react ページ で最新バージョンを確認できる。本記事執筆時点の最新は v1.20.0。

lucide-react は Feather icons から派生した MIT ライセンスの SVG アイコンセットで、1,500 以上のアイコンを収録している。各アイコンは独立した named export になっているため、使ったアイコン以外はビルド時にツリーシェイキングで除外される。バンドルサイズへの影響は最小限。

Step 2. 薄型ラッパーコンポーネントを作る

アイコンをコードベース全体に直接散らばらせると、sizestrokeWidth の指定がバラバラになる。まずプロジェクト共通のデフォルト値を 1 箇所に集約するラッパーを作る。

// components/icon.tsx
import { type LucideIcon } from "lucide-react";

type IconProps = {
  Icon: LucideIcon;
  size?: number;
  className?: string;
  strokeWidth?: number;
};

export function Icon({
  Icon,
  size = 16,
  strokeWidth = 1.5,
  className,
}: IconProps) {
  return (
    <Icon size={size} strokeWidth={strokeWidth} className={className} />
  );
}

lucide-react が受け取る主なプロパティ:

prop デフォルト 説明
size number | string 24 幅・高さ(px)
strokeWidth number 2 線の太さ
color string currentColor CSS color を継承
absoluteStrokeWidth boolean false サイズ変更時に線幅を拡縮しない
className string Tailwind 等のクラスを渡せる

color のデフォルトが currentColor なので、親要素の text-gray-500dark:text-gray-400 でそのまま色が変わる。ダークモードにも自動追従する。

Step 3. 絵文字を lucide アイコンに置き換える

よく使う絵文字と対応するコンポーネント名の対照表:

絵文字 lucide コンポーネント 用途例
📋 ClipboardList フォーム、コピー操作
🔗 Link URL 共有
CircleCheck 完了ステータス
CircleX エラー、削除確認
🔍 Search 検索入力フィールド
⚙️ Settings 設定画面
🔔 Bell 通知バッジ
👤 User プロフィール
Star お気に入り登録
🏠 House ホームへ戻る

実際の置換コード:

// Before
<button>📋 コピー</button>

// After
import { ClipboardList } from "lucide-react";
import { Icon } from "@/components/icon";

<button className="flex items-center gap-1">
  <Icon Icon={ClipboardList} size={14} />
  コピー
</button>

アイコン名は PascalCase。lucide の命名規則は kebab-case をそのまま PascalCase に変換したものになっている(例: circle-checkCircleCheckclipboard-listClipboardList)。どのアイコンが何という名前かは lucide.dev/icons の検索が早い。

ハマったこと

next/dynamic を噛ませたらハイドレーションエラーが大量発生した

「アイコンが多いから遅延読み込みにしよう」と思って next/dynamic を使った。

// ❌ これはやってはいけない
import dynamic from "next/dynamic";

const ClipboardList = dynamic(() =>
  import("lucide-react").then((m) => ({ default: m.ClipboardList }))
);

結果、Warning: Expected server HTML to contain a matching... のハイドレーション不一致が大量発生した。サーバーでは null レンダリング、クライアントでアイコンが遅れて登場するタイミングが合わず、SSR と CSR の出力が一致しないのが原因。

これが一番やった失敗で、先に気づくべきだった。lucide-react の各アイコンはただの SVG コンポーネントで、サーバー・クライアント両方でそのまま同期レンダリングできる。dynamic import は不要で、むしろ有害。

// ✅ そのまま named import で足りる
import { ClipboardList } from "lucide-react";

② マイナーバージョンアップでアイコン名が変わっていた

lucide はアイコン名を定期的に整理する。ClipboardClipboardList に変わった例のように、パッケージ更新後にビルドが通らなくなることがある。更新時は lucide の GitHub releases の Breaking Changes セクションを確認する習慣をつけておくと安心。

size に Tailwind クラス名を渡してレイアウトが崩れた

size="text-sm" のように文字列を渡すと、width="text-sm" という不正な SVG 属性になりレイアウトが崩れる。size prop は数値(px)専用。Tailwind でサイズを制御したい場合は className="h-4 w-4" を使う。

まとめ

絵文字アイコンから lucide-react への移行は、作業量としては 1 日以内で終わった。OS・フォント依存から解放されて、ダークモード対応が自動になる。個人開発でもプロダクション環境でも、早めにやっておいて損のない一手だと思う。

App Router でのキャッシュ挙動も以前ハマったのでまとめている。同じ Next.js スタックで開発している人は fetch に no-store を設定しないと静かに古いデータが出る理由 もどうぞ。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?