Skip to main content
RapidDev - Software Development Agency

How to Build Image hosting with V0

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'll build

  • Multi-file drag-and-drop uploader with progress bars and privacy toggle using Input and Progress
  • Public gallery with responsive masonry grid layout showing image thumbnails
  • Shareable short-code URLs (e.g., /i/a8Bk3mNx) with image metadata and direct link copying
  • On-the-fly image resizing via Supabase Image Transformations with query parameters
  • Album creation and management with public/private visibility and slug-based URLs
  • User dashboard showing uploaded images, storage usage, and album management
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 PremiumApril 2026RapidDev Engineering Team
TL;DR

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

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase, Auth & Storage

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

1

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.

prompt.txt
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 conflict
7// Create a Supabase Storage bucket 'images' with 10MB file limit
8// Add RLS: public images readable by all, authenticated users can upload and manage their own images

Pro 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.

2

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.

prompt.txt
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/*' multiple
6// - Show file previews as thumbnails before upload
7// - Switch toggle for public/private per image
8// - Upload Progress bar per file showing percentage
9// - On upload, call POST /api/upload with FormData containing:
10// the file, is_public flag
11// - The API route (app/api/upload/route.ts) should:
12// Generate a short_code using the generate_short_code() PostgreSQL function
13// 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 metadata
16// Return the short_code for the shareable link
17// - After upload, show the shareable link /i/{short_code} with copy Button
18// - Use Card to wrap the upload zone and Progress for upload status

Expected result: Users can drag and drop multiple images, see upload progress, set privacy, and receive shareable short-code links after upload completes.

3

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.

prompt.txt
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 size
7// - Select for sort order: recent (default), most viewed
8// - "Upload" Button as CTA
9// - Infinite scroll or pagination with 24 images per page
10//
11// 2. app/i/[short_code]/page.tsx — image viewer:
12// - Full-size image display
13// - Metadata Card: original name, dimensions, file size Badge, upload date, view count
14// - Share buttons: copy direct link, copy embed code (<img> tag), copy markdown
15// - Dialog for image details with download Button
16// - On-the-fly resize: links for common sizes (640px, 1024px, 1920px) using ?w= query param
17// - Increment view_count via Server Action on page load
18// - If image is private and viewer is not the owner, show 404

Expected result: The homepage shows a masonry gallery of public images. Individual image pages display full-size images with metadata, share buttons, and resize links.

4

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.

prompt.txt
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 params
5// - Looks up image by short_code
6// - 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 URL
9// - Cache headers: public, max-age=31536000 for resized images
10//
11// 2. app/album/[slug]/page.tsx — album view:
12// - Album title, description, image count Badge
13// - Image grid similar to gallery but filtered to album images ordered by position
14// - Share Button for the album URL
15//
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 used
19// - "Create Album" Button opening Dialog with title Input, description Textarea, slug Input
20// - Drag images into albums from the image grid
21// - Server Actions for album CRUD and image-to-album assignment

Pro 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.

5

Add short-code generation and sharing features

Create the PostgreSQL function for unique short codes and add embed code generation and social sharing.

prompt.txt
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 characters
6// Check uniqueness against images.short_code
7// Retry up to 5 times if collision, raise exception if all fail
8// Returns the unique short_code
9//
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_name
12// - Twitter card meta tags for image preview
13// - Embed code generator: Dialog showing copyable HTML, Markdown, and BBCode snippets
14// - Social share buttons for Twitter, Reddit, and direct link copy
15// - "Report" Button (stores report in a reports table)
16//
17// 3. Add generateMetadata function for SEO with dynamic og:image

Expected 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

app/api/upload/route.ts
1import { createClient } from '@/lib/supabase/server'
2import { NextRequest, NextResponse } from 'next/server'
3
4export 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 })
8
9 const formData = await request.formData()
10 const file = formData.get('file') as File
11 const isPublic = formData.get('is_public') === 'true'
12
13 if (!file || !file.type.startsWith('image/')) {
14 return NextResponse.json({ error: 'Invalid image file' }, { status: 400 })
15 }
16
17 const { data: shortCode } = await supabase.rpc('generate_short_code')
18 const storagePath = `${user.id}/${shortCode}-${file.name}`
19
20 const { error: uploadError } = await supabase.storage
21 .from('images')
22 .upload(storagePath, file, {
23 contentType: file.type,
24 upsert: false,
25 })
26
27 if (uploadError) {
28 return NextResponse.json({ error: uploadError.message }, { status: 500 })
29 }
30
31 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 })
41
42 if (dbError) {
43 return NextResponse.json({ error: dbError.message }, { status: 500 })
44 }
45
46 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.

ChatGPT Prompt

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 Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.