---
title: 'Next.js'
description: "Pelajari cara menggabungkan JSONEval-Rs dengan Next.js baik menggunakan App router maupun Pages router, serta cara menangani lingkungan server dan klien secara komprehensif."
navigation:
  icon: 'i-simple-icons-nextdotjs'
seo:
  title: 'Next.js'
  description: "Pelajari cara menggabungkan JSONEval-Rs dengan Next.js baik menggunakan App router maupun Pages router, serta cara menangani lingkungan server dan klien secara komprehensif."
---

# Menggunakan JSONEval-Rs di Next.js

Next.js memiliki tantangan tersendiri untuk WebAssembly karena kode bisa berjalan di Sisi Server (backend Node.js) atau Sisi Klien (backend Browser/Bundler).

Panduan ini akan menunjukkan cara menangani kedua lingkungan tersebut dengan penggunaan tingkat lanjut.

## 1. Instalasi

Kamu butuh package utama, target Node backend untuk SSR, dan target WASM klien yang bergantung pada bundler Next.js milikmu.

**Untuk Webpack (Bawaan Next.js):**
```bash
yarn add @json-eval-rs/webcore @json-eval-rs/node @json-eval-rs/bundler
```

**Untuk Turbopack (`next dev --turbo`):**
Karena Turbopack belum mendukung fitur async WebAssembly seperti pada Webpack, gunakan target `vanilla`:
```bash
yarn add @json-eval-rs/webcore @json-eval-rs/node @json-eval-rs/vanilla
```

## Dukungan Penuh BigInt

JavaScript seringkali kehilangan presisi pada angka bulat (integer) yang melewati batas aman `Number.MAX_SAFE_INTEGER` (2^53 - 1). JSONEval-Rs mengatasi hal ini dengan mengimplementasikan modul `json-with-bigint` secara internal pada seluruh instalasi Web dan React Native. Ini menjamin angka 64-bit raksasa (`i64`) milik engine tidak akan dikonversi menjadi tipe Float kacau, melainkan dipasangkan akurat dengan tipe standar `BigInt` JavaScript.

## 2. Penggunaan Sisi Server (Node.js)

Jika kamu mengevaluasi logika langsung di dalam Next.js App Router Server Component, Server Action, atau Route Handler (`route.ts`), artinya kamu berjalan di lingkungan Node.js.

Cukup impor backend `@json-eval-rs/node`.

### Validasi Standar (Route Handler)

```tsx
// app/api/validate/route.ts
import { JSONEval } from '@json-eval-rs/webcore';
import '@json-eval-rs/node'; // Backend Node otomatis terdaftar
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const data = await request.json();
  const schema = { /* schema kamu */ };

  const evalInstance = new JSONEval({ schema: JSON.stringify(schema) });

  try {
    const result = await evalInstance.validate({ data: JSON.stringify(data) });
    return NextResponse.json({ success: !result.has_error, errors: result.errors });
  } finally {
    // Sangat penting untuk manajemen memori di server
    evalInstance.free();
  }
}
```

### Mengambil Schema Tervalidasi Berdasarkan Jalur (Path)

Sering kali, server hanya perlu mengekstrak spesifik target properti dan menghitung suatu nilai berdasarkan aturan tertentu tanpa harus mengembalikan seluruh struktur objek yang berat ke klien.

```tsx
// app/api/calculate/route.ts
import { JSONEval } from '@json-eval-rs/webcore';
import '@json-eval-rs/node';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const data = await request.json();
  const schema = { /* schema kamu */ };

  const evalInstance = new JSONEval({ schema: JSON.stringify(schema) });

  try {
    await evalInstance.evaluate({ data: JSON.stringify(data) });
    
    // Tarik nilai dari node spesifik pada skema
    const totalNode = await evalInstance.getEvaluatedSchemaByPath({ 
      path: "#/properties/total_amount", 
      skipLayout: true 
    });
    
    return NextResponse.json({ total: totalNode.value });
  } finally {
    evalInstance.free();
  }
}
```

## 3. Penggunaan Sisi Klien (Browser)

Saat mengevaluasi logika di browser menggunakan Next.js, kamu wajib memakai direktif `"use client"`, mengimpor modul WebAssembly secara dinamis, dan mencegah SSR mencoba memuat modul browser tersebut.

### Validator Klien Dasar

```tsx
// app/components/ClientValidator.tsx
"use client";

import { useEffect, useState } from 'react';
import { JSONEval } from '@json-eval-rs/webcore';

export function ClientValidator() {
  const [evaluator, setEvaluator] = useState<JSONEval | null>(null);
  
  useEffect(() => {
    let instance: JSONEval | null = null;
    let isMounted = true;

    async function load() {
      // Impor dinamis untuk wasm
      // Catatan: Jika memakai Turbopack, gunakan impor '@json-eval-rs/vanilla'
      const wasmModule = await import('@json-eval-rs/bundler');
      
      if (!isMounted) return;

      instance = new JSONEval({
        schema: JSON.stringify({
          type: "object",
          properties: {
             score: { rules: { minValue: { value: 10, message: "Skor terlalu rendah" } } }
          }
        }),
        wasmModule
      });

      setEvaluator(instance);
    }
    
    load();

    return () => {
      isMounted = false;
      if (instance) instance.free();
    };
  }, []);

  const handleTest = async () => {
    if (!evaluator) return;
    const result = await evaluator.validate({ data: JSON.stringify({ score: 5 }) });
    console.log(result);
  };

  return (
    <div>
      <button onClick={handleTest} disabled={!evaluator}>
        Jalankan Validasi
      </button>
    </div>
  );
}
```

### Evaluasi Subform

Untuk UI yang kompleks seperti array yang dinamis (misal daftar pesanan, tabel barang), memvalidasi seluruh form pada setiap inputan (keystroke) akan sangat membebani. JSONEval-Rs memungkinkan evaluasi sebagian per subform dengan konteks terisolasi sehingga lebih cepat dan hemat performa.

```tsx
// app/components/OrderForm.tsx
"use client";
import { useState } from 'react';
import { JSONEval } from '@json-eval-rs/webcore';

export function OrderForm() {
  const [evaluator, setEvaluator] = useState<JSONEval | null>(null);

  // ...(Logika inisialisasi sama seperti di atas)

  const handleItemChange = async (index: number, itemData: any) => {
    if (!evaluator) return;

    const subformPath = `#/properties/items`;

    // Pastikan dukungan batas engine subform ada
    if (await evaluator.hasSubform(subformPath)) {
      
      // 1. Evaluasi khusus hanya pada item array yang diubah
      await evaluator.evaluateSubform({
        subformPath: subformPath,
        data: JSON.stringify(itemData),
        paths: [] // Opsional: batasan evaluasi field (path spesifik)
      });

      // 2. Ambil nilai spesifik dari kalkulasi form di skopnya saja
      const itemCalculation = await evaluator.getEvaluatedSchemaByPathSubform({
        subformPath: subformPath,
        path: "#/properties/item_total" // Path milik skema di dalam item array
      });

      console.log(`Total item baru untuk baris ${index}:`, itemCalculation.value);
    }
  };

  return (
      <div>{/* Komponen Form UI Kamu */}</div>
  );
}
```

### Menggunakan Web Workers untuk UI Non-Blocking

Saat membangun aplikasi kompleks, mengevaluasi schema yang sangat besar di main thread bisa mengakibatkan UI tersendat (stuttering). Memindahkan beban eksekusi WebAssembly sepenuhnya ke dalam Web Worker memastikan thread render React tetap lancar tanpa hambatan.

Di bawah ini adalah arsitektur komprehensif yang memanfaatkan antrean pesan berbasis Promise secara universal melalui Custom React Hook. Perlu dicatat bahwa Web Workers lebih pas menggunakan target `@json-eval-rs/vanilla` untuk mengontrol inisialisasi WASM murni dengan lebih baik.

**1. Buat File Worker (`app/workers/jsoneval.worker.ts`)**
```typescript
import type { JSONEval } from "@json-eval-rs/vanilla";

let engineInstance: JSONEval | null = null;

self.addEventListener("message", async (event: MessageEvent) => {
  const { id, type, payload } = event.data;

  try {
    if (type === "INIT") {
      const { JSONEval, version } = await import("@json-eval-rs/vanilla");
      
      await JSONEval.initWasm(
        new URL("@json-eval-rs/vanilla/pkg/json_eval_rs_bg.wasm", import.meta.url)
      );

      engineInstance = new JSONEval({
        schema: payload.schema,
        context: payload.context,
        data: payload.data,
      });

      await engineInstance.init();
      self.postMessage({ id, type: "INIT_SUCCESS" });
      return;
    }

    if (!engineInstance) throw new Error("Worker belum diinisialisasi");

    let result: any;
    switch (type) {
      case "VALIDATE":
        result = await engineInstance.validate(payload);
        self.postMessage({ id, type: "VALIDATE_SUCCESS", result });
        break;
      case "EVALUATE":
        await engineInstance.evaluateOnly(payload);
        result = await engineInstance.getEvaluatedSchemaWithoutParams({ skipLayout: false });
        self.postMessage({ id, type: "EVALUATE_SUCCESS", result });
        break;
      case "EVALUATE_DEPENDENTS":
        result = await engineInstance.evaluateDependents({
          ...payload, reEvaluate: true, includeSubforms: true 
        });
        self.postMessage({ id, type: "EVALUATE_DEPENDENTS_SUCCESS", result });
        break;
      case "GET_EVALUATED_SCHEMA_BY_PATH":
        result = await engineInstance.getEvaluatedSchemaByPath({ path: payload });
        self.postMessage({ id, type: "SUCCESS", result });
        break;
      case "EVALUATE_SUBFORM":
        result = await engineInstance.evaluateSubform(payload);
        self.postMessage({ id, type: "SUCCESS", result });
        break;
      case "GET_EVALUATED_SCHEMA_SUBFORM":
        result = await engineInstance.getEvaluatedSchemaWithoutParamsSubform(payload);
        self.postMessage({ id, type: "SUCCESS", result });
        break;
      case "RELOAD_SCHEMA":
        await engineInstance.reloadSchema(payload);
        self.postMessage({ id, type: "SUCCESS" });
        break;
      case "FREE":
        engineInstance.free();
        engineInstance = null;
        self.postMessage({ id, type: "FREE_SUCCESS" });
        break;
      default:
        throw new Error(`Tipe pesan tidak terdaftar: ${type}`);
    }
  } catch (error: any) {
    self.postMessage({ id, type: "ERROR", error: error.message });
  }
});
```

**2. Buat React Hook Berbasis Promise (`hooks/useJSONEvalWorker.ts`)**
```typescript
import { useEffect, useRef, useState, useCallback } from "react";

let messageId = 0;

export function useJSONEvalWorker({ schema, context, data }: any) {
  const workerRef = useRef<Worker | null>(null);
  const pendingRef = useRef<Map<number, { resolve: Function; reject: Function }>>(new Map());

  const [isReady, setIsReady] = useState(false);

  const sendMessage = useCallback((type: string, payload?: any): Promise<any> => {
    return new Promise((resolve, reject) => {
      if (!workerRef.current) return reject(new Error("Worker belum aktif"));

      const id = messageId++;
      pendingRef.current.set(id, { resolve, reject });
      workerRef.current.postMessage({ id, type, payload });

      // Opsional: Batas waktu pembersihan janji (promise) timeout bisa ditempatkan di sini
    });
  }, []);

  useEffect(() => {
    const worker = new Worker(new URL("../workers/jsoneval.worker.ts", import.meta.url));
    workerRef.current = worker;

    worker.addEventListener("message", (event) => {
      const { id, type, result, error } = event.data;
      const pending = pendingRef.current.get(id);
      
      if (!pending) return;
      pendingRef.current.delete(id);

      if (type === "ERROR") pending.reject(new Error(error));
      else pending.resolve(result);
    });

    // Inisialisasi engine di dalam worker
    sendMessage("INIT", {
      schema: JSON.stringify(schema),
      context: context ? JSON.stringify(context) : undefined,
      data: data ? JSON.stringify(data) : undefined,
    }).then(() => setIsReady(true)).catch(console.error);

    return () => {
      if (workerRef.current) {
        sendMessage("FREE").catch(console.error);
        workerRef.current.terminate();
      }
    };
  }, [schema, context, data, sendMessage]);

  // Buka API Eksekusi
  const validate = useCallback((data: any) => {
    return sendMessage("VALIDATE", { data: JSON.stringify(data) });
  }, [sendMessage]);

  const evaluateDependents = useCallback((changedPaths: string[], data: any) => {
    return sendMessage("EVALUATE_DEPENDENTS", { changedPaths, data: JSON.stringify(data) });
  }, [sendMessage]);

  const evaluateSubform = useCallback((subformPath: string, itemData: any) => {
    return sendMessage("EVALUATE_SUBFORM", { subformPath, data: JSON.stringify(itemData) });
  }, [sendMessage]);

  return { isReady, validate, evaluateDependents, evaluateSubform /* ... tambahkan sesuai butuh */ };
}
```

**3. Gunakan Hook di dalam Komponenmu**
```tsx
"use client";
import { useJSONEvalWorker } from '@/hooks/useJSONEvalWorker';
import { useState } from 'react';

export function WorkerForm() {
  const { isReady, validate, evaluateDependents } = useJSONEvalWorker({
    schema: { /* schema form kamu */ }
  });

  const handleTest = async () => {
    const result = await validate({ field: "value" });
    console.log("Hasil Validasi dari Worker:", result);
  };

  return <button disabled={!isReady} onClick={handleTest}>Validasi via Worker</button>;
}
```

## Konfigurasi Webpack Next.js yang Penting

Jika kamu menemui error resolusi modul terkait file WebAssembly di konsol browsermu, kamu mungkin perlu mengaktifkan `asyncWebAssembly` di file `next.config.js` atau `next.config.mjs`:

```javascript
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    config.experiments = { ...config.experiments, asyncWebAssembly: true };
    return config;
  },
};

export default nextConfig;
```
