A masonry gallery page for your own picture collection, with tag search and a full-screen viewer. Add the component to a Next.js app, put image links and tags in Supabase, and you get an endless wall of pictures in random order.
- Masonry grid: 2 columns on phones and up to 7 on wide screens, picked from the window width.
- Endless scroll: 14 more images load as you near the bottom, in random order.
- Tag search: type a tag in the search box; the query runs 500 ms after you stop typing, and tags must be lowercase.
- Full-screen viewer: click an image to see it large, rotate it, jump to its tags, and move with arrow keys or swipes.
Browser (client.jsx) ──► Supabase RPC get_random_media ──► "Gallery" table (image URL + tags)
Browser ──► image URL (any host, e.g. a cloud storage bucket) ──► picture
The images themselves are not stored in Supabase; the table holds only a link, size and tags for each one.
Needs a Next.js app with Tailwind CSS, and a Supabase project. The images must already be hosted somewhere with a public URL.
- Install the dependencies:
npm install @supabase/supabase-js framer-motion lucide-react - Copy
client.jsxinto your app and render it from a page, for exampleimport GalleryClient from './client'. - Create
lib/supabaseClient.jsexporting asupabaseclient, since the component imports@/lib/supabaseClient. PutNEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEYin.env.local. - In
next.config.js, allow the host of your image URLs underimages.remotePatterns, because the full-screen viewer uses Next.js image optimization. - In Supabase, open SQL Editor and create the table. Put the image link in
gcs_urland lowercase tags intags, for example{ln,miku}.
CREATE TABLE public."Gallery" (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
gcs_url TEXT NOT NULL,
tags TEXT[] NOT NULL,
width INT,
height INT,
source TEXT
);- Run the second block to create the random-order search function:
CREATE OR REPLACE FUNCTION get_random_media(
p_search_term text DEFAULT '',
p_limit int DEFAULT 14,
p_offset int DEFAULT 0
)
RETURNS TABLE (
id bigint,
tags text[],
width int,
height int,
source text,
gcs_url text,
created_at timestamptz
)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT
m.id,
COALESCE(m.tags, '{}')::text[],
m.width,
m.height,
m.source,
m.gcs_url,
m.created_at
FROM "Gallery" m
WHERE
(p_search_term = '' OR m.tags @> ARRAY[p_search_term]::text[])
ORDER BY random()
LIMIT p_limit
OFFSET p_offset;
END;
$$;
GRANT EXECUTE ON FUNCTION get_random_media(text, int, int) TO anon, authenticated;The browser sends search terms to your Supabase project and loads pictures straight from wherever their URLs point. Nothing goes to any other service. The function runs as SECURITY DEFINER and is open to the public anon key, so anyone who has the key can list every row in Gallery. Keep only images you are happy to expose.
