The whole stack behind this site, step by step
This site has a tagged gallery, a private admin, a print shop that sends orders straight to a print lab, private client galleries, product feeds for Instagram, Pinterest and TikTok shopping, and pages built for Google and AI search. No monthly website platform, no commission on sales, and I own every line of the code. This guide is everything I learned building it, including what broke. Read it, or grab the AI playbook and have Claude or ChatGPT walk you through it.
You can stop at any step and still have a working site. Most people want the first three. The shop and the feeds are what turn it into something that sells.
llms.txt, so Google and AI assistants can find and cite your work.Hosting, database and payments all have free tiers that cover a new site. Plan on a domain (a yearly fee), a paid database tier once you go live (free tiers pause idle projects, more on that in step 13), and the standard card processing fee on each sale. Prints are paid for out of each order, so there's no inventory.
With an AI coding assistant doing the typing: a weekend for the portfolio and admin, another weekend or two for the shop, and a few evenings each for client galleries, feeds and SEO.
index.html. It loads fast and any AI assistant can edit it.Why not Squarespace, Pixieset or SmugMug? They're fine and faster to start. You trade a monthly fee, someone else's templates and limits on what you can automate. Building your own lets you do things like push your whole catalog to Instagram Shopping automatically, which most platforms can't.
Public keys (Supabase anon key, Stripe publishable key) can live in your page. Secret keys never go in the page. That's the Supabase service role key, the Stripe secret key, the Stripe webhook secret and the lab API key. They go in environment variables in Vercel or Supabase, and only server functions read them. Don't paste secret keys into an AI chat either. Tell the assistant the variable name and add the value yourself.
One table holds every photo. Start with something like this and add columns as you need them:
photos
id bigint primary key
src text -- public URL of the web-size file
master_path text -- path to the full-res file in a private bucket
title text -- "Eiffel Tower at Night, Paris, France"
tags text[] -- {paris, france, night, architecture}
width int
height int -- width/height gives you the aspect ratio
sort_order int default 0
hidden boolean default false
for_sale boolean default false
created_at timestamptz default now()
Make two storage buckets: a public one for web-size images and a private one for full-resolution masters. The masters are what the print lab gets, through short-lived signed links, never a public URL.
loading="lazy", so phones aren't pulling 200 images at once.When I filtered by tag, hidden photos were collapsed to zero height but still sat in the column layout, so the visible ones crammed to the left. Only render the matching photos, or hide the rest with display:none, never just height:0.
Every photo needs two files:
Have your upload resize and compress the web version in the browser before it uploads, so you can drop in full-size exports and never think about it.
Twice, new photos just never showed up in the gallery. The cause both times was oversized web files that failed silently. Build the resize into the upload so it can't happen, and have the uploader tell you when something fails.
Camera and phone files carry GPS coordinates. Strip location data from every public file. If you want to show where a photo was taken, put the city in the title and tags yourself.
This is the page that makes the site something you'll actually keep updating. Mine does bulk upload with drag and drop, tag editing, reordering, hiding, marking photos for sale, and an analytics panel.
noindex. Don't link it from anywhere public.My first admin panel's upload form shipped to every visitor, just hidden with CSS. It was harmless because the database rules held, but it's the wrong pattern. Load admin code only after login.
This is the boring step that does the most for search and sales. "IMG_4471" sells nothing. "Eiffel Tower at Night, Paris, France" gets found.
buyer picks photo + size + finish
→ your server function creates a Stripe Checkout session
→ buyer pays on Stripe
→ Stripe calls your webhook
→ webhook checks the payment is real, then places the
print order with the lab, using a signed link to the master
→ lab prints and ships, sends tracking
→ buyer lands on your thank-you page
Pull print cost and shipping from the lab's quote API instead of typing them in. Prices change and shipping varies by country. Then price to the margin you want:
price = (print cost + shipping) / (1 - target margin)
$30 cost, 45% margin → 30 / 0.55 = $54.55 → round to $55
I build shipping into the price and show "free shipping." Add a bulk discount for multiple prints, and sell sets of three or more as a bundle.
Run a complete order in Stripe test mode against the lab's sandbox. Then place one real order to your own address before you tell anyone the shop is open. You'll want to see the print quality yourself anyway.
People who tap your link inside Instagram land in Instagram's built-in browser, which has no Apple Pay. Detect it and show a small bar offering to open the page in Safari or Chrome.
If you shoot for clients, deliver through your own site instead of a separate gallery service. It looks more professional and it's one more reason people end up on your domain.
/g/smith-family?code=7K2Q. The server checks the code before sending any photos.noindex and keep them out of your sitemap, product feeds and search pages. Serve photos through signed links that expire.Client photos are never for sale to the public. They're the client's. With permission, I only use them to show my work.
Put these up before you connect any shopping platform. Pinterest rejected my merchant application until I had them.
Sales tax: prints are physical goods, and most US states tax them once your sales pass a threshold. Look up your state's rules, and talk to an accountant if you're unsure. This guide isn't tax advice.
Every shopping platform takes a product catalog, which is just a spreadsheet or feed of your products: ID, title, description, price, image URL, product page link, availability. Since every photo already lives in your database, a small script can generate those feeds automatically.
Use your sets as product groups so related photos stay together. Regenerate the feeds whenever you add photos, or have the platform fetch them from a URL on a schedule.
A gallery built with JavaScript can look like an empty page to a search engine. Give every photo a real page.
/print/eiffel-tower-night-paris), generated by a script at deploy time, with the title, description, sizes and prices in plain HTML.VisualArtwork and Product with offers and prices. This is how Google shows prices and images in results.sitemap.xml listing every public page, generated from the database. Submit it in Google Search Console.llms.txt at the root: a plain-language summary of who you are, what you sell and how pricing works. AI assistants read it./catalog.json) so agents and tools can read your whole catalog in one request.A redesign quietly dropped my page titles and descriptions, and I didn't notice for weeks. After any big change, check a few pages with a link preview tool and Google's Rich Results Test.
Vercel's built-in analytics covers the basics. I also log my own events (page views, photo opens, size picks, checkout starts) to a Supabase table and chart them in my admin, so I can see exactly where buyers drop off.
www.My gallery went down because the database hung, not because anything in the site changed. That's exactly what an uptime check would have caught before a visitor did.
A text file that turns an AI assistant into a build partner for this exact site. Paste it into Claude, ChatGPT or another assistant (or drop it into a Claude Code project). It interviews you about what you shoot and what you want, then walks you through every step above one at a time, checks your work, and keeps you away from the security mistakes. If your assistant can edit files, it can do most of the typing.
You don't need any of this to build the site. It's what I shoot with and use to get photos from camera to print. Each slot has a budget, a pick and an upgrade, with what each is best for. "I use this" marks gear I own. Links open on Amazon.
I build these for photographers: your photos, your brand, your domain, and accounts in your name, so you own everything. Pick what you want and I'll send you a quote.
Disclosure: As an Amazon Associate I earn from qualifying purchases. Product links on this page are affiliate links. Items marked "I use this" are gear I own; the rest are options I researched. The price you pay is the same. Other services named in this guide (Vercel, Supabase, Stripe, Prodigi, Claude) are mentioned because I use them, not because they pay me.