# Plugin Claude Code untuk Next.js: Skill, Hook, Subagent

> Sebuah agent menaruh use client di baris pertama file route, dan seluruh halaman ikut terkirim ke browser. Type checker tidak bilang apa-apa. Ini plugin yang saya tulis untuk stack itu, lengkap — manifest, skill, hook, subagent — dan mana dari keempatnya yang saya hapus.

URL: https://www.matthewswong.com/id/blog/claude-code-plugin-nextjs-stack-toolkit/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-09-08
- Updated: 2026-09-08
- Category: Web Dev

> **Key takeaway:** Plugin Claude Code untuk codebase Next.js App Router butuh empat file: satu skill yang mengajarkan batas server dan client, satu hooks.json yang menjalankan type check sekali per tool batch, satu subagent dengan tools terbatas untuk review route metadata, dan satu manifest plugin.json. Pilih event hook-nya dengan hati-hati, atau tim akan mematikannya.

Waktu saya memperbaiki hal yang sama untuk keempat kalinya dalam satu minggu, saya berhenti memperbaikinya dan mulai menuliskannya. Sebuah agent menaruh directive use client di baris pertama sebuah file route supaya satu tombol bisa menyimpan state, dan setiap module yang di-import route itu ikut terkirim ke browser. Type checker diam. Build lolos. Tidak ada yang rusak selain bundle-nya.

Ini plugin yang saya tulis untuk stack itu, utuh: satu manifest, satu skill, satu hook, dan satu subagent, di atas App Router plus TypeScript plus Tailwind. Saya memakainya di situs ini, sebuah repository yang direktori blog component-nya saja berisi lebih dari 550 file. Bagian menariknya bukan keempat file itu, melainkan kenyataan bahwa hanya satu dari empat yang selamat satu minggu kerja tanpa diubah.

## Empat hal yang salah dilakukan agent di sini, tanpa diminta

Tidak ada yang aneh di antara keempatnya, dan justru itu masalahnya. Masing-masing adalah langkah yang wajar di codebase React lain, jadi agent memilihnya tanpa perlu diminta.

- Agent menandai file route sebagai file client. Satu halaman butuh satu kontrol interaktif, directive-nya mendarat di baris pertama page.tsx, dan karena use client mendeklarasikan batas module graph, semua yang di-import file itu dan setiap component yang dirender langsung ikut masuk ke client bundle.
- Agent melempar function melewati batas itu. Sebuah Server Component menyerahkan callback onSelect ke leaf client. Dokumentasi Next.js tegas soal ini: props yang dikirim ke Client Component harus serialisable menurut React, dan function bukan salah satunya.
- Agent memanggil useState atau useEffect di file tanpa directive. File itu Server Component secara default, jadi tidak ada hook yang bisa dipanggil dan tidak ada client runtime yang menjalankannya.
- Agent meng-import Link dari next/link. Benar di hampir semua repository Next.js lain, salah di repository ini, karena di sini wrapper navigation dari next-intl yang membawa prefix locale.

Tidak satu pun dari keempatnya berupa type error, dan pembedaan itu akhirnya menentukan seluruh desain plugin-nya. Aturan yang bisa diputuskan mesin tempatnya di hook. Aturan yang butuh pertimbangan harus sudah ada di context agent sebelum file ditulis, dan itu berarti skill.

## Skill yang memikul batas itu, karena tsc tidak akan

Next.js sebenarnya punya checker untuk persis kesalahan-kesalahan ini, dan agent tidak pernah menjalankannya. Dokumentasi konfigurasi TypeScript menjelaskan sebuah TypeScript plugin khusus yang memperingatkan nilai route segment config yang tidak valid, memastikan directive use client dipakai dengan benar, dan memastikan client hook seperti useState hanya muncul di Client Component. Statusnya IDE plugin: kamu mengaktifkannya di VS Code dengan memilih workspace TypeScript version, dan ia bekerja lewat language server editor sambil kamu mengetik. Agent yang menulis file lewat tool call tidak punya editor maupun language server.

Halaman yang sama mengarahkan kamu ke tsc --noEmit untuk pemeriksaan sebelum build, dan mencatat bahwa type checking lewat CLI mencetak diagnostics tsc apa adanya tanpa penulisan ulang khas Next.js. Jadi pemeriksaan yang seharusnya menangkap keempat kesalahan itu justru yang tidak ada di loop yang benar-benar dijalankan agent. Aturannya masuk ke skill. Skill adalah markdown yang dimuat model ketika ia menilai description-nya relevan, jadi description-nya ditulis sebagai daftar pemicu, bukan ringkasan: bagian itulah yang menetap di context, sementara isinya baru datang saat dibutuhkan.

Pasangan wrong-and-right bekerja lebih keras daripada semua prosa di sekitarnya. Aturan yang dinyatakan abstrak akan diparafrase jadi sesuatu yang sekadar mirip; aturan yang dinyatakan sebagai dua file bernama, salah satunya diberi label salah, akan disalin. Sebagian besar nilai file itu ada di dua belas baris tersebut.

## Isi plugin-nya, dalam bentuk file

Empat komponen, empat direktori, dan satu aturan soal letaknya: hanya plugin.json yang tinggal di dalam .claude-plugin. Direktori skills, agents, dan hooks semuanya berada di root plugin, dan dokumentasi plugin menyebut menaruhnya di dalam .claude-plugin sebagai kesalahan yang umum. Memang begitu, dan saya yang pertama melakukannya.

name adalah satu-satunya field wajib di manifest, dan sekaligus namespace-nya: skill-nya dipanggil dengan nama ber-namespace, nextjs-stack-toolkit:app-router-boundaries, dan subagent-nya diselesaikan dengan cara yang sama. version adalah tombol update. Kalau diisi, orang yang meng-install baru menerima perubahan ketika kamu menaikkan angkanya, jadi field itu bukan hiasan. Plugin yang mengirim tepat satu skill boleh menaruh SKILL.md di root plugin dan melewatkan manifest sama sekali; punya saya berisi empat komponen, jadi ia memakai layout lengkap.

## Hook yang jalan di setiap write adalah hook yang dimatikan orang

Satu putaran penuh tsc --noEmit di repository ini butuh sekitar tiga setengah detik dalam kondisi warm. Saya mengukur tiga run berurutan pada 3,83, 3,47, dan 3,52 detik wall clock, di proyek yang saat saya ukur berisi 556 file di direktori blog component-nya saja. Harga itu wajar kalau dibayar sekali. Tidak wajar kalau dibayar per write, dan per write itulah biaya PostToolUse: referensi hooks menyatakan PostToolUse jalan sekali per tool, yang berarti ia jalan bersamaan ketika Claude memanggil tool secara paralel. Lima Edit paralel berarti lima proses tsc di satu tree pada waktu yang sama.

Saya mencoba perbaikan yang paling jelas lebih dulu, dan hasilnya malah lebih buruk. Menambahkan --incremental dengan tsBuildInfoFile butuh 35,2 detik pada run pertama, karena ia harus menulis build info seluruh proyek, lalu 4,19 detik pada setiap run berikutnya: lebih lambat daripada perintah biasa, karena sekarang ia juga membaca dan menulis ulang file itu. Tidak ada keuntungan incremental yang murah di tsconfig satu proyek yang bukan composite.

Yang benar-benar memperbaikinya adalah nama event-nya, bukan perintahnya. PostToolBatch jalan sekali setelah semua tool call dalam satu batch selesai, sebelum request berikutnya dikirim ke model, dan berbeda dari PostToolUse ia termasuk event yang bisa memblokir: exit code 2 menghentikan agentic loop. Satu run per batch, bukan satu per file, di titik terakhir yang masih bisa menghemat satu turn.

Entry kedua adalah yang murah, dan ia bertahan berkat field if, yang memuat satu permission rule dan dievaluasi sebelum script-nya di-spawn, jadi satu batch berisi panggilan Bash tidak membayar biaya spawn proses sama sekali. Entry itu juga yang akhirnya saya buang, karena alasan yang baru muncul setelah dipakai.

> Field if memuat tepat satu permission rule. Tidak ada and, tidak ada or, dan tidak ada sintaks daftar, jadi menutup .ts sekaligus .tsx berarti dua hook handler yang menunjuk ke perintah yang sama, bukan satu pola yang pintar. Perhatikan juga kedalamannya: pola satu segmen seperti Edit(app/**) hanya cocok untuk direktori app di working directory, jadi sebuah package di monorepo butuh Edit(**/app/**).

*Band hook adalah satu-satunya yang benar-benar ditunggu orang, dan itulah sebabnya nama event-nya lebih penting daripada perintah yang dijalankannya.*

## Subagent untuk urusan metadata, dan field yang diabaikannya

Pekerjaan berulang yang ingin saya keluarkan dari thread utama adalah review SEO tingkat route: buka satu route, telusuri layout induknya, periksa generateMetadata, lapor. Pekerjaannya banyak membaca, mekanis, dan butuh context yang bersih, karena melakukannya di session utama berarti menarik setengah lusin file layout ke dalam window yang sedang dipakai untuk hal lain. Subagent mendapat context window sendiri, dan menurut referensi subagents ia hanya menerima system prompt di file-nya sendiri plus detail environment dasar, bukan system prompt Claude Code. tools bersifat allowlist, jadi subagent yang dibatasi seperti ini tidak bisa mengedit file, menulis file, atau menjangkau tool MCP apa pun.

Field skills adalah bagian yang tidak akan saya lepas. Ia memuat isi lengkap skill batas itu, bukan hanya description-nya, sehingga reviewer membaca sebuah route dengan aturan yang sama seperti yang dipakai penulisnya. Tanpa itu reviewer menciptakan standarnya sendiri, dan dua komponen dalam satu plugin yang berbeda pendapat soal konvensi lebih buruk daripada tidak mengirim keduanya.

> Tiga field frontmatter subagent diabaikan untuk subagent yang datang dari plugin: permissionMode, mcpServers, dan hooks. Definisi yang terasa aman ter-sandbox selama ia tinggal di direktori agents sebuah proyek berubah sifat begitu file yang sama dikirim di dalam plugin, karena permission mode-nya dibuang dan hanya daftar tools yang tersisa sebagai pembatas. Batasi dengan tools dan disallowedTools, dan anggap permission mode session-lah yang berlaku.

## Mana dari keempatnya yang layak bertahan

Setelah beberapa minggu, keputusannya bukan yang saya perkirakan saat menulis manifest. Komponen yang paling sering saya edit justru yang tidak berisi kode, dan komponen yang saya hapus justru yang paling terlihat seperti pekerjaan engineering.

| Komponen | Keputusan | Apa yang menentukannya |
| --- | --- | --- |
| Skill batas server dan client | Dipertahankan, dua kali ditulis ulang | Satu-satunya lapisan yang bisa mencegah, bukan cuma melaporkan |
| Type check di PostToolBatch | Dipertahankan, setelah ganti event | Tiga setengah detik sekali per batch tak terasa; per write terasa |
| Subagent metadata | Dipertahankan, dipakai dua kali seminggu | Tidak berbiaya saat idle, jadi frekuensi bukan ukuran yang tepat |
| Boundary lint di PostToolUse | Dibuang | Jalan di tengah edit, pada file yang sedang diperbaiki agent |

Yang saya buang punya tugas yang benar dengan waktu yang salah. Meng-grep file yang baru ditulis untuk mencari client hook di file tanpa directive adalah pemeriksaan yang benar, tapi agent yang sedang membuat leaf client sering menulis isinya dulu dan directive-nya kemudian, jadi handler-nya jalan pada file yang dua detik lagi sudah benar dan model menghabiskan satu turn membela pekerjaan yang sedang ia selesaikan. Semua yang ia tangkap juga tertangkap pemeriksaan tingkat batch, satu model call lebih lambat dan dengan seluruh batch terlihat. Pertanyaan di belakang keempat keputusan itu ternyata sama: di titik mana dalam loop ini hal tersebut masih bisa mengubah hasil?

*Baca panel ketiga dengan cermat: type check hanya membantu kalau kesalahannya kebetulan berupa type error, dan directive use client yang salah tempat bukan salah satunya.*

## Validasi dulu sebelum orang lain meng-install-nya

Plugin adalah dependency yang di-install orang lain, jadi hal terakhir yang ditulis bukan sebuah fitur. Empat langkah, dengan urutan ini, setiap kali ada yang berubah di dalamnya.

1. Muat untuk satu session tanpa meng-install apa pun: claude --plugin-dir ./nextjs-stack-toolkit. Flag ini menerima direktori atau arsip .zip, dan bisa diulang untuk memuat satu plugin bersama plugin yang menjadi dependency-nya.
2. Picu event setiap hook dengan sengaja, dengan meminta agent mengedit sebuah file TypeScript, lalu baca debug log-nya, yang mencatat hook mana yang cocok, bagaimana ia keluar, dan apa yang dicetaknya. Hook yang diam-diam tidak pernah cocok terlihat persis seperti hook yang lolos.
3. Jalankan claude plugin validate ./nextjs-stack-toolkit. Ia melaporkan field manifest yang tidak dikenal sebagai warning, bukan error, jadi tambahkan --strict kalau kamu ingin salah tulis nama field menggagalkan pemeriksaan.
4. Naikkan version di plugin.json. Kalau tidak, orang yang meng-install tetap memakai salinan yang sudah ia punya dan perbaikanmu tidak sampai ke siapa pun.

> Ada satu ketidaksimetrisan di loop pengembangan yang sebaiknya kamu tahu sebelum kehilangan satu sore karenanya. Perubahan pada SKILL.md sebuah skill langsung berlaku di session yang sedang jalan, tapi perubahan pada hooks, agents, dan konfigurasi MCP plugin tidak. Jalankan perintah reload-plugins setelah menyentuh yang tiga itu, atau sore itu habis untuk men-debug versi hook yang lama.

Aturan yang saya bawa dari sini: taruh sebuah aturan di hook hanya kalau mesin bisa memutuskannya, taruh di skill kalau hanya pertimbangan yang bisa, dan pilih event hook-nya dengan bertanya kapan jawabannya masih bisa mengubah sesuatu. Empat file, satu di antaranya sudah dihapus, dan bagian tersulitnya adalah nama event.

## Sumber

- Dokumentasi Claude Code — Plugins reference: skema manifest, layout direktori, dan variabel root plugin
- Dokumentasi Claude Code — Hooks: daftar event, PostToolUse versus PostToolBatch, field if, dan timeout
- Dokumentasi Claude Code — Subagents: field frontmatter, pembatasan tool, dan apa yang diabaikan subagent plugin
- Dokumentasi Next.js — Server and Client Components: batas use client dan props yang serialisable
- Dokumentasi Next.js — TypeScript: apa yang diperiksa IDE plugin, dan tsc --noEmit

## Source links

- https://code.claude.com/docs/en/plugins-reference
- https://code.claude.com/docs/en/hooks
- https://code.claude.com/docs/en/sub-agents
- https://nextjs.org/docs/app/getting-started/server-and-client-components
- https://nextjs.org/docs/app/api-reference/config/typescript

## FAQ

### Apa saja yang masuk ke dalam plugin Claude Code untuk proyek Next.js?

Empat hal sudah menutup hampir semua kebutuhan: satu skill di skills/ yang memuat konvensi App Router yang tidak bisa disimpulkan agent, satu hooks/hooks.json yang menjalankan type check setelah write, satu subagent di agents/ untuk satu pekerjaan review yang sempit dan berulang, serta manifest .claude-plugin/plugin.json. Hanya plugin.json yang tinggal di dalam .claude-plugin; direktori lainnya berada di root plugin. Field name di manifest adalah satu-satunya yang wajib, dan ia sekaligus menjadi namespace bagi skill dan subagent yang kamu kirim.

### Hook type check sebaiknya pakai PostToolUse atau PostToolBatch?

PostToolBatch, hampir pada semua kasus. PostToolUse jalan sekali per tool dan jalan bersamaan ketika Claude memanggil tool secara paralel, jadi satu batch berisi lima edit memicu lima proses type check di tree yang sama. PostToolBatch jalan sekali setelah seluruh batch selesai dan sebelum model call berikutnya, dan ia termasuk event yang exit code 2-nya benar-benar memblokir agentic loop.

### Kenapa tsc --noEmit tidak menangkap directive use client yang salah tempat?

Karena pemeriksaan itu ada di tool yang berbeda. Next.js menyertakan TypeScript plugin khusus yang memastikan directive use client dipakai dengan benar dan client hook hanya muncul di Client Component, tapi ia berjalan di editor melalui TypeScript language server. Menjalankan tsc dari CLI mencetak diagnostics TypeScript apa adanya tanpa pemeriksaan khas Next.js itu, jadi agent yang menulis file lewat tool call tidak pernah melihatnya. Itulah sebabnya aturan batas server dan client tempatnya di skill, bukan di hook.

### Bagaimana membatasi subagent yang dikirim di dalam plugin?

Pakai field tools sebagai allowlist, atau disallowedTools sebagai denylist, di frontmatter subagent-nya. Subagent yang daftar tools-nya hanya Read, Grep, dan Glob tidak bisa mengedit file, menulis file, atau menjangkau tool MCP apa pun. Perlu diingat bahwa permissionMode, mcpServers, dan hooks di frontmatter subagent diabaikan untuk subagent yang datang dari plugin, jadi definisi yang tampak ter-sandbox sebagai file proyek kehilangan permission mode-nya begitu dikirim di dalam plugin.

### Bagaimana menguji plugin Claude Code tanpa mem-publish-nya?

Jalankan Claude Code dengan claude --plugin-dir ./plugin-kamu, yang memuat plugin itu untuk session tersebut tanpa meng-install apa pun. Flag ini menerima direktori atau arsip .zip dan bisa diulang untuk beberapa plugin. Jalankan claude plugin validate ./plugin-kamu untuk memeriksa manifest-nya, tambahkan --strict kalau kamu ingin warning seperti nama field yang tidak dikenal ikut menggagalkan pemeriksaan, dan ingat bahwa perubahan skill berlaku langsung sementara perubahan hook dan subagent butuh reload plugin.

---

- HTML version: https://www.matthewswong.com/id/blog/claude-code-plugin-nextjs-stack-toolkit/
- English: https://www.matthewswong.com/en/blog/claude-code-plugin-nextjs-stack-toolkit/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
