Skip to content

Next.js

Integrate PutPut with Next.js App Router using Server Actions. No SDK required — plain fetch calls.

1. Get a token

Store the token server-side or pass it to the client via an environment variable.

// .env.local
PUTPUT_TOKEN=pp_guest_...

2. Server Action for presign + confirm

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

const API = "https://putput.io/api/v1";
const TOKEN = process.env.PUTPUT_TOKEN!;

export async function presignUpload(filename: string, contentType: string, sizeBytes: number) {
  const res = await fetch(`${API}/upload/presign`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ filename, content_type: contentType, size_bytes: sizeBytes }),
  });
  return res.json();
}

export async function confirmUpload(uploadId: string) {
  const res = await fetch(`${API}/upload/confirm`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ upload_id: uploadId }),
  });
  return res.json();
}

3. Client upload component

// app/components/FileUpload.tsx
"use client";

import { presignUpload, confirmUpload } from "@/app/actions/upload";
import { useState } from "react";

export function FileUpload() {
  const [url, setUrl] = useState<string | null>(null);
  const [uploading, setUploading] = useState(false);

  async function handleFile(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);

    // 1. Presign
    const presign = await presignUpload(file.name, file.type, file.size);

    // 2. Upload directly to R2
    await fetch(presign.presigned_url, {
      method: "PUT",
      headers: { "Content-Type": file.type },
      body: file,
    });

    // 3. Confirm
    const result = await confirmUpload(presign.upload_id);
    setUrl(result.file.public_url);
    setUploading(false);
  }

  return (
    <div>
      <input type="file" onChange={handleFile} disabled={uploading} />
      {uploading && <p>Uploading...</p>}
      {url && <a href={url}>{url}</a>}
    </div>
  );
}
v0.4.77