Recipe
File Upload Patterns
Production-ready patterns for handling file uploads in Next.js App Router with Meridian.
Server Action Upload
// app/actions/upload.ts
'use server'
import { writeFile } from 'fs/promises'
import { join } from 'path'
export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const path = join(process.cwd(), 'uploads', file.name)
await writeFile(path, buffer)
return { success: true, name: file.name }
}Client Component
'use client'
import { uploadFile } from '@/actions/upload'
export function UploadForm() {
return (
<form action={uploadFile}>
<input type="file" name="file"
className="text-sm text-gray-300
file:mr-4 file:py-2 file:px-4
file:rounded file:border-0
file:bg-[#8B5CF6] file:text-white
hover:file:bg-[#F472B6]
file:transition-colors" />
<button type="submit"
className="ml-4 px-4 py-2 bg-[#8B5CF6]
rounded text-sm hover:bg-[#F472B6]
transition-colors">
Upload
</button>
</form>
)
}Validation & Limits
- ▸Check
file.sizebefore processing — reject payloads over your limit - ▸Validate MIME type via
file.type— never trust the extension alone - ▸Sanitize filenames: strip path separators, limit length, reject null bytes
- ▸Stream large files to object storage (S3/R2) instead of buffering in memory
Edge Notes
Server Actions have a 4.5 MB body limit on Vercel. For larger files, use a direct upload endpoint with export const runtime = 'nodejs' or presigned URLs to bypass the edge runtime entirely.
Always set maxDuration in your route segment config when processing uploads on Hobby plans to avoid 10-second timeouts.