
Bagaimana saya menskalakan blog Next.js dari 1.000 ke 12.000+ file MDX dengan menyelesaikan limit ukuran fungsi serverless Vercel dan membangun pipeline kompilasi konten paralel dengan sharding di GitHub Actions

Blog Next.js Anda berjalan dengan baik di 500 file MDX. Di 5.000 mulai melambat. Di 12.000 rusak total.
Vercel menabrak limit ukuran fungsi serverless 250 MB-nya. GitHub Actions runner OOM kill di 7 GB RAM. Satu kali next build memakan waktu lebih dari satu jam. Pipeline MDX yang tadinya bekerja dengan indah untuk situs portofolio menjadi bottleneck terbesar untuk platform konten.
Ini adalah cerita bagaimana saya menskalakan dari 1.000 ke 12.583 file MDX di stack Next.js + Velite -- menabrak dua dinding berbeda dan menyelesaikan masing-masing dengan cara berbeda. Jika Anda mengelola situs Next.js yang kontennya padat dan melihat waktu build meningkat, artikel ini membahas teknik tepat yang mengembalikan saya ke deploy di bawah 5 menit.
Situs saya awalnya adalah blog Next.js standar dengan segelintir postingan MDX. Seiring waktu, berkembang menjadi platform pembelajaran bilingual dengan 472 series mencakup dua locale (Inggris dan Indonesia). Berikut isi direktori konten saat ini:
| Tipe Konten | File |
|---|---|
| Episode series | 11.750 |
| Metadata series | 472 |
| Postingan blog | 285 |
| Proyek | 43 |
| Halaman legal | 4 |
| Halaman guide | 29 |
| Total file MDX | 12.583 |
Total konten di disk: ~120 MB sumber MDX, ~150 MB aset thumbnail.
Scaling terjadi dalam dua fase, masing-masing menabrak limit infrastruktur yang berbeda:
Memahami fase Anda menentukan solusi yang dibutuhkan.
Vercel mendeploy Next.js sebagai fungsi serverless. Setiap route menjadi satu bundle fungsi. Vercel menggunakan @vercel/nft (Node File Traces) untuk menentukan file apa yang dibutuhkan setiap fungsi, lalu menggabungkannya.
Masalahnya adalah nft bersifat konservatif. Ketika melihat dynamic import atau akses filesystem yang tidak bisa di-resolve secara statis, ia cenderung menyertakan lebih banyak file daripada yang kurang. Di skala kecil ini tidak masalah. Di 12.000 file MDX, ini menjadi bencana.
Velite mengkompilasi semua file MDX menjadi satu file JSON gabungan. Untuk situs saya, seriesEpisodes.json berukuran 152 MB -- 124 MB konten MDX yang sudah dikompilasi ditambah 28 MB metadata.
Karena file ini di-import oleh aplikasi, nft men-trace-nya ke setiap fungsi serverless yang secara transitif bergantung padanya. Setiap bundle fungsi tiba-tiba membawa 152 MB data konten, meskipun fungsi tersebut hanya membutuhkan segelintir series.
public/ Tree 906 MBLebih buruk lagi, saya memiliki logika resolusi thumbnail yang berjalan saat request time:
// Pemeriksaan runtime ini adalah bencana untuk serverless bundling
const hasThumb = fs.existsSync(
`${process.cwd()}/public/series/${slug}.jpg`
)nft Vercel tidak bisa resolve path dinamis yang dibangun dari variabel runtime. Ketika menemukan fs.existsSync() dengan path yang dikomputasi, ia menyerah mencoba menentukan file mana yang benar-benar dibutuhkan dan menggabungkan seluruh direktori public/ -- semua 906 MB-nya -- ke setiap fungsi.
Ditambah dengan JSON 152 MB, bundle fungsi individual melampaui limit ukuran fungsi serverless Vercel 250 MB. Deploy mulai gagal.
1. Bagi JSON monolitik menjadi file per-series
Daripada satu seriesEpisodes.json sebesar 152 MB, saya menulis script post-compile yang memecahnya menjadi ~472 file individual:
public/static/series-episodes/en/learn-docker.json (50-300 KB masing-masing)
public/static/series-episodes/en/learn-kubernetes.json
public/static/series-episodes/id/learn-docker.json
...File gabungan dihapus setelah dipecah. Karena nft hanya men-trace file yang benar-benar di-import, dan file JSON individual dimuat on-demand per halaman series, setiap bundle fungsi hanya membawa konten yang benar-benar dibutuhkan.
2. Strip body dari collection JSON
Collection posts dan projects dipecah menjadi dua tier:
public/static/ -- hanya dimuat di halaman detail post/project individualIni mengurangi data collection yang di-trace dari 152 MB menjadi 2 MB.
3. Resolusi thumbnail saat build time
// Sebelum: fs.existsSync runtime membuat nft bundle public/ tree
const hasThumb = fs.existsSync(`${process.cwd()}/public/series/${slug}.jpg`)
// Sesudah: di-resolve selama Velite computedFields, hasil di-bake ke JSON
// nft tidak pernah menyentuh filesystem saat runtimeDengan meng-resolve keberadaan thumbnail selama langkah kompilasi Velite (di computedFields), path yang sudah di-resolve ditulis langsung ke output JSON. Fungsi serverless tidak perlu lagi mengecek filesystem.
Bundle fungsi serverless turun dari 250+ MB menjadi di bawah 15 MB. Deploy Vercel berhasil kembali.
Tapi ini hanya menyelesaikan setengah masalah. Konten bisa di-deploy, tapi mengompilasinya menjadi semakin lambat dan tidak bisa diandalkan setiap minggu.
Setelah menyelesaikan masalah ukuran bundle, next build mulai crash dengan error berbeda: JavaScript heap out of memory selama fase "Collecting page data".
Penyebabnya adalah seriesEpisodes.json yang sama -- sekarang sudah 173 MB seiring pertumbuhan konten. Setiap halaman detail episode secara statis meng-import collection penuh di module scope, dan generateStaticParams() mengembalikan semua 3.400+ episode. Selama build, V8 workers meng-parse seluruh JSON untuk setiap route, menghabiskan heap memory.
Fix-nya adalah arsitektur tiga bagian:
1. Collection meta ringan
Collection seriesEpisodesMeta terpisah mengkompilasi file MDX yang sama tapi tanpa field body -- tanpa kompilasi MDX, tanpa Shiki highlighting. Output: ~4 MB alih-alih 173 MB. Halaman list, generateStaticParams, dan generateMetadata semuanya membaca dari collection meta.
2. Asset CDN per-series
Daripada satu JSON gabungan, script post-compile membagi episode menjadi satu file per-series (public/static/series-episodes/{lang}/{series}.json, ~2.5 MB masing-masing). Halaman detail episode hanya mengambil file series-nya sendiri via fetch() dengan ISR caching 24 jam. File gabungan dihapus setelah dibagi -- tidak pernah masuk ke module graph Next.js.
3. Dynamic rendering untuk detail routes
Detail routes (blog, projects, episodes) menggunakan force-dynamic alih-alih static pre-rendering. Ini mengeliminasi fase "Collecting page data" yang rentan OOM untuk 3.400+ route, mengurangi route yang di-pre-render dari ~3.500 menjadi ~540, dan menjaga bundle halaman episode tetap di ~96 KB tanpa data episode yang di-trace ke fungsi serverless manapun.
Dengan masalah ukuran bundle terselesaikan, masalah berikutnya menjadi mustahil untuk diabaikan: mengkompilasi 12.000 file MDX melalui pipeline Velite itu lambat dan boros memori.
Setiap file MDX melewati pipeline pemrosesan yang berat:
one-light + one-dark-pro)Dampak terukur di GitHub Actions runner 7 GB:
| Metrik | Nilai |
|---|---|
| Kompilasi tunggal (semua 12.583 file) | 30-60 menit |
| Peak RAM per 2.000 file MDX | ~3.5 GB RSS |
| Threshold OOM kill | ~5.5 GB V8 heap |
| Limit build Vercel | 45 menit |
Di 12.000+ file dalam satu proses Velite, V8 heap tumbuh melampaui RAM yang tersedia dan proses di-OOM kill. Bahkan di mesin dengan RAM cukup, waktu kompilasi melampaui limit 45 menit Vercel.
Velite (dan sebagian besar kompilator MDX) tidak memiliki kompilasi incremental yang sesungguhnya. Setiap build memproses semua file dari awal. Tidak ada state intermediate yang di-cache, tidak ada output diferensial, tidak ada rebuild parsial. Satu typo fix di satu file MDX memicu recompile penuh dari semua 12.000 file.
Ini adalah limit fundamental dari model kompilasi MDX. Plugin chain rehype dan remark beroperasi pada full document tree, dan Shiki perlu memuat definisi grammar dan theme terlepas dari berapa file yang benar-benar berubah.
Jawabannya adalah horizontal scaling. Daripada mengkompilasi semua 12.000 file dalam satu proses, pecah menjadi kelompok lebih kecil (shards), kompilasi setiap shard secara independen, jalankan secara paralel di runner CI terpisah, lalu gabungkan outputnya.
Script kustom (generate-shards.ts) menganalisis direktori konten dan menghasilkan rencana shard:
Algoritmanya sederhana:
en-1, en-2, id-1, dst.Keputusan desain kunci:
Granularitas per-series -- Satu series tidak pernah dipecah lintas shard. Jika learn-graphql punya 51 episode, semua 51 tetap di shard yang sama. Ini menjaga merge tetap sederhana dan menghindari dependensi lintas shard.
Enforcement batas locale -- Shard tidak pernah mencampur konten Inggris dan Indonesia. Ini di-enforce oleh deteksi perubahan locale di planner. Artinya en-1 hanya berisi series Inggris, dan id-1 hanya berisi series Indonesia.
Penugasan konten bersama -- Postingan blog, proyek, guide, dan halaman legal hanya ditugaskan ke shard pertama setiap locale. Blog dan proyek memiliki subdirektori per-locale (en/, id/), jadi terpecah secara natural. Konten hanya-Inggris (guide/, legal/) masuk ke en-1.
Jumlah shard dinamis -- Jumlah shard tidak di-hardcode. Algoritma menghasilkan shard sebanyak yang dibutuhkan. Hari ini 7; jika konten tumbuh ke 20.000 file, otomatis menjadi ~10.
| Shard | Series | File | Konten |
|---|---|---|---|
en-1 | 82 | ~2.000 | Series Inggris + blog EN + proyek EN + guide + legal |
en-2 | 82 | ~2.000 | Series Inggris |
en-3 | 15 | ~400 | Series Inggris |
id-1 | 78 | ~2.000 | Series Indonesia + blog ID + proyek ID |
id-2 | 78 | ~2.000 | Series Indonesia |
id-3 | 80 | ~2.000 | Series Indonesia |
id-4 | 57 | ~1.400 | Series Indonesia |
Setiap shard adalah package npm yang mandiri di bawah packages/content-{locale}-{N}/. Menggunakan symlink untuk mereferensikan direktori konten kanonik, menghindari duplikasi di disk:
packages/content-en-1/
content/
blog/posts/en -> symlink ke content/blog/posts/en
projects/en -> symlink ke content/projects/en
guide -> symlink ke content/guide
legal -> symlink ke content/legal
series/en/
learn-docker -> symlink ke content/series/en/learn-docker
learn-docker.mdx -> symlink ke content/series/en/learn-docker.mdx
velite.config.ts # satu baris import shared config
package.json
shard.tsConfig Velite untuk setiap shard hanya satu baris:
import { createShardConfig } from "../shared/velite/config"
export default createShardConfig("content")Seluruh konfigurasi Velite -- collections, MDX plugins, Shiki themes, rehype plugins -- terpusat di packages/shared/. Mengubah pipeline MDX memperbarui semua shard secara bersamaan.
Sebelum kompilasi dimulai, setup job memvalidasi integritas shard:
bun script/generate-shards.ts --checkIni menangkap tiga kategori masalah:
content/ tapi tidak ditugaskan ke shard manapunJika pemeriksaan gagal, seluruh pipeline dibatalkan. Tidak ada runner yang terbuang untuk layout yang rusak. Ini adalah safeguard kritis -- tanpanya, Anda mungkin mengkompilasi 7 shard hanya untuk menemukan saat merge bahwa 300 item terduplikasi.
Matrix dihitung dari rencana shard secara runtime, bukan di-hardcode di workflow:
- name: Compute shard matrix
id: matrix
run: |
MATRIX=$(bun script/generate-shards.ts --json | jq -c '[.[].name]')
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"Ketika konten tumbuh dan algoritma menghasilkan shard ke-8, matrix secara otomatis memasukkannya. Tidak perlu edit file workflow. Ini mengeliminasi seluruh kategori kesalahan maintenance.
Setiap matrix runner mengkompilasi satu shard. Prosesnya:
Setiap shard diberi limit V8 heap 5 GB di runner 7 GB, menyisakan ruang untuk OS dan proses lain:
env:
NODE_OPTIONS: "--max-old-space-size=5120"Waktu kompilasi per-shard:
| Shard | File | Waktu |
|---|---|---|
| en-1 | 2.000 | ~8 menit |
| en-2 | 2.000 | ~8,5 menit |
| en-3 | 400 | ~1,5 menit |
| id-1 | 2.000 | ~8,5 menit |
| id-2 | 2.000 | ~9,5 menit |
| id-3 | 2.000 | ~8 menit |
| id-4 | 1.400 | ~4 menit |
Wall-clock time dengan 7 runner paralel: ~10 menit (terhambat oleh shard terlambat). Bandingkan dengan 30-60 menit untuk kompilasi monolitik di satu runner.
Vercel tidak pernah menjalankan Velite. Build script mengunduh shard yang sudah dikompilasi dari R2 dan menjalankan next build:
// vercel-build.ts (simplified)
async function main() {
// 1. Download shard yang sudah dikompilasi dari R2
execSync("bun script/download-shards.ts")
// 2. next build (data konten sudah dikompilasi)
execSync("bun run build:next")
// 3. Sync static assets ke R2
execSync("bun run assets:sync:static")
}Download script menggabungkan semua output shard ke satu direktori .velite/ dengan deduplikasi ketat. Setiap item di-key oleh kombinasi locale, series slug, dan episode slug. Item duplikat lintas shard menyebabkan error keras untuk mencegah korupsi data:
Seluruh Vercel build (download + merge + next build) selesai di bawah 5 menit.
Saya memilih Cloudflare R2 untuk penyimpanan artifact shard karena tiga alasan:
Tanpa egress fees -- Berbeda dengan AWS S3, R2 tidak mengenakan biaya egress. Ini penting ketika Vercel mengunduh 7 archive shard di setiap deploy. Di skala besar, biaya egress di S3 akan bertambah cepat.
API S3-compatible -- R2 bekerja dengan rclone out of the box menggunakan protocol S3. Tidak ada vendor lock-in, tidak ada SDK proprietary. Perintah rclone yang sama bekerja dengan S3, MinIO, atau storage S3-compatible lainnya.
Edge network -- Edge network Cloudflare menyediakan download latency rendah dari region Vercel manapun. Archive shard biasanya 50-200 MB masing-masing, dan terdownload dalam hitungan detik.
devvnull-content-shards/
content-shards/
en-1/
a1b2c3d4e5f6.tar.gz # archive shard yang sudah dikompilasi
en-2/
f6e5d4c3b2a1.tar.gz
...
content-builds/
{hash}.tar.gz # build monolitik (release workflow)
static/
production/ # static assets per-environment
preview/Setiap archive shard dinamai berdasarkan content hash-nya -- SHA-256 dari semua file sumber MDX dan config Velite dalam shard tersebut. Jika konten tidak berubah, hash-nya sama, dan runner CI melewati kompilasi sepenuhnya.
// build-shard.ts
const hash = computeContentHash(shardDir)
const cacheKey = `${SHARD_NAME}-${hash}.tar.gz`
// Cek local cache dulu, lalu R2
if (existsSync(`.cache/shards/${cacheKey}`)) {
console.log("cache hit, skipping compile")
return
}Dengan 7 shards, mengedit postingan blog di en-1 hanya mengkompilasi shard tersebut (~8 menit). 6 shard lainnya adalah cache hit dan melewati kompilasi sepenuhnya.
Direktori konten menggunakan struktur berbasis locale yang terpeta secara natural ke sharding:
content/
series/
en/ # 179 series Inggris
learn-docker/
episode-0.mdx
episode-1.mdx
...
learn-docker.mdx # Metadata series
id/ # 293 series Indonesia
learn-docker/
...
blog/
posts/
en/ # 120 postingan blog Inggris
id/ # 165 postingan blog Indonesia
projects/
en/ # 24 proyek Inggris
id/ # 19 proyek Indonesia
legal/ # 4 halaman legal hanya-Inggris
guide/ # 29 halaman dokumentasi hanya-InggrisShard planner menggunakan struktur locale ini untuk menegaskan batas. Series diurutkan berdasarkan locale dulu, lalu alphabetically. Ketika planner menemukan perubahan locale, ia finalisasi shard saat ini dan mulai yang baru. Ini memastikan tidak ada shard yang pernah mencampur konten Inggris dan Indonesia.
Setiap shard hanya mensymlink konten yang ditugaskan kembali ke direktori konten kanonik content/. Ini menghindari duplikasi 120 MB file MDX di 7 package shard di disk.
fs.existsSync() Runtime di Fungsi ServerlessIni adalah kesalahan paling menyakitkan. Satu baris kode yang mengecek keberadaan thumbnail saat request time membuat file tracer Vercel menggabungkan 906 MB ke setiap fungsi.
Pelajarannya: di lingkungan serverless, jangan pernah mengakses filesystem saat runtime dengan path dinamis. Resolve semuanya saat build time dan bake hasilnya ke data file Anda.
Membiarkan Velite menghasilkan satu seriesEpisodes.json sebesar 152 MB tampak praktis. Ini adalah bencana untuk deployment serverless. Setiap fungsi mendapat full 152 MB tergabung di dalamnya.
Fix-nya sederhana: bagi menjadi file kecil per-series dan hapus file gabungan. Tradeoff-nya adalah lebih banyak HTTP request saat runtime (satu per series), tapi setiap request kecil dan bisa di-cache.
Ketika saya pertama kali mengimplementasikan sharding, saya mensymlink seluruh direktori content/blog/ ke kedua shard en-1 dan id-1. Ini menghasilkan 332 item duplikat lintas shard. Langkah merge mendeteksi duplikasi dan gagal fatal, yang menyebabkan Vercel build retry dalam loop tak terbatas.
Fix-nya: bagi konten bersama berdasarkan locale. en-1 mendapat blog/posts/en/ dan projects/en/. id-1 mendapat blog/posts/id/ dan projects/id/. Konten hanya-Inggris (guide/, legal/) masuk ke en-1 saja.
Awalnya saya memelihara penugasan shard secara manual. Ketika series baru ditambahkan ke content/, tidak selalu ditugaskan ke shard. Konten diam-diam hilang dari situs yang di-deploy.
Fix-nya: generate-shards.ts --check sebagai CI gate. Ia exit non-zero pada series orphan, duplikasi, atau symlink rusak. Ini berjalan sebelum kompilasi dimulai, jadi layout yang rusak langsung terdeteksi.
Awalnya saya mencantumkan nama shard secara manual di matrix workflow. Setiap kali jumlah shard berubah, saya harus edit file workflow. Ini rentan kesalahan dan mengalahkan tujuan automasi.
Fix-nya: hitung matrix secara dinamis dari output --json. Workflow tidak pernah perlu update manual ketika konten tumbuh.
Caching berbasis content hash berarti shard yang tidak berubah melewati kompilasi sepenuhnya. Dengan 7 shards, sebagian besar push hanya mengkompilasi 1-2 shard. Shard lainnya adalah cache hit. Ini adalah kemenangan terbesar untuk velocity development sehari-hari.
fail-fast: falsestrategy:
fail-fast: falseJika satu shard gagal, biarkan yang lain selesai. Ini memberikan hasil parsial dan memudahkan debugging. Anda tahu persis shard mana yang rusak dan bisa menginspeksi outputnya tanpa menjalankan ulang seluruh pipeline.
env:
NODE_OPTIONS: "--max-old-space-size=5120"GitHub Actions ubuntu-latest runner memiliki 7 GB RAM. Mengatur limit V8 heap 5 GB menyisakan ruang untuk OS dan proses lain. Tanpa ini, Node.js mungkin mengalokasikan lebih banyak memori daripada yang bisa ditangani runner, menyebabkan OOM kill.
Semua package shard import dari packages/shared/velite/config. Satu sumber kebenaran untuk collections, plugins, dan output configuration. Ketika Anda upgrade Shiki atau menambahkan rehype plugin baru, Anda ubah satu file dan semua shard langsung memperbarui.
Kompilasi Velite (pemrosesan konten) dan next build (kompilasi aplikasi) adalah langkah terpisah. Pemisahan inilah yang memungkinkan seluruh arsitektur. Kompilasi konten di runner CI yang kuat dengan 7 GB RAM dan tanpa limit waktu build. Build aplikasi di lingkungan Vercel yang terbatas di mana next build selesai dalam hitungan menit karena data konten sudah dikompilasi.
Bahkan dengan sharding, kompilasi Velite penuh dari 12.000+ file memakan waktu 30-60 menit. Cache content-aware mengeliminasi kompilasi yang tidak perlu sepenuhnya.
Script build menghitung hash SHA-256 dari semua file content/**, velite.config.ts, plugin velite/**, dan bun.lock. Pada cache hit, direktori .velite/ dan public/static/ yang sudah dikompilasi dipulihkan dan Velite tidak pernah dipanggil. Pada cache miss, Velite berjalan dan output di-snapshot untuk waktu berikutnya.
Dalam praktiknya: cold build 859 detik (14:21) turun menjadi cache hit 0,2 detik. Mengedit postingan blog di en-1 hanya mengkompilasi shard tersebut; 6 shard lainnya adalah cache hit dan melewati kompilasi sepenuhnya. Di Vercel, Turborepo dengan remote caching menyediakan manfaat yang sama -- konten yang tidak berubah dipulihkan dari cache dalam waktu kurang dari satu detik.
Pengembangan lokal dengan pipeline Shiki + twoslash penuh lambat karena Velite men-tokenize ~13.000 blok kode setiap cold start. Config dev-only menggantikan rehype-pretty-code dengan plugin passthrough ringan yang mempertahankan struktur kode (layout, atribut data, code groups) tapi melewati syntax highlighting sepenuhnya.
Dengan config ini, edit satu file dalam watch mode rebuild dalam ~5 detik alih-alih menjalankan ulang pipeline penuh. Build produksi mempertahankan Shiki highlighting lengkap. Trade-off-nya adalah preview lokal menampilkan kode tanpa warna token, tapi layout dan strukturnya identik.
Sebelum memilih sharding, saya menguji runner GitHub Actions self-hosted menggunakan Multipass di ThinkPad T14 Gen 2i (Intel i7-1185G7, 32 GB RAM). VM menjalankan Ubuntu 24.04 dengan 4 vCPU dan 23 GB RAM -- cukup headroom untuk kompilasi full tree Velite.
Hasilnya mengonfirmasi kurva scaling:
| Runner | RAM | Kompilasi (11k file) | Hasil |
|---|---|---|---|
| GitHub-hosted (ubuntu-latest) | 7 GB | OOM kill | Tidak bisa mengkompilasi full tree |
| Self-hosted VM (Multipass) | 23 GB | ~65 menit | Berhasil, tapi lambat |
| Self-hosted VM (8 vCPU) | 23 GB | ~40 menit | Lebih cepat, masih viable |
| Shard paralel (7 runner) | 7 GB masing-masing | ~10 menit wall-clock | Solusi yang dipilih |
Pendekatan self-hosted bekerja untuk set konten yang lebih kecil atau server build khusus, tapi membutuhkan mesin yang menyala selama CI. Persyaratan RAM berskala linear: Velite menahan seluruh AST MDX + token Shiki di memory sebelum menulis output, pada sekitar 1 MB RSS per file. Kompilasi full tree mencapai peak 18,7 GB RSS.
Pelajaran kunci: NODE_OPTIONS membantu untuk V8 heap tapi tidak untuk total RSS. Mengatur --max-old-space-size=5120 membatasi heap JavaScript, tapi memory native (Shiki grammars, AST, tokenization) mendorong RSS jauh lebih tinggi. Swap tersedia tapi membunuh performa -- kompilasi 3-5x lebih lambat ketika RSS melebihi RAM fisik.
Sharding adalah pilihan pragmatis: menggunakan 7 runner GitHub-hosted yang sama yang sudah tersedia, mengkompilasi secara paralel, dan cache R2 berarti konten yang tidak berubah melewati kompilasi sepenuhnya.
Limit serverless 250 MB Vercel itu nyata. JSON konten monolitik dan akses filesystem runtime akan meledakkannya. Bagi konten menjadi file kecil dan resolve semuanya saat build time.
Kompilasi MDX tidak scale secara vertikal. Di ~2.000 file per proses, V8 heap pressure menjadi bottleneck. Horizontal scaling melalui sharding adalah solusi pragmatis.
Masalah OOM episode terpisah dari masalah ukuran bundle. Setelah membagi JSON monolitik, collection 173 MB masih bisa OOM build saat di-import di module scope. Gunakan collection meta-only untuk list/params, file per-series yang dilayani CDN untuk body, dan dynamic rendering untuk detail routes.
Content-aware caching mengeliminasi kompilasi yang tidak perlu. Cache content-hash berarti shard yang tidak berubah melewati Velite sepenuhnya -- cold build turun dari 14 menit menjadi 0,2 detik pada cache hit.
Pipeline dev yang murah menjaga iterasi tetap cepat. Menggantikan Shiki dengan plugin passthrough selama pengembangan berarti edit satu file rebuild dalam ~5 detik alih-alih menjalankan ulang pipeline penuh.
Arsitekturnya: Konten di git, shard planner, kompilasi CI paralel, R2 artifact storage, Vercel download dan merge, deploy cepat.
Automasi mencegah drift. CI gate --check dan dynamic matrix berarti maintenance shard zero-touch. Tambah konten, push, langsung jalan.
Jika Anda mengalami limit Vercel build, mulai dengan fix body-splitting dan resolusi thumbnail sebelum melompat ke sharding. Jika Anda butuh pipeline sharding penuh, teknik dalam artikel ini sudah teruji di 12.000+ file MDX. Mulai dari fix terkecil yang menangani bottleneck Anda saat ini dan tambahkan kompleksitas hanya ketika dibutuhkan.


