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