diff --git a/templates/next-image/src/app/api/edit-image/route.ts b/templates/next-image/src/app/api/edit-image/route.ts index 11c52b38e..6743d651e 100644 --- a/templates/next-image/src/app/api/edit-image/route.ts +++ b/templates/next-image/src/app/api/edit-image/route.ts @@ -17,13 +17,7 @@ const providers = { gemini: handleGoogleEdit, }; -export const config = { - api: { - bodyParser: { - sizeLimit: '4mb', - }, - }, -}; +export const maxDuration = 60; export async function POST(req: Request) { try { diff --git a/templates/next-image/src/app/api/generate-image/route.ts b/templates/next-image/src/app/api/generate-image/route.ts index 15bf30c3a..372ba0584 100644 --- a/templates/next-image/src/app/api/generate-image/route.ts +++ b/templates/next-image/src/app/api/generate-image/route.ts @@ -19,13 +19,7 @@ const providers = { gemini: handleGoogleGenerate, }; -export const config = { - api: { - bodyParser: { - sizeLimit: '4mb', - }, - }, -}; +export const maxDuration = 60; export async function POST(req: Request) { try { diff --git a/templates/next-image/src/components/image-generator.tsx b/templates/next-image/src/components/image-generator.tsx index e585d3cc5..b3f6c09d9 100644 --- a/templates/next-image/src/components/image-generator.tsx +++ b/templates/next-image/src/components/image-generator.tsx @@ -25,7 +25,7 @@ import { Button } from '@/components/ui/button'; import { X } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { fileToDataUrl } from '@/lib/image-utils'; +import { fileToDataUrl, resizeImageFile } from '@/lib/image-utils'; import type { EditImageRequest, GeneratedImage, @@ -219,12 +219,14 @@ export default function ImageGenerator() { try { const imageUrls = await Promise.all( imageFiles.map(async imageFile => { - // Convert blob URL to data URL for API + // Convert blob URL to resized data URL for API + // Resizing prevents 413 Request Entity Too Large errors const response = await fetch(imageFile.url); const blob = await response.blob(); - return await fileToDataUrl( - new File([blob], 'image', { type: imageFile.mediaType }) - ); + const file = new File([blob], 'image', { + type: imageFile.mediaType, + }); + return await resizeImageFile(file); }) ); diff --git a/templates/next-image/src/lib/image-utils.ts b/templates/next-image/src/lib/image-utils.ts index cefb219f3..84e814371 100644 --- a/templates/next-image/src/lib/image-utils.ts +++ b/templates/next-image/src/lib/image-utils.ts @@ -4,6 +4,12 @@ * Simple, clean API with just data URLs. No complex conversions. */ +/** + * Maximum dimension (width or height) for resized images before upload. + * Prevents 413 Request Entity Too Large errors on serverless functions. + */ +const MAX_RESIZE_DIMENSION = 1024; + /** * Converts a File to a data URL */ @@ -16,6 +22,64 @@ export async function fileToDataUrl(file: File): Promise { }); } +/** + * Resizes an image File to fit within MAX_RESIZE_DIMENSION while preserving aspect ratio. + * Returns a JPEG data URL for smaller payload sizes. + * If the image is already small enough, returns the original data URL unchanged. + */ +export async function resizeImageFile(file: File): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + const url = URL.createObjectURL(file); + + img.onload = () => { + URL.revokeObjectURL(url); + + let { width, height } = img; + + // If already within limits, just convert to data URL as-is + if (width <= MAX_RESIZE_DIMENSION && height <= MAX_RESIZE_DIMENSION) { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + ctx.drawImage(img, 0, 0); + resolve(canvas.toDataURL('image/jpeg', 0.9)); + return; + } + + // Scale down preserving aspect ratio + const scale = MAX_RESIZE_DIMENSION / Math.max(width, height); + width = Math.round(width * scale); + height = Math.round(height * scale); + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + reject(new Error('Failed to get canvas context')); + return; + } + + ctx.drawImage(img, 0, 0, width, height); + // Use JPEG at 90% quality for smaller payload + resolve(canvas.toDataURL('image/jpeg', 0.9)); + }; + + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Failed to load image for resizing')); + }; + + img.src = url; + }); +} + /** * Converts a data URL to a File object */