Build an Imgur-style image hosting app with V0 using Next.js, Supabase Storage for uploads, and short-code shareable links. You'll create a drag-and-drop uploader, public gallery with masonry layout, image transformations, and album organization — all in about 1-2 hours without touching a terminal.
What you're building
Image hosting lets people upload, share, and organize images with minimal friction. Users get short shareable links, a public gallery, and on-the-fly resizing for embedding images at any size.
V0 generates the upload interface, gallery grid, and image viewer from prompts. Supabase Storage handles file uploads with automatic thumbnail generation via Image Transformations, while the database tracks metadata, short codes, and album organization.
The architecture uses Next.js App Router with an API route for upload processing, Server Components for the gallery and image pages, a client component for the drag-and-drop uploader, and Supabase Image Transformations for automatic thumbnail and resize operations.
Final result
An image hosting app with drag-and-drop upload, short-code shareable links, masonry gallery, on-the-fly resizing, albums, and a user dashboard.
Tech stack
Prerequisites
- A V0 account (Premium recommended for the multi-page app)
- A Supabase project with Storage enabled and Image Transformations turned on
- Basic understanding of image sharing (upload, share link, gallery)
- No advanced coding experience needed
Build steps
Set up the project and image storage schema
Open V0 and create a new project. Connect Supabase via the Connect panel. Create the database schema and configure the Storage bucket for images.
1// Paste this prompt into V0's AI chat:2// Build an image hosting app. Create a Supabase schema with:3// 1. images: id (uuid PK), user_id (uuid FK to auth.users nullable), filename (text), original_name (text), file_size (bigint), width (int), height (int), mime_type (text), storage_path (text), thumbnail_path (text), is_public (boolean default true), view_count (int default 0), short_code (text unique), created_at (timestamptz)4// 2. albums: id (uuid PK), user_id (uuid FK to auth.users), title (text), description (text), slug (text unique), is_public (boolean default true), created_at (timestamptz)5// 3. album_images: image_id (uuid FK to images), album_id (uuid FK to albums), position (int), PK(image_id, album_id)6// Create a PostgreSQL function generate_short_code() that creates a random 8-char alphanumeric string using encode(gen_random_bytes(6), 'base64') with retry on unique conflict7// Create a Supabase Storage bucket 'images' with 10MB file limit8// Add RLS: public images readable by all, authenticated users can upload and manage their own imagesPro tip: Enable Image Transformations in the Supabase Dashboard under Storage settings — this lets you generate thumbnails and resize images via URL query parameters without any extra code.
Expected result: Database schema created with images, albums, and album_images tables. Storage bucket configured with 10MB limit and Image Transformations enabled.
Build the drag-and-drop uploader
Create the upload page with a multi-file drag-and-drop zone, upload progress tracking, and privacy toggle. Files go to Supabase Storage via an API route that generates short codes.
1// Paste this prompt into V0's AI chat:2// Build an upload page at app/upload/page.tsx as a 'use client' component.3// Requirements:4// - Drag-and-drop zone that accepts multiple image files (PNG, JPG, GIF, WebP)5// - Click to browse fallback using Input type='file' with accept='image/*' multiple6// - Show file previews as thumbnails before upload7// - Switch toggle for public/private per image8// - Upload Progress bar per file showing percentage9// - On upload, call POST /api/upload with FormData containing:10// the file, is_public flag11// - The API route (app/api/upload/route.ts) should:12// Generate a short_code using the generate_short_code() PostgreSQL function13// Upload the original to Supabase Storage at images/{user_id}/{filename}14// Read image dimensions (use the file metadata or sharp if available)15// Insert row into images table with all metadata16// Return the short_code for the shareable link17// - After upload, show the shareable link /i/{short_code} with copy Button18// - Use Card to wrap the upload zone and Progress for upload statusExpected result: Users can drag and drop multiple images, see upload progress, set privacy, and receive shareable short-code links after upload completes.
Build the public gallery and image viewer
Create the homepage gallery showing public images in a masonry grid and the individual image page with metadata, sharing buttons, and direct link.
1// Paste this prompt into V0's AI chat:2// Build two pages:3// 1. app/page.tsx — public gallery:4// - Masonry grid layout using CSS columns (3 cols desktop, 2 tablet, 1 mobile)5// - Card for each image thumbnail (300px width via Supabase Image Transformations URL)6// - Hover overlay showing image dimensions and file size7// - Select for sort order: recent (default), most viewed8// - "Upload" Button as CTA9// - Infinite scroll or pagination with 24 images per page10//11// 2. app/i/[short_code]/page.tsx — image viewer:12// - Full-size image display13// - Metadata Card: original name, dimensions, file size Badge, upload date, view count14// - Share buttons: copy direct link, copy embed code (<img> tag), copy markdown15// - Dialog for image details with download Button16// - On-the-fly resize: links for common sizes (640px, 1024px, 1920px) using ?w= query param17// - Increment view_count via Server Action on page load18// - If image is private and viewer is not the owner, show 404Expected result: The homepage shows a masonry gallery of public images. Individual image pages display full-size images with metadata, share buttons, and resize links.
Build the on-the-fly resize API and albums
Create the image resize API that serves images at custom dimensions and the album system for organizing images.
1// Paste this prompt into V0's AI chat:2// Build image resize API and albums:3// 1. app/api/images/[short_code]/route.ts:4// - GET handler that reads ?w (width) and ?q (quality, default 75) query params5// - Looks up image by short_code6// - Returns a redirect to the Supabase Storage transform URL:7// {SUPABASE_URL}/storage/v1/render/image/public/images/{path}?width={w}&quality={q}8// - If no params, redirect to the original file URL9// - Cache headers: public, max-age=31536000 for resized images10//11// 2. app/album/[slug]/page.tsx — album view:12// - Album title, description, image count Badge13// - Image grid similar to gallery but filtered to album images ordered by position14// - Share Button for the album URL15//16// 3. app/dashboard/page.tsx — user dashboard:17// - Tabs: My Images (grid with delete Button per image), My Albums (list with edit/delete)18// - Storage usage Card showing total bytes used19// - "Create Album" Button opening Dialog with title Input, description Textarea, slug Input20// - Drag images into albums from the image grid21// - Server Actions for album CRUD and image-to-album assignmentPro tip: Supabase Image Transformations handle resizing at the CDN level — no server compute needed. Just append width and quality params to the storage URL.
Expected result: The resize API redirects to transformed Supabase Storage URLs with caching. Users can create albums, organize images, and manage everything from their dashboard.
Add short-code generation and sharing features
Create the PostgreSQL function for unique short codes and add embed code generation and social sharing.
1// Paste this prompt into V0's AI chat:2// Add short code generation and sharing:3// 1. Create a PostgreSQL function generate_short_code() that:4// Generates encode(gen_random_bytes(6), 'base64') → replace '+' with 'a', '/' with 'b', remove '='5// Truncate to 8 characters6// Check uniqueness against images.short_code7// Retry up to 5 times if collision, raise exception if all fail8// Returns the unique short_code9//10// 2. Update app/i/[short_code]/page.tsx to add:11// - Open Graph meta tags: og:image pointing to the image URL, og:title = original_name12// - Twitter card meta tags for image preview13// - Embed code generator: Dialog showing copyable HTML, Markdown, and BBCode snippets14// - Social share buttons for Twitter, Reddit, and direct link copy15// - "Report" Button (stores report in a reports table)16//17// 3. Add generateMetadata function for SEO with dynamic og:imageExpected result: Short codes are generated uniquely via PostgreSQL. Image pages include OG meta tags for social previews and embed code generation for sharing.
Complete code
1import { createClient } from '@/lib/supabase/server'2import { NextRequest, NextResponse } from 'next/server'34export async function POST(request: NextRequest) {5 const supabase = await createClient()6 const { data: { user } } = await supabase.auth.getUser()7 if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })89 const formData = await request.formData()10 const file = formData.get('file') as File11 const isPublic = formData.get('is_public') === 'true'1213 if (!file || !file.type.startsWith('image/')) {14 return NextResponse.json({ error: 'Invalid image file' }, { status: 400 })15 }1617 const { data: shortCode } = await supabase.rpc('generate_short_code')18 const storagePath = `${user.id}/${shortCode}-${file.name}`1920 const { error: uploadError } = await supabase.storage21 .from('images')22 .upload(storagePath, file, {23 contentType: file.type,24 upsert: false,25 })2627 if (uploadError) {28 return NextResponse.json({ error: uploadError.message }, { status: 500 })29 }3031 const { error: dbError } = await supabase.from('images').insert({32 user_id: user.id,33 filename: `${shortCode}-${file.name}`,34 original_name: file.name,35 file_size: file.size,36 mime_type: file.type,37 storage_path: storagePath,38 is_public: isPublic,39 short_code: shortCode,40 })4142 if (dbError) {43 return NextResponse.json({ error: dbError.message }, { status: 500 })44 }4546 return NextResponse.json({ short_code: shortCode })47}Customization ideas
Add image annotations
Let users draw on images with a canvas overlay for highlighting areas or adding text labels before sharing.
Add expiring links
Let users set an expiration time on shared images — after the TTL, the short code returns a 410 Gone response.
Add watermarking
Apply a user-configurable text watermark to downloaded images using an Edge Function with image manipulation.
Add image comparison slider
Build a before/after slider component for comparing two images side by side with a draggable divider.
Common pitfalls
Pitfall: Storing images in the database instead of Supabase Storage
How to avoid: Upload files to a Supabase Storage bucket and store only the storage_path in the database.
Pitfall: Generating short codes on the client side
How to avoid: Use a PostgreSQL function with a unique constraint and retry loop to guarantee uniqueness at the database level.
Pitfall: Not setting cache headers on resized images
How to avoid: Set Cache-Control: public, max-age=31536000 on the resize API response since resized versions of the same image never change.
Pitfall: Allowing unlimited file sizes
How to avoid: Set a 10MB file limit on the Supabase Storage bucket and validate file size client-side before upload.
Best practices
- Upload files to Supabase Storage and store only the path in the database — never store binary data in PostgreSQL.
- Use Supabase Image Transformations for thumbnails and resizing via URL parameters instead of server-side processing.
- Generate short codes with a PostgreSQL function using gen_random_bytes for randomness and a unique constraint for safety.
- Set long-lived cache headers (max-age=31536000) on resized images since transformed versions are immutable.
- Validate file types and sizes both client-side (for UX) and server-side (for security) in the upload API route.
- Use Open Graph meta tags with the image URL so shared links preview correctly on social media and messaging apps.
AI prompts to try
Copy these prompts to build this project faster.
I'm building an image hosting app with Next.js and Supabase Storage. I need a PostgreSQL function that generates unique 8-character short codes using gen_random_bytes with a retry loop on collision. Show me the function SQL, how to call it from an API route during upload, and how to serve resized images via Supabase Image Transformations with cache headers.
Build the drag-and-drop image uploader for an image hosting app. Create a 'use client' component with a drop zone that accepts multiple image files (PNG, JPG, GIF, WebP). Show file previews as thumbnails before upload. Each file gets a Progress bar during upload. Include a Switch for public/private. On upload, POST FormData to /api/upload which stores in Supabase Storage and returns a short_code. Display the shareable link with a copy Button.
Frequently asked questions
Can I build this with the free V0 plan?
The core upload and gallery pages work with the free tier. V0 Premium is recommended for the full multi-page app with albums and dashboard.
How do short codes work?
A PostgreSQL function generates a random 8-character alphanumeric string using gen_random_bytes. A unique constraint ensures no collisions, and the function retries up to 5 times if a duplicate is generated.
Can I resize images on the fly?
Yes. Enable Image Transformations in the Supabase Dashboard under Storage settings. Then append ?width=800&quality=75 to any storage URL to get a resized version served from the CDN.
What's the maximum file size?
The Supabase Storage bucket is configured with a 10MB limit. You can increase this in the bucket settings, but consider the impact on storage costs and page load times.
Can anonymous users upload?
The current setup requires authentication for uploads. To allow anonymous uploads, make user_id nullable in the images table and adjust the RLS policies accordingly.
How do I deploy?
Publish via V0's Share menu. Set NEXT_PUBLIC_SUPABASE_ANON_KEY and NEXT_PUBLIC_SUPABASE_URL for the client-side gallery, and SUPABASE_SERVICE_ROLE_KEY for the upload API route.
Can RapidDev help build a custom image platform?
Yes. RapidDev has built 600+ apps including media platforms with CDN optimization, watermarking, and advanced content moderation. Book a free consultation to discuss your project.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation