starting → processing → succeeded (or failed / canceled) CODE_BLOCK: starting → processing → succeeded (or failed / canceled) CODE_BLOCK: starting → processing → succeeded (or failed / canceled) COMMAND_BLOCK: // Create the prediction const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, }); // Poll until done let result = prediction; while (result.status !== "succeeded" && result.status !== "failed") { await new Promise((r) => setTimeout(r, 1000)); result = await replicate.predictions.get(result.id); } COMMAND_BLOCK: // Create the prediction const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, }); // Poll until done let result = prediction; while (result.status !== "succeeded" && result.status !== "failed") { await new Promise((r) => setTimeout(r, 1000)); result = await replicate.predictions.get(result.id); } COMMAND_BLOCK: // Create the prediction const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, }); // Poll until done let result = prediction; while (result.status !== "succeeded" && result.status !== "failed") { await new Promise((r) => setTimeout(r, 1000)); result = await replicate.predictions.get(result.id); } CODE_BLOCK: const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, webhook: `${process.env.VERCEL_URL}/api/webhooks`, webhook_events_filter: ["completed"], // only fire when done }); CODE_BLOCK: const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, webhook: `${process.env.VERCEL_URL}/api/webhooks`, webhook_events_filter: ["completed"], // only fire when done }); CODE_BLOCK: const prediction = await replicate.predictions.create({ model: "owner/model-name", input: { image: imageUrl }, webhook: `${process.env.VERCEL_URL}/api/webhooks`, webhook_events_filter: ["completed"], // only fire when done }); CODE_BLOCK: https://yourapp.com/api/webhooks?userId=abc123&predictionType=watermark CODE_BLOCK: https://yourapp.com/api/webhooks?userId=abc123&predictionType=watermark CODE_BLOCK: https://yourapp.com/api/webhooks?userId=abc123&predictionType=watermark CODE_BLOCK: // Via the Replicate dashboard or API: // Create a deployment for your model with min_instances = 1 // This keeps the model warm 24/7 CODE_BLOCK: // Via the Replicate dashboard or API: // Create a deployment for your model with min_instances = 1 // This keeps the model warm 24/7 CODE_BLOCK: // Via the Replicate dashboard or API: // Create a deployment for your model with min_instances = 1 // This keeps the model warm 24/7 CODE_BLOCK: // Next.js API route export async function GET(request: Request) { const output = await replicate.run("owner/model", { input }); return new Response(output); // stream back to client } CODE_BLOCK: // Next.js API route export async function GET(request: Request) { const output = await replicate.run("owner/model", { input }); return new Response(output); // stream back to client } CODE_BLOCK: // Next.js API route export async function GET(request: Request) { const output = await replicate.run("owner/model", { input }); return new Response(output); // stream back to client } CODE_BLOCK: const output = await replicate.run("owner/model", { input }); const response = await fetch(output[0]); // download from Replicate const buffer = await response.arrayBuffer(); await supabase.storage.from("outputs").upload(`${userId}/${id}.png`, buffer); CODE_BLOCK: const output = await replicate.run("owner/model", { input }); const response = await fetch(output[0]); // download from Replicate const buffer = await response.arrayBuffer(); await supabase.storage.from("outputs").upload(`${userId}/${id}.png`, buffer); CODE_BLOCK: const output = await replicate.run("owner/model", { input }); const response = await fetch(output[0]); // download from Replicate const buffer = await response.arrayBuffer(); await supabase.storage.from("outputs").upload(`${userId}/${id}.png`, buffer); CODE_BLOCK: // next.config.ts const nextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "replicate.delivery", }, { protocol: "https", hostname: "*.replicate.delivery", }, ], }, }; CODE_BLOCK: // next.config.ts const nextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "replicate.delivery", }, { protocol: "https", hostname: "*.replicate.delivery", }, ], }, }; CODE_BLOCK: // next.config.ts const nextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "replicate.delivery", }, { protocol: "https", hostname: "*.replicate.delivery", }, ], }, }; COMMAND_BLOCK: try { const prediction = await replicate.predictions.create({ ... }); if (prediction?.error) { return NextResponse.json({ error: prediction.error }, { status: 500 }); } // poll with timeout safety let result = prediction; const deadline = Date.now() + 60_000; // 60s max wait while (result.status !== "succeeded" && result.status !== "failed") { if (Date.now() > deadline) { return NextResponse.json({ error: "Prediction timed out" }, { status: 504 }); } await new Promise((r) => setTimeout(r, 1500)); result = await replicate.predictions.get(result.id); } if (result.status === "failed") { return NextResponse.json({ error: "Model failed" }, { status: 500 }); } return NextResponse.json({ output: result.output }); } catch (err) { return NextResponse.json({ error: "Unexpected error" }, { status: 500 }); } COMMAND_BLOCK: try { const prediction = await replicate.predictions.create({ ... }); if (prediction?.error) { return NextResponse.json({ error: prediction.error }, { status: 500 }); } // poll with timeout safety let result = prediction; const deadline = Date.now() + 60_000; // 60s max wait while (result.status !== "succeeded" && result.status !== "failed") { if (Date.now() > deadline) { return NextResponse.json({ error: "Prediction timed out" }, { status: 504 }); } await new Promise((r) => setTimeout(r, 1500)); result = await replicate.predictions.get(result.id); } if (result.status === "failed") { return NextResponse.json({ error: "Model failed" }, { status: 500 }); } return NextResponse.json({ output: result.output }); } catch (err) { return NextResponse.json({ error: "Unexpected error" }, { status: 500 }); } COMMAND_BLOCK: try { const prediction = await replicate.predictions.create({ ... }); if (prediction?.error) { return NextResponse.json({ error: prediction.error }, { status: 500 }); } // poll with timeout safety let result = prediction; const deadline = Date.now() + 60_000; // 60s max wait while (result.status !== "succeeded" && result.status !== "failed") { if (Date.now() > deadline) { return NextResponse.json({ error: "Prediction timed out" }, { status: 504 }); } await new Promise((r) => setTimeout(r, 1500)); result = await replicate.predictions.get(result.id); } if (result.status === "failed") { return NextResponse.json({ error: "Model failed" }, { status: 500 }); } return NextResponse.json({ output: result.output }); } catch (err) { return NextResponse.json({ error: "Unexpected error" }, { status: 500 }); } - starting: model is booting (cold start happens here) - processing: predict() is actively running - succeeded: output is ready — but files are deleted after 1 hour
- Predictions take more than ~10-15 seconds
- You want to persist results to a database
- You're building background processing flows
- Network timeouts
- Model errors (bad input format, unsupported file type)
- Rate limits (429)
- Prediction timeouts (30 min hard cap)
- Create prediction: 600 requests/minute
- All other endpoints: 3000 requests/minute
- Frontend: Next.js + Tailwind CSS
- AI: Replicate (Qwen model)
- Hosting: Vercel
- Payments: Stripe (two credit tiers) - ~150 weekly organic users - $0 paid acquisition
- Zero infrastructure management
- Understand the prediction lifecycle — especially the 1-hour file expiry
- Use polling for short tasks, webhooks for long/background ones
- Use Deployments if cold start latency is a problem for your UX
- Save or stream outputs immediately after succeeded
- Add replicate.delivery to your Next.js image domains
- Set your own deadline — don't wait 30 minutes for a user-facing request
- Test multiple models before committing — quality varies significantly