title: How I Optimized My AI Image App from 3s to 300ms with Next.js & Supabase

Published: (January 8, 2026 at 03:02 AM EST)
2 min read
Source: Dev.to

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

Community Feed Screenshot

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!

Back to Blog

Related posts

Read more Ā»