Lompat ke konten utama
vourdev
Kembali ke Blog

Dev Notes

Kenapa Belajar Fundamental Jauh Lebih Penting daripada Ikut Hype Framework

Bedah tuntas kenapa menguasai Javascript core, browser runtime, dan arsitektur web bikin karir software engineer jauh lebih kebal resesi dibanding gonta-ganti framework.

Gambar Cover Kenapa Belajar Fundamental Jauh Lebih Penting daripada Ikut Hype Framework
Ditulis olehvourdev11 menit baca

Coba ingat-ingat dalam lima tahun terakhir: berapa banyak framework, meta-framework, atau library state management yang sempat digadang-gadang sebagai "masa depan web development"?

Dari era jQuery ke AngularJS, loncat ke React SPA, heboh Redux, pindah ke Zustand, beralih ke Next.js Pages Router, dirombak total ke App Router dengan React Server Components (RSC), lalu orang-orang mulai teriak soal Remix, SvelteKit, SolidStart, Astro, hingga TanStack Start.

Kalau kamu tipikal developer yang kerjanya cuma mengejar apa yang lagi trending di linimasa Twitter/X atau Reddit, kamu pasti mengalami apa yang disebut Framework Fatigue—kondisi di mana kamu merasa lelah, tertinggal, dan cemas karena stack yang baru saja kamu kuasai tahun lalu tiba-tiba dianggap usang (deprecated) tahun ini.

Masalahnya bukan pada ekosistem JavaScript yang bergerak terlalu cepat. Masalahnya ada pada sudut pandang belajarmu.

Ketika kamu belajar web development dengan cara menghafal API framework (misalnya cara pasang useEffect atau konfigurasi next.config.js), kamu sedang membangun rumah di atas pasir hisap. Begitu API-nya berubah, pondasimu runtuh. Namun, ketika kamu memahami core primitives—bagaimana JavaScript Engine mengeksekusi kode, bagaimana browser merender piksel, dan bagaimana protokol jaringan bekerja—framework apa pun yang keluar tahun depan cuma butuh waktu beberapa hari untuk kamu kuasai.

Perbedaan fundamental antara Framework-First vs Fundamentals-First EngineerFramework-FirstMenghafal sintaksis API khususPanik saat terjadi breaking changeDebugging dengan trial-and-errorTergantung package eksternaluntuk hal sepeleSulit adaptasi ke bahasa/runtimelainFundamentals-FirstPaham underlying primitives &runtimeMelihat framework hanya sebagaitrade-off toolRoot-cause debugging sampai keengine/networkMampu membangun tooling &abstraksi sendiriAdaptasi ke framework/bahasabaru dalam hitungan hariInvestasi pada fundamental memberikan compound interest seumur hidup karir rekayasa perangkat lunak.
Perbedaan fundamental antara Framework-First vs Fundamentals-First Engineer

Membedah Lapisan Abstraksi: Apa yang Terjadi di Bawah Kap Mesin?

Visualisasi arsitektur browser engine dan JavaScript runtime
Memahami engine runtime adalah pembeda utama antara framework user dan true engineer. · JavaScript Event Loop — Byteslovesbits (CC BY-SA 4.0)

Semua framework frontend—mulai dari React, Vue, Svelte, hingga Qwik—diciptakan untuk satu tujuan: menyederhanakan manipulasi UI yang kompleks dan menjaga state tetap sinkron dengan DOM.

Namun, ada satu hukum universal dalam rekayasa perangkat lunak yang dirumuskan oleh Joel Spolsky: The Law of Leaky Abstractions. Bunyinya: "Semua abstraksi yang tidak sepele, pada tingkat tertentu, akan bocor (leaky)."

Artinya, sehebat apa pun framework yang kamu pakai, suatu saat kamu akan menabrak dinding performa, memory leak, race condition, atau hydration mismatch yang tidak bisa diselesaikan hanya dengan membaca dokumentasi framework tersebut. Kamu terpaksa harus turun ke lapisan di bawahnya.

Lapisan abstraksi web development dari runtime hingga framework modernFrameworks & AbstractionsReact, Next.js, Vue, Solid, SvelteWeb Standards…Fetch, Streams, Web Workers, Proxy, MutationObserverBrow…V8/SpiderMonkey, DOM API, Event Loop, Critical Rendering PathNetwork & Protocol LayerTCP/IP, TLS, HTTP/2, HTTP/3, WebSocketsHardware & OS KernelMemory allocation, CPU threads
Lapisan abstraksi web development dari runtime hingga framework modern

Bayangkan kamu sedang debugging kenapa halaman web kamu lambat (Total Blocking Time tinggi). Developer yang cuma paham framework akan sibuk memindahkan hook atau memasang useMemo di mana-mana secara membabi buta.

Sebaliknya, senior engineer yang paham fundamental browser akan membuka Performance Profiler di Chrome DevTools, menganalisis Long Tasks, melihat Execution Stack, memeriksa Style Recalculation dan Layout Thrashing, lalu menemukan bahwa biang keroknya adalah manipulasi layout DOM berulang di dalam microtask.

Mari kita buktikan poin ini dengan membedah 3 konsep fundamental yang sering disangka sebagai "sihir" framework.


Bedah Kasus 1: Reaktivitas Bukan Sihir (Bikin Reactive Engine Sendiri)

Source code TypeScript implementasi reactive state system
Reaktivitas pada framework modern hanyalah wrapper di atas JavaScript native primitives. · Markus Spiske (Unsplash)

Banyak junior engineer menganggap reaktivitas (reactivity)—seperti ref di Vue, signals di Solid/Preact, atau State di modern framework—adalah hal gaib yang hanya bisa dibuat oleh core team kelas dunia. Padahal, fondasi dari modern reactivity hanyalah kombinasi dari JavaScript `Proxy`, Closures, dan Observer Pattern.

Mari kita bangun sebuah reactivity engine minimalis berbasis signal dari nol menggunakan TypeScript murni tanpa dependensi apa pun.

typescript
// reactive.ts
type EffectCallback = () => void;

// Menyimpan effect yang sedang dieksekusi saat ini
let activeEffect: EffectCallback | null = null;

export class Signal<T> {
  private _value: T;
  private subscribers: Set<EffectCallback> = new Set();

  constructor(initialValue: T) {
    this._value = initialValue;
  }

  get value(): T {
    // 1. Dependency Tracking: Jika sedang ada effect yang berjalan, catat sebagai subscriber
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
    return this._value;
  }

  set value(newValue: T) {
    if (this._value !== newValue) {
      this._value = newValue;
      // 2. Trigger / Notification: Jalankan semua effect yang bergantung pada signal ini
      this.subscribers.forEach((effect) => effect());
    }
  }
}

export function createSignal<T>(initialValue: T): [() => T, (val: T) => void] {
  const signal = new Signal(initialValue);
  const getter = () => signal.value;
  const setter = (newValue: T) => {
    signal.value = newValue;
  };
  return [getter, setter];
}

export function createEffect(callback: EffectCallback): void {
  activeEffect = callback;
  // Eksekusi pertama kali untuk mengumpulkan dependency (trigger getter)
  callback();
  // Reset setelah selesai
  activeEffect = null;
}

Penjelasan Baris demi Baris:

  1. `activeEffect`: Variabel penampung di level module scope. Ini bertindak sebagai pointer global untuk melacak siapa yang sedang membaca nilai state.
  2. `Signal<T>` Class: Menyimpan _value aktual dan subscribers dalam bentuk Set<EffectCallback>. Menggunakan Set memastikan tidak ada fungsi listener duplikat.
  3. `get value()`: Kunci dari automatic dependency tracking. Ketika nilai diakses di dalam createEffect, getter ini terpanggil dan otomatis menambahkan activeEffect ke dalam daftar subscribers.
  4. `set value(newValue)`: Ketika nilai berubah, sistem melakukan iterasi ke seluruh subscribers dan mengeksekusinya kembali.
  5. `createEffect(callback)`: Menyetel activeEffect = callback, lalu langsung menjalankan callback(). Di dalam eksekusi tersebut, jika ada sinyal yang dibaca, koneksi otomatis terbentuk.

Sekarang coba kita jalankan kode di atas:

typescript
// index.ts
import { createSignal, createEffect } from "./reactive";

const [count, setCount] = createSignal(0);
const [name, setName] = createSignal("Vour");

// Effect ini HANYA bergantung pada count
createEffect(() => {
  console.log(`[Effect 1] Counter updated: ${count()}`);
});

// Effect ini bergantung pada count DAN name
createEffect(() => {
  console.log(`[Effect 2] Greeting: Halo ${name()}, hitungan: ${count()}`);
});

console.log("--- Update State ---");
setCount(1); 
// Output: 
// [Effect 1] Counter updated: 1
// [Effect 2] Greeting: Halo Vour, hitungan: 1

setName("Studio");
// Output:
// [Effect 2] Greeting: Halo Studio, hitungan: 1
// (Effect 1 TIDAK terpanggil karena tidak subscribe ke 'name')
Insight: Begitu kamu memahami pola ini, kamu tidak akan lagi bingung melihat bagaimana Vue 3 Reactivity, MobX, SolidJS Signals, Svelte 5 Runes, atau Preact Signals bekerja. Semuanya adalah variasi dari arsitektur dependency tracking di atas.

Bedah Kasus 2: Event Loop, Microtask, dan Batching UI Updates

Salah satu sumber bug paling menyebalkan di frontend adalah race condition dan unnecessary re-renders. Framework seperti React melakukan automatic batching agar ketika kamu mengubah state 5 kali berturut-turut, DOM tidak di-render ulang 5 kali.

Bagaimana cara kerjanya? Jawabannya ada pada JavaScript Event Loop dan Microtask Queue.

Alur eksekusi synchronous call stack, microtask queue, dan browser renderingCall StackMicrotask QueueRender PipelineMacrotask Queue1. 1. Antrekan job via queueMicrotask()2. 3. Eksekusi semua microtask sampai ha…3. 4. RequestAnimationFrame / Layout / Paint4. 5. Ambil 1 Macrotask berikutnya (setTimeout/I/O)Microtask queue SELALU dikosongkan sepenuhnya sebelum browser melakukan tahapan visual render.
Alur eksekusi synchronous call stack, microtask queue, dan browser rendering

Kalau kamu tidak paham perbedaan antara Macrotask (setTimeout, setInterval, I/O) dan Microtask (Promise.then, queueMicrotask, MutationObserver), kamu akan kesulitan membuat sistem asynchronous yang deterministik.

Mari kita buat sebuah batching scheduler murni yang meniru cara framework memproses antrean mutasi UI:

typescript
// batcher.ts
type Task = () => void;

class BatchScheduler {
  private queue: Set<Task> = new Set();
  private isFlushing: boolean = false;

  public schedule(task: Task): void {
    this.queue.add(task);

    if (!this.isFlushing) {
      this.isFlushing = true;

      // Memanfaatkan microtask queue native browser/Node.js
      queueMicrotask(() => {
        this.flush();
      });
    }
  }

  private flush(): void {
    try {
      console.log(`[BatchScheduler] Memproses ${this.queue.size} task sekaligus dalam 1 tick.`);
      this.queue.forEach((task) => task());
    } finally {
      this.queue.clear();
      this.isFlushing = false;
    }
  }
}

// Demo penggunaan:
const scheduler = new BatchScheduler();

function updateDOM(component: string) {
  console.log(`Render DOM untuk: ${component}`);
}

// Simulasi mutasi beruntun dalam satu synchronous execution block
console.log("Start synchronous execution");

scheduler.schedule(() => updateDOM("Sidebar"));
scheduler.schedule(() => updateDOM("Header"));
scheduler.schedule(() => updateDOM("Sidebar")); // Duplikat diabaikan oleh Set

console.log("End synchronous execution");

// Output di konsol:
// Start synchronous execution
// End synchronous execution
// [BatchScheduler] Memproses 2 task sekaligus dalam 1 tick.
// Render DOM untuk: Sidebar
// Render DOM untuk: Header

Mengapa Ini Sangat Penting?

Tanpa queueMicrotask, setiap pemanggilan schedule() akan langsung memicu render seketika (blocking UI). Dengan menunda eksekusi ke antrean microtask, kita memberikan kesempatan bagi seluruh JavaScript sinkron untuk selesai dijalankan, mengumpulkan semua mutasi, membuang duplikasi, lalu mengeksekusi pembaruan DOM tepat sebelum browser melakukan proses Repaint/Reflow.

Inilah alasan kenapa di React kamu tidak bisa membaca nilai state terbaru tepat satu baris setelah fungsi setCount(count + 1) dipanggil. Bukan karena React rusak, melainkan karena React sengaja menunda eksekusi ke fase batching berikutnya.


Bedah Kasus 3: Network Primitives vs Next.js Caching Magic

Beberapa tahun belakangan, banyak developer mengeluhkan fitur Data Cache di Next.js App Router yang membingungkan. Ada halaman yang tidak mau ter-update, ada data yang tersangkut (stale data), dan perintah revalidatePath yang terasa tidak konsisten.

Kenapa hal ini terjadi? Karena banyak developer terbiasa memperlakukan fetch() di Next.js sebagai magical framework feature, bukan sebagai ekstensi dari Web Fetch API Specification dan standar HTTP Cache Headers.

Mari kita lihat bagaimana logika caching HTTP native bekerja di balik layar:

typescript
// httpClient.ts
interface CacheEntry<T> {
  data: T;
  timestamp: number;
  etag?: string;
}

export class ResilientHttpClient {
  private cache: Map<string, CacheEntry<any>> = new Map();

  async fetchWithSWR<T>(url: string, maxAgeMs: number = 5000): Promise<T> {
    const cached = this.cache.get(url);
    const now = Date.now();

    // 1. Fresh Cache Hit -> Langsung kembalikan tanpa request jaringan
    if (cached && (now - cached.timestamp < maxAgeMs)) {
      console.log(`[CACHE HIT] Mengambil dari memory cache: ${url}`);
      return cached.data;
    }

    // 2. Stale Cache Hit -> Kembalikan data lama, lakukan revalidasi di background (SWR pattern)
    if (cached) {
      console.log(`[STALE HIT] Data kedaluwarsa, trigger background revalidation: ${url}`);
      this.revalidateInBackground(url);
      return cached.data;
    }

    // 3. Cache Miss -> Wajib fetch ke server secara sinkron
    console.log(`[CACHE MISS] Fetching data baru dari network: ${url}`);
    return await this.executeNetworkFetch<T>(url);
  }

  private async executeNetworkFetch<T>(url: string): Promise<T> {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
    
    const data = await response.json();
    this.cache.set(url, {
      data,
      timestamp: Date.now(),
      etag: response.headers.get("ETag") || undefined,
    });
    return data;
  }

  private revalidateInBackground(url: string): void {
    // Jalankan tanpa mengunci main thread flow
    this.executeNetworkFetch(url).catch((err) => {
      console.error(`Background revalidation failed for ${url}:`, err);
    });
  }
}

Pelajaran Penting:

Pola Stale-While-Revalidate (SWR) yang dipopulerkan oleh Vercel, TanStack Query, atau HTTP RFC 5861 sebenarnya hanyalah logika state machine sederhana:

  1. Jika data ada dan masih segar (fresh), pakai.
  2. Jika data ada tapi sudah usang (stale), tetap tampilkan ke pengguna demi performa instan, tapi diam-diam kirim request ke server untuk memperbarui data (background revalidation).
  3. Jika data sama sekali tidak ada, tunggu sampai network request selesai.

Kalau kamu paham konsep ini beserta cara kerja HTTP Header seperti Cache-Control: s-maxage=3600, stale-while-revalidate=59, kamu tidak akan pernah bingung lagi menyetel konfigurasi cache di serverless CDN seperti Cloudflare Workers, Fastly, AWS CloudFront, maupun Next.js Server Actions.


Mengapa Engineer dengan Fundamental Kuat Selalu Menang di Industri?

Di pasar kerja teknologi yang semakin kompetitif, kemampuan menghafal sintaksis framework sudah terkomoditisasi—bahkan AI seperti Claude dan ChatGPT bisa menulis kode React boilerplate dalam hitungan detik.

Yang tidak bisa digantikan oleh AI dan tidak bisa ditiru oleh developer karbitan adalah kemampuan arsitektural dan intuisi pemecahan masalah (deep root-cause analysis):

  1. Kemampuan Navigasi Breaking Changes: Ketika Next.js beralih dari Pages Router ke App Router, developer yang paham NodeJS Streams, AsyncLocalStorage, dan HTTP Streaming transisi dengan mulus. Sementara developer yang cuma hafal getServerSideProps panik luar biasa.
  2. Efisiensi Debugging Tingkat Tinggi: Mereka tidak menghabiskan 3 hari menatap log console. Mereka tahu cara menganalisis memory heap dump untuk menemukan detached DOM nodes yang menyebabkan browser crash.
  3. Kemampuan Cross-Ecosystem: Bagi engineer yang menguasai konsep Data Structures, Async Concurrency, dan Software Design Patterns, berpindah dari TypeScript (Node.js) ke Go, Rust, atau Elixir bukanlah hal yang menakutkan. Sintaksis boleh beda, tapi masalah konkurensi, memory, dan networking-nya tetap sama.

Action Items: Kurikulum 4 Minggu Menempa Fundamental

Kalau kamu merasa selama ini terlalu bergantung pada framework dan ingin membangun pondasi teknis yang solid, berikut adalah langkah konkret yang bisa kamu eksekusi mulai hari ini:

Minggu 1: JavaScript Engine & Memory Model

  • [ ] Pelajari bagaimana JavaScript Engine (seperti V8) mengompilasi kode: JIT Compiler, Ignition Bytecode, Turbofan optimization.
  • [ ] Kuasai konsep Memory Management: Call Stack vs Heap Memory, Garbage Collection (Mark-and-Sweep algorithm), dan cara menghindari Memory Leak pada Closures.
  • [ ] Eksperimen: Buka Chrome DevTools -> tab Memory, rekam heap snapshot, dan buat skenario buatan yang sengaja menyebabkan memory leak (misal: event listener yang lupa di-removeEventListener).

Minggu 2: Browser Internals & Web Platform APIs

  • [ ] Pahami secara mendalam Critical Rendering Path: DOM Tree + CSSOM -> Render Tree -> Layout (Reflow) -> Paint -> Compositing.
  • [ ] Pelajari Web APIs modern yang sering diabstraksi framework: IntersectionObserver, ResizeObserver, MutationObserver, dan AbortController (untuk cancel network request).
  • [ ] Latihan: Bangun fitur Infinite Scroll dan Lazy Loading Image menggunakan TypeScript murni dan IntersectionObserver tanpa dependensi luar.

Minggu 3: Asynchronous Deep-Dive & Protocols

  • [ ] Kuasai siklus hidup JavaScript Event Loop: Callstack, Microtask Queue (Promise, queueMicrotask), Macrotask Queue (setTimeout, I/O, UI events).
  • [ ] Pahami protokol jaringan: Perbedaan HTTP/1.1 (Head-of-Line blocking), HTTP/2 (Multiplexing, Header Compression), dan HTTP/3 (QUIC/UDP).
  • [ ] Latihan: Buat promise pool / concurrency limiter murni yang membatasi maksimal 3 network request berjalan bersamaan (concurrency queue).

Minggu 4: Build Your Own Mini-Framework

  • [ ] Bangun router SPA (Single Page Application) sendiri menggunakan HTML5 History API (pushState, popstate).
  • [ ] Terapkan reactive state system (seperti yang kita bangun di Bedah Kasus 1) yang langsung mengikat data ke DOM (document.querySelector).
  • [ ] Satukan semuanya menjadi aplikasi Todo List atau Dashboard sederhana murni tanpa bundler (cukup ES Modules di browser).

Kesimpulan

Framework datang dan pergi. Hari ini React dan Next.js, kemarin Angular dan Backbone, besok mungkin sesuatu yang sama sekali baru.

Jangan gadaikan nilai karirmu hanya sebagai "tukang ketik framework X". Jadilah seorang Software Engineer sejati yang memahami platform web secara menyeluruh. Framework adalah alat kerja yang kamu kendalikan, bukan tuan yang mendikte batas kemampuanmu.

Investasikan 80% waktu belajarmu pada hal-hal yang bertahan puluhan tahun: protokol web, arsitektur sistem, runtime internals, dan algoritma dasar. Sisanya? 20% waktu sudah lebih dari cukup untuk membaca dokumentasi framework apa pun yang sedang populer.

Butuh penyesuaian khusus untuk project Anda?

Jika situasi operasional atau arsitektur sistem bisnis Anda membutuhkan solusi kustom, diskusikan langsung bersama tim engineer kami. Anda juga bisa melihat rincian layanan vour.dev atau menghitung estimasi biaya project lebih dulu.

Mulai Project