Resep Integrasi

Integrasi Next.js App Router

Cara mengintegrasikan `@cloudizero/sdk` dengan Next.js 14, 15, dan 16 menggunakan React Server Actions dan Client Components.

Pola Unggah Server Action

Menggunakan Server Actions menjaga kunci API utama Anda tetap aman di sisi server sembari menyediakan API bertipe aman (type-safe) untuk formulir klien:

app/actions/upload.ts
// app/actions/upload.ts
"use server";

import { Cloudizero } from "@cloudizero/sdk";

const cz = new Cloudizero({
  apiKey: process.env.CLOUDIZERO_API_KEY!,
});

export async function uploadAvatar(formData: FormData) {
  const file = formData.get("avatar") as File;
  if (!file) throw new Error("No file provided");

  const buffer = Buffer.from(await file.arrayBuffer());

  const asset = await cz.storage.upload({
    file: buffer,
    fileName: file.name,
    projectId: process.env.CLOUDIZERO_PROJECT_ID!,
    mimeType: file.type,
  });

  return { success: true, fileId: asset.id, url: asset.url };
}

export async function replaceExistingAvatar(fileId: string, formData: FormData) {
  const file = formData.get("avatar") as File;
  if (!file) throw new Error("No replacement file provided");

  const buffer = Buffer.from(await file.arrayBuffer());

  // Menimpa biner di storage engine dengan mempertahankan fileId yang sama
  const updated = await cz.storage.replaceFile({
    fileId,
    file: buffer,
    fileName: file.name,
    mimeType: file.type,
  });

  return { success: true, fileId: updated.id, url: updated.url };
}

Komponen Formulir Klien

Kirimkan FormData langsung ke Server Action Anda dari formulir sisi klien:

app/components/avatar-uploader.tsx
// app/components/avatar-uploader.tsx
"use client";

import { useTransition } from "react";
import { uploadAvatar } from "@/app/actions/upload";

export function AvatarUploader() {
  const [isPending, startTransition] = useTransition();

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);

    startTransition(async () => {
      const res = await uploadAvatar(formData);
      alert("Uploaded! " + res.url);
    });
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input type="file" name="avatar" required />
      <button
        type="submit"
        disabled={isPending}
        className="px-4 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
      >
        {isPending ? "Uploading..." : "Upload Avatar"}
      </button>
    </form>
  );
}