Apa itu Next.js?
Jika Anda baru mengenal Next.js, biar saya jelaskan dengan sederhana. Next.js adalah React dengan kekuatan super. React biasa berjalan sepenuhnya di browser — server mengirim halaman HTML kosong, lalu JavaScript membangun halaman di browser pengguna. Next.js bisa me-render halaman di server sebelum mengirimnya, jadi pengguna melihat konten secara instan.
Mengapa ini penting untuk dashboard armada kita? Operator kita membuka dashboard pertama kali di pagi hari, seringkali dengan WiFi kantor yang lambat. Dengan server-side rendering, mereka langsung melihat peta armada alih-alih menatap spinner loading.
SSR vs CSR vs ISR — Kapan Menggunakan Apa
| Strategi | Cara Kerjanya | Cocok Untuk | Penggunaan Kita |
|---|---|---|---|
| SSR (Server-Side Rendering) | Halaman di-render di server setiap request | Data dinamis yang berubah per-request | Halaman utama dashboard |
| CSR (Client-Side Rendering) | Halaman di-render di browser | UI yang sangat interaktif | Interaksi peta, update real-time |
| ISR (Incremental Static Regeneration) | Halaman pre-built, di-regenerate berkala | Konten yang jarang berubah | Laporan, profil driver |
Keputusan Senior: Dashboard kita menggunakan SSR untuk loading awal (ambil data fresh dari API) lalu beralih ke CSR dengan WebSocket untuk update real-time. Ini memberikan yang terbaik dari kedua pendekatan.
Setup Proyek
npx create-next-app@latest fleet-web --typescript --eslint --src-dir
cd fleet-web
TypeScript Strict Mode — Mengapa Penting
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true
}
}
Mengapa strict mode? Karena undefined is not a function adalah error paling umum di JavaScript. TypeScript strict menangkap bug ini saat compile time, bukan jam 2 pagi ketika operator tidak bisa melihat truk di peta.
Arsitektur Komponen
Saya mengorganisir komponen ke dalam 4 kategori:
src/
├── components/
│ ├── ui/ # Primitif UI yang reusable dan stateless
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ │ └── Badge.tsx
│ │
│ ├── features/ # Komponen spesifik fitur dengan logika bisnis
│ │ ├── fleet/
│ │ │ ├── FleetMap.tsx
│ │ │ ├── VehicleCard.tsx
│ │ │ └── VehicleStatusBadge.tsx
│ │ └── driver/
│ │ ├── DriverList.tsx
│ │ └── DriverProfile.tsx
│ │
│ ├── layouts/ # Komponen layout halaman
│ │ ├── DashboardLayout.tsx
│ │ └── Sidebar.tsx
│ │
│ └── providers/ # Context providers
│ └── AuthProvider.tsx
Mengapa struktur ini? Ketika developer baru bergabung dan perlu memperbaiki bug di peta armada, mereka tahu harus mencari di components/features/fleet/. Organisasi yang jelas = onboarding lebih cepat.
Membangun Komponen Vehicle Card
Contoh komponen fitur yang terstruktur dengan baik:
// src/components/features/fleet/VehicleCard.tsx
interface Vehicle {
id: string;
plateNumber: string;
driverName: string;
status: 'active' | 'idle' | 'maintenance' | 'offline';
fuelLevel: number;
speed: number;
lastUpdate: Date;
location: {
lat: number;
lng: number;
address: string;
};
}
const STATUS_CONFIG = {
active: { label: 'Aktif', color: 'green' },
idle: { label: 'Diam', color: 'yellow' },
maintenance: { label: 'Maintenance', color: 'orange' },
offline: { label: 'Offline', color: 'red' },
} as const;
export default function VehicleCard({ vehicle, isSelected, onSelect }: VehicleCardProps) {
const statusConfig = STATUS_CONFIG[vehicle.status];
return (
<div
className={`vehicle-card ${isSelected ? 'selected' : ''}`}
onClick={() => onSelect(vehicle.id)}
role="button"
tabIndex={0}
aria-label={`Kendaraan ${vehicle.plateNumber}, status: ${statusConfig.label}`}
>
<div className="vehicle-card__header">
<h3>{vehicle.plateNumber}</h3>
<Badge color={statusConfig.color}>{statusConfig.label}</Badge>
</div>
<p>🧑✈️ {vehicle.driverName}</p>
<p>📍 {vehicle.location.address}</p>
<p>🚚 {vehicle.speed} km/h</p>
<FuelGauge level={vehicle.fuelLevel} />
</div>
);
}
Perhatikan pola-pola senior di sini:
- Tipe TypeScript eksplisit — tidak ada
any, tidak menebak - Objek konfigurasi konstan —
STATUS_CONFIGalih-alih rantai if/else - Aksesibilitas —
role,tabIndex,aria-labelpada elemen interaktif
State Management: Kapan Menggunakan Apa
| Tipe State | Solusi | Contoh |
|---|---|---|
| State UI | useState | Sidebar buka/tutup |
| State UI bersama | React Context | Tema, user terautentikasi |
| State server | React Query / SWR | Data armada, daftar driver |
| State real-time | WebSocket + useReducer | Posisi kendaraan live |
Kesalahan Umum: Menggunakan Redux untuk semua hal. Jika state Anda berasal dari API, gunakan library data-fetching seperti React Query. Library ini menangani caching, re-fetching, dan loading states.
API Service Layer
// src/services/fleet.service.ts
const API_BASE = process.env.NEXT_PUBLIC_API_URL;
export const fleetService = {
async getVehicles(): Promise<Vehicle[]> {
const res = await fetch(`${API_BASE}/api/vehicles`);
if (!res.ok) throw new Error(`Gagal mengambil data kendaraan: ${res.status}`);
return res.json();
},
};
Mengapa service layer? Ketika endpoint API berubah, Anda update satu file — bukan 15 komponen.
Tips Optimasi Performa
1. Code Splitting dengan Dynamic Imports
import dynamic from 'next/dynamic';
const FleetMap = dynamic(() => import('@/components/features/fleet/FleetMap'), {
loading: () => <div>Memuat peta...</div>,
ssr: false,
});
2. Memoize Komputasi Mahal
const stats = useMemo(() => ({
total: vehicles.length,
active: vehicles.filter(v => v.status === 'active').length,
lowFuel: vehicles.filter(v => v.fuelLevel < 25).length,
}), [vehicles]);
Selanjutnya
Di Part 3, kita akan membangun backend API NestJS — mesin di balik dashboard armada kita. Kita akan membahas module, controller, service, DTO, dan dependency injection.
Ini adalah Part 2 dari seri Fleet Management System. Setiap artikel membangun di atas yang sebelumnya, secara bertahap meningkatkan kompleksitas.
