30. Dynamic Icon Management

Pengelolaan ikon yang tidak terstruktur seringkali menjadi penyebab utama membengkaknya ukuran bundle (bundle size bloat), inefisiensi render, dan inkonsistensi desain dalam aplikasi web modern. Dokumen ini menjelaskan standar pengelolaan ikon berbasis SVG secara dinamis dan efisien di ekosistem React (Next.js & Vite).


A. Kenapa Menggunakan SVG Berbasis React?

Sebelum masuk ke teknis, penting untuk memahami perbandingan metode penyajian ikon:

MetodeKelebihanKekuranganRekomendasi
Icon Fonts (e.g., FontAwesome)Mudah digunakan secara global.Harus memuat seluruh font file meskipun hanya memakai beberapa ikon; performa rendering text-layout lambat; tidak fleksibel untuk multi-warna.❌ Sangat tidak disarankan untuk aplikasi modern.
Raw SVG / Image Tag (<img src="...">)File cacheable oleh browser; memisahkan aset dari JavaScript.Tidak bisa dimanipulasi warnanya (fill/stroke) secara dinamis menggunakan CSS atau React props; memicu request HTTP tambahan jika tidak menggunakan sprite.⚠️ Hanya untuk logo statis atau ilustrasi yang tidak butuh interaksi/hover.
Inline React SVG ComponentKontrol penuh lewat props (size, color, className); dapat dianimasikan; terintegrasi dengan TypeScript.Meningkatkan ukuran bundle JavaScript jika tidak dikelola dengan dynamic import atau tree-shaking yang baik.🌟 Sangat Direkomendasikan untuk elemen UI interaktif.

B. Membuat Komponen SVG Kustom (React + TypeScript)

Saat desainer memberikan file .svg mentah, konversikan menjadi komponen React mandiri. Hindari menyalin file SVG mentah langsung ke komponen halaman.

Aturan Konversi SVG ke React:

  1. Ganti atribut bertipe kebab-case menjadi camelCase (contoh: stroke-width menjadi strokeWidth, clip-path menjadi clipPath).
  2. Gunakan currentColor pada atribut fill atau stroke agar ikon secara otomatis mengikuti warna teks (color) dari parent container.
  3. Sediakan props standar seperti size (mempengaruhi width dan height secara proporsional), color, dan className.
  4. Bungkus komponen menggunakan React.forwardRef agar parent dapat me-reference DOM element <svg> secara langsung jika diperlukan (misalnya untuk tooltips atau animasi pihak ketiga).

Contoh Implementasi Komponen Ikon Kustom:

import React, { forwardRef } from "react";

export interface IconProps extends React.SVGProps<SVGSVGElement> {
  size?: number;
  color?: string;
}

export const DashboardIcon = forwardRef<SVGSVGElement, IconProps>(
  ({ size = 24, color = "currentColor", className, ...props }, ref) => {
    return (
      <svg
        ref={ref}
        width={size}
        height={size}
        viewBox="0 0 24 24"
        fill="none"
        stroke={color}
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
        className={className}
        {...props}
      >
        <rect x="3" y="3" width="7" height="9" />
        <rect x="14" y="3" width="7" height="5" />
        <rect x="14" y="12" width="7" height="9" />
        <rect x="3" y="16" width="7" height="5" />
      </svg>
    );
  }
);

DashboardIcon.displayName = "DashboardIcon";

C. Otomatisasi dengan SVGR (SVG React Loader)

Mengonversi SVG secara manual sangat melelahkan dan rentan kesalahan. Gunakan SVGR untuk mengimpor file .svg secara langsung sebagai komponen React di tingkat bundler.

1. Konfigurasi Pada Vite (vite.config.ts):

Instal plugin @svgr/rollup atau vite-plugin-svgr:

npm install -D vite-plugin-svgr

Konfigurasi di vite.config.ts:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";

export default defineConfig({
  plugins: [
    react(),
    svgr({
      // Opsi untuk memperlakukan SVG sebagai React Component secara default
      svgrOptions: {
        icon: true,
        expandProps: "end",
      },
    }),
  ],
});

Cara penggunaan di komponen Vite:

import { ReactComponent as StarIcon } from "@/assets/icons/star.svg";

export default function Rating() {
  return <StarIcon className="text-yellow-500 w-6 h-6" />;
}

2. Konfigurasi Pada Next.js (next.config.js):

Next.js menggunakan Webpack di balik layar. Anda dapat mengonfigurasi next.config.js untuk memproses file .svg:

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    // Cari rule webpack yang menangani SVG
    const fileLoaderRule = config.module.rules.find((rule) =>
      rule.test?.test?.(".svg")
    );

    config.module.rules.push(
      // Pasang kembali file-loader untuk SVG dengan resourceQuery ?url
      {
        ...fileLoaderRule,
        test: /\.svg$/i,
        resourceQuery: /url/, // e.g. import logo from './logo.svg?url'
      },
      // Konversi SVG biasa menjadi React Component
      {
        test: /\.svg$/i,
        issuer: fileLoaderRule.issuer,
        resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] }, // exclude jika diakhiri ?url
        use: ["@svgr/webpack"],
      }
    );

    // Modifikasi fileLoaderRule agar mengabaikan SVG standar yang tidak memiliki query (?url)
    fileLoaderRule.exclude = /\.svg$/i;

    return config;
  },
};

module.exports = nextConfig;

Cara penggunaan di komponen Next.js:

import StarIcon from "@/assets/icons/star.svg"; // Diimpor sebagai komponen React
import starUrl from "@/assets/icons/star.svg?url"; // Diimpor sebagai static path URL untuk <img>

export default function StarRating() {
  return (
    <div>
      <StarIcon className="w-5 h-5 text-yellow-400" />
      <img src={starUrl} alt="Star path indicator" className="w-5 h-5" />
    </div>
  );
}

D. Mengelola Library Ikon (Lucide-react) & Tree-Shaking

Jika proyek Anda menggunakan library pihak ketiga seperti lucide-react (seperti portal dokumentasi ini), pastikan melakukan impor dengan cara yang mendukung Tree-Shaking.

1. Impor yang Benar (Named Imports):

Tree-shaking memastikan hanya kode ikon yang digunakan yang masuk ke dalam bundle akhir.

// ✅ BENAR - Mendukung Tree-Shaking
import { Shield, Zap, Search } from "lucide-react";

2. Hindari Wildcard / Barrel Import Bermasalah:

Hindari mengimpor seluruh library lalu memilihnya lewat string dinamis, karena hal ini merusak kemampuan bundler untuk membuang ikon yang tidak terpakai.

// ❌ SALAH - Seluruh ikon lucide (ribuan ikon) akan masuk ke bundle akhir!
import * as Icons from "lucide-react";

interface DynamicIconProps {
  name: string;
}

export function BadDynamicIcon({ name }: DynamicIconProps) {
  const IconComponent = Icons[name as keyof typeof Icons];
  return <IconComponent />;
}

Jika terpaksa harus menggunakan nama ikon yang dinamis (misal, dari API database), gunakan pendekatan lazy loading via lucide-react/dynamic atau next/dynamic agar memuat ikon secara on-demand.


E. Pola Desain Registry / Wrapper Component

Untuk mempertahankan konsistensi visual (ukuran standard, warna, margin) dan type-safety, buatlah sebuah Dynamic Icon Wrapper terpusat.

Contoh Wrapper Component (components/ui/Icon.tsx):

import React from "react";
import * as LucideIcons from "lucide-react";

// Definisikan tipe nama ikon yang valid dari Lucide
export type IconName = keyof typeof LucideIcons;

interface IconProps extends React.ComponentPropsWithoutRef<"svg"> {
  name: IconName;
  size?: number;
  color?: string;
  className?: string;
}

export const Icon = ({
  name,
  size = 20,
  color = "currentColor",
  className,
  ...props
}: IconProps) => {
  // Ambil komponen ikon dari registry Lucide secara statis
  const IconComponent = LucideIcons[name] as React.ComponentType<any>;

  if (!IconComponent) {
    console.warn(`Icon dengan nama "${name}" tidak ditemukan di Lucide.`);
    return null;
  }

  return (
    <IconComponent
      size={size}
      color={color}
      className={className}
      {...props}
    />
  );
};

Catatan: Gunakan pola ini dengan hati-hati. Jika bundler Anda tidak mengkonfigurasi tree-shaking dengan optimal, pendekatan registry di atas bisa memperbesar bundle size. Jika bundle size sangat kritis, sebaiknya import ikon secara spesifik di tiap file.


F. Best Practices: Accessibility & Performance

1. Pedoman Accessibility (A11y):

Ikon seringkali bersifat dekoratif, tetapi terkadang memiliki makna fungsional. Pastikan memberikan markup yang ramah bagi screen reader:

  • Ikon Dekoratif (Hanya Hiasan): Tambahkan aria-hidden="true" agar screen reader mengabaikannya.
    <Shield className="w-4 h-4" aria-hidden="true" />
    
  • Ikon Standalone / Interaktif (Bertindak sebagai Tombol): Jika ikon digunakan sebagai tombol tanpa teks pendamping (contoh: tombol tutup X), wajib berikan label teks alternatif.
    <button aria-label="Tutup Halaman" onClick={closeModal}>
      <X className="w-5 h-5" aria-hidden="true" />
    </button>
    

2. Optimasi Kinerja (Performance):

  • Gunakan SVG Sprite Sheet untuk Ikon Statis Densitas Tinggi: Jika halaman Anda merender ratusan ikon kecil sekaligus (seperti dasbor analitik kompleks), gunakan SVG Sprite Sheets. Menggunakan satu file .svg eksternal yang di-link via <use href="/sprite.svg#icon-id" /> jauh lebih cepat memuat dan menghemat konsumsi memori DOM daripada merender ratusan komponen SVG React independen.
  • Satu Desain Viewbox: Pastikan seluruh ikon tim Anda dirancang dengan grid yang konsisten (misalnya, viewBox="0 0 24 24" atau viewBox="0 0 20 20"). Hal ini menghindari pergeseran layout yang aneh saat berganti ikon.

G. Decision Tree Pemilihan Strategi Ikon

Gunakan diagram keputusan berikut untuk memilih cara penanganan ikon terbaik di proyek Anda:

graph TD
    A[Mulai Integrasi Ikon] --> B{Apakah ikon bersumber dari UI Library umum?}
    B -- Ya --> C[Gunakan Named Import dari lucide-react / react-icons]
    B -- Tidak --> D{Apakah ikon merupakan file kustom buatan designer?}
    
    D -- Ya --> E{Berapa jumlah ikon kustom tersebut?}
    E -- Sedikit / Jarang Berubah --> F[Gunakan SVGR Loader / Konversi manual ke React Component]
    E -- Sangat Banyak / Butuh Performa Tinggi --> G[Gunakan SVG Sprite Sheet & elemen use href]

H. Referensi Tambahan

© 2026 Bit Group Asia. All rights reserved.

ArchitectureSecurityGuidelines