---
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

Karena arsitektur Next.js mengeksekusi kode di node.js (Server) maupun Browser (Klien), target WebAssembly yang digunakan harus dibedakan berdasarkan environment di mana fungsi sedang dipanggil.

## 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 Berdasarkan Path (Jalur) Terdekat

Seringkali server cukup membutuhkan status kalkulasi sebuah node tanpa mempedulikan balikan JSON payload form secara seutuhnya.

```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>
  );
}
```

### Optimasi Target Subform

Mengevaluasi ulang root logic array tabel di tiap input keystroke React itu berat. Panggil target evaluator spesifik `evaluateSubform` untuk menghitung logic pointer child yang diketik saja untuk melewati bottleneck main-thread rendering.

```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>
  );
}
```

### Gunakan Web Worker untuk Performa Ekstrem

Render engine thread UI yang terblokir karena memproses mega-bytes logika UI dihindari dengan menembak object logic payload secara asinkron (lepas tangan) ke background worker.

**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;
```
