Page builder tidak harus mengompilasi semua block ke dalam satu bundle aplikasi permanen. Core dapat tetap stabil sementara plugin yang dikirim secara terpisah mendaftarkan tipe block baru, menyediakan control editor, dan menentukan bagaimana block tersebut tampil di website publik.

Batas yang paling penting bukan React itu sendiri. Yang menentukan adalah kontrak antara core builder dan setiap block. Setelah kontrak itu jelas, editor dapat tetap menjadi satu aplikasi React, sementara block, CSS, dan JavaScript frontend opsional hanya dimuat saat memang dibutuhkan.

Builder menyimpan struktur halaman, bukan source code

Halaman lebih mudah diperluas jika database menyimpan data block, bukan JSX yang sudah dihasilkan atau source yang terikat ke framework tertentu.

{
  "blocks": [
    {
      "id": "b1",
      "type": "core/heading",
      "props": { "text": "New collection" }
    },
    {
      "id": "b2",
      "type": "acme/slider",
      "props": { "slides": [1, 2, 3] }
    }
  ]
}

Field type menjadi lookup key. Builder tidak perlu mengetahui implementasi acme/slider ketika halaman disimpan. Builder hanya membutuhkan registry yang dapat menemukan tipe tersebut saat nanti dirender.

Dengan begitu, konten yang tersimpan tidak bergantung pada susunan bundle saat ini, dan instalasi plugin menjadi urusan runtime, bukan alasan untuk rebuild core.

Block mendaftarkan dirinya ke core

Core menyediakan API registrasi yang kecil:

const blocks = new Map();

export function registerBlock(definition) {
  blocks.set(definition.name, definition);
}

export function getBlock(name) {
  return blocks.get(name);
}

Plugin kemudian dapat mendaftarkan block:

registerBlock({
  name: 'acme/slider',
  editor: SliderEditor,
  frontend: SliderFrontend,
  dependencies: ['swiper'],
  style: '/plugins/acme-slider/style.css'
});

Editor React merender editor. Renderer publik memakai frontend, atau renderer server yang setara, sesuai model deployment. Core mengatur orkestrasi; plugin mengatur perilaku yang khusus untuk block tersebut.

Memasang block tidak perlu rebuild builder

Plugin dapat membawa manifest yang menunjuk ke asset hasil build:

{
  "name": "acme/slider",
  "editor": "/plugins/acme-slider/editor.js",
  "frontend": "/plugins/acme-slider/frontend.js",
  "style": "/plugins/acme-slider/style.css",
  "dependencies": ["swiper"]
}

Saat plugin diaktifkan, backend menambahkan manifest itu ke kumpulan block aktif. Aplikasi admin dapat memuat module editor melalui dynamic import(), lalu module tersebut memanggil registerBlock().

Hanya plugin itu sendiri yang perlu pernah di-build dari TypeScript, JSX, atau format source lain. Memasangnya tidak seharusnya memaksa page-builder core dibangun ulang.

Block yang hanya berbasis schema bisa dibuat lebih ringan lagi. Heading, spacer, atau card sederhana dapat dideskripsikan lewat metadata dan dirender oleh generic component tanpa module JavaScript khusus.

Library bersama membutuhkan dependency manager

Block yang memakai Swiper menunjukkan salah satu failure mode yang umum. Jika lima plugin slider masing-masing membawa salinan Swiper sendiri, halaman dapat mengunduh kode dan CSS yang sama berulang kali. Versi yang berbeda juga dapat menghasilkan perilaku yang tidak kompatibel.

Block sebaiknya hanya mendeklarasikan dependency, sementara core menentukan cara memuatnya:

registerBlock({
  name: 'acme/slider',
  dependencies: ['swiper'],
  frontend: SliderFrontend
});

Dependency manager dapat menyimpan module yang sudah dimuat:

const loaded = new Map();

async function loadDependency(name) {
  if (loaded.has(name)) return loaded.get(name);

  const module = await dependencyLoaders[name]();
  loaded.set(name, module);
  return module;
}

Library-nya dibagi, tetapi instance component tidak. Tiga block slider seharusnya membuat tiga instance Swiper, masing-masing terikat ke elemen DOM miliknya sendiri dan dihancurkan ketika block tersebut di-unmount.

Aturan yang sama berlaku untuk React. Editor biasanya memakai satu runtime React, sementara setiap block tetap menjadi component biasa di dalam aplikasi tersebut, bukan membuat React root sendiri.

Tailwind adalah build dependency, bukan shared browser runtime

Tailwind perlu diperlakukan berbeda dari Swiper. Swiper adalah JavaScript yang berjalan di browser; Tailwind biasanya menghasilkan CSS pada saat build.

Plugin dapat memakai Tailwind selama development dan hanya mengirim CSS hasil akhirnya:

plugin source
    -> Tailwind build
    -> style.css
    -> browser

Plugin sebaiknya tidak membawa global reset miliknya sendiri. Selector block juga perlu diberi namespace, atau utility diberi prefix unik jika CSS hasil generate berpotensi bertabrakan dengan theme website atau plugin lain.

Contohnya:

.acme-slider__slide {
  border-radius: 0.75rem;
}

lebih aman dibanding selector global seperti:

.slide {
  border-radius: 0.75rem;
}

CSS Modules, generated class name, atau Shadow DOM dapat memberi isolasi yang lebih kuat, tetapi Shadow DOM juga mengubah cara theme inheritance dan global typography menjangkau block. Untuk page builder, namespaced CSS biasanya merupakan titik awal yang lebih praktis sebelum memakai isolasi penuh.

Live editing dan public rendering adalah dua urusan berbeda

Interface admin dapat tetap sepenuhnya React meskipun website publik tidak mengirim runtime builder.

Arsitektur editor yang praktis:

React admin
   -> settings and block state
   -> iframe canvas
   -> live block renderer

iframe menjaga CSS admin agar tidak bercampur dengan CSS halaman dan membuat preview berjalan dalam lingkungan dokumen yang lebih dekat dengan website publik. Admin dapat mengirim perubahan block ke canvas, lalu canvas merender ulang hanya block yang terdampak.

Hasilnya adalah feedback langsung untuk padding, text, color, image, dan setting lain tanpa membuat frontend publik menjadi aplikasi React client-side yang besar.

Frontend hanya membutuhkan renderer block yang ada di halaman

Misalkan sebuah halaman berisi heading, slider, dan button. Tidak ada alasan untuk memuat plugin gallery, chart, map, atau form pada request tersebut.

Renderer dapat memindai data halaman yang tersimpan, menemukan tiga tipe block yang dipakai, lalu mengumpulkan asset yang diperlukan:

page JSON
   -> heading
   -> slider
   -> button
   -> collect renderers
   -> collect CSS
   -> collect interactive JS

Block statis dapat menjadi HTML dan CSS saja. Block interaktif dapat menambahkan JavaScript secara selektif.

Di titik ini frontend HTML-first seperti Astro cocok digunakan: halaman dapat dirender di server, sementara block interaktif menjadi island yang hanya di-hydrate saat dibutuhkan. Next.js juga tetap masuk akal jika produk publik memang sengaja dibangun sebagai aplikasi React. Kontrak plugin lebih penting daripada memaksa satu framework menangani semua layer.

Output frontend harus menjadi bagian dari kontrak plugin

Preview editor tidak boleh menjadi satu-satunya implementasi sebuah block. Setiap block yang dapat dipasang harus memiliki jalur rendering publik yang jelas.

Jalur tersebut dapat berupa beberapa bentuk:

  • server renderer yang menghasilkan HTML;
  • framework component yang dirender frontend;
  • HTML statis ditambah CSS;
  • HTML statis ditambah client module kecil untuk interaksi.

Kontrak juga perlu menentukan kepemilikan asset dan lifecycle. Jika block me-mount library pihak ketiga, block tersebut juga harus membersihkan instance ketika selesai. Jika mendeklarasikan CSS, renderer harus memastikan CSS itu dimuat sekali. Jika dependency dipakai bersama, core harus mengontrol versi yang memenuhi plugin API.

Tanpa aturan tersebut, builder yang extensible perlahan berubah menjadi kumpulan script terpisah yang berebut global variable, selector, dan versi dependency.

Batas plugin yang stabil lebih penting daripada framework

React, Vue, dan Svelte sama-sama mampu dipakai untuk membuat visual builder yang kompleks. React menarik untuk ekosistem plugin besar karena component model dan ekosistem library di sekitarnya memudahkan pembuatan SDK yang bertahan lama, tetapi framework sendiri tidak menyelesaikan isolasi plugin.

Bagian desain yang harus stabil adalah batasnya:

page data
   -> block type
   -> registry
   -> dependency resolution
   -> editor renderer
   -> frontend renderer
   -> scoped assets

Jika batas ini stabil, block baru dapat dipasang tanpa compile ulang core, admin dapat langsung menampilkannya di live preview, dan halaman publik hanya memuat kode yang memang dibutuhkan oleh block yang digunakan.