title: How I Optimized My AI Image App from 3s to 300ms with Next.js & Supabase
Source: Dev.to
Hello Developers! š
I recently launched Nanobanan Editor, an AIāpowered image editing tool that works with naturalālanguage prompts. While the MVP was fun to build, the Community Feed page was taking 3ā5āÆseconds to loadāa performance problem I needed to solve.
Below is how I diagnosed the bottleneck and brought the load time down to under 300āÆms (a 10Ć improvement) using clientāside rendering strategies and database indexing.
The Stack š ļø
- Framework: Next.jsāÆ14 (App Router)
- Database: Supabase (PostgreSQL)
- Styling: TailwindCSS
- Deployment: Vercel
The Problem: Traditional SSR Bloat š¢
Initially the community page was rendered with standard ServerāSide Rendering (SSR):
// ā The slow way (Simplified)
export const dynamic = "force-dynamic";
export default async function CommunityPage() {
// Blocking current thread to fetch database
const images = await supabase.from('generations').select('*')...;
return ;
}
Why it was slow
- Blocking ā HTML didnāt stream until the database query finished.
- Complex query ā No proper indexes on the large dataset.
- Network latency ā Each request incurred a serverātoādatabase roundātrip.
The Solution: CSR + SWR + Indexes š
I switched to ClientāSide Rendering (CSR) with a StaleāWhileāRevalidate (SWR) strategy and added the necessary database indexes.
StepāÆ1: Switch to CSR with Skeleton Loading
Show a UI skeleton instantly, then fetch the data in the background.
"use client";
import useSWR from "swr";
export default function CommunityPage() {
// Nonāblocking fetch
const { data, isLoading } = useSWR("/api/community/feed", fetcher);
if (isLoading) return ; // Instant feedback
return ;
}
StepāÆ2: Intelligent Caching with SWR
Cache the response so a quick revisit doesnāt hit the API again.
const { data } = useSWR("/api/community/feed", fetcher, {
dedupingInterval: 60_000, // Reuse data for 60āÆs
revalidateOnFocus: false, // No refetch just because the tab gains focus
});
StepāÆ3: Database Indexing (The Real MVP)
The original query performed a sequential scan:
SELECT * FROM generations
WHERE is_public = true
ORDER BY created_at DESC
LIMIT 12;
Adding a composite index solved the problem:
CREATE INDEX idx_generations_public_created
ON generations (is_public, created_at DESC)
WHERE is_public = true;
The query time dropped from āāÆ500āÆms to āāÆ15āÆms.
Results š
- First load: ~300āÆms (skeleton UI appears instantly)
- Repeat visit: <āÆ50āÆms (cache hit)
- Lighthouse score: jumped from 65 to 95
Try It Out
Experience the speed difference live:
š Nanobanan Editor Community

This journey showed that while SSR is powerful, a wellādesigned CSR approach with smart caching and proper indexing can deliver a much snappier UX for feedābased pages. Feel free to ask any questions about the Next.jsāÆ+āÆSupabase stack in the comments!