Why capturioo.com Is So Fast - 05/07/2025
App speed isn't an accident. I break down exactly why capturioo.com feels instant — from stack choices to Optimistic UI, lazy loading, aggressive caching, and Supabase Realtime.
Live app: capturioo.com
App speed isn’t an accident. It’s the result of deliberate decisions around architecture, stack, and UI patterns. In this post, I break down exactly why capturioo.com feels instant — from the moment the user opens the page to when they interact with their data.
1. The Stack Is Fast by Design
Stack choices already predispose speed before writing the first line of code:
- React + Vite: the fastest bundler in the ecosystem. Instant HMR in development, optimized bundles with automatic code splitting in production.
- Supabase: direct queries to PostgreSQL without an intermediate API layer. Fewer network hops = less latency.
- Tailwind CSS: utilities generated at build time, minimal CSS in production. No runtime style engine blocking the render.
2. Optimistic UI: The UI Doesn’t Wait for the Server
The pattern that most impacts perceived speed is Optimistic UI. Instead of waiting for server confirmation to update the screen, I apply the change immediately to local state and persist in the background:
async function createCapture(data: NewCapture) {
// 1. Update UI instantly
const temp = { ...data, id: crypto.randomUUID(), pending: true };
setCaptures((prev) => [temp, ...prev]);
// 2. Persist to Supabase (non-blocking)
const { data: saved, error } = await supabase
.from('captures')
.insert(data)
.select()
.single();
if (error) {
// Revert on failure
setCaptures((prev) => prev.filter((c) => c.id !== temp.id));
return;
}
// Replace temp item with real one
setCaptures((prev) =>
prev.map((c) => (c.id === temp.id ? saved : c))
);
}
The user sees the action completed in ~0ms. Network latency happens in parallel, invisible.
3. Suspense + Lazy Loading: Only Load What’s Needed
Heavy modules aren’t included in the initial bundle. I load them lazily when the user needs them:
import { lazy, Suspense } from 'react';
const CaptureDetail = lazy(() => import('./CaptureDetail'));
function App() {
return (
<Suspense fallback={<SkeletonCard />}>
<CaptureDetail />
</Suspense>
);
}
The initial bundle is minimal. Heavy routes and components download only when the user visits them.
4. Aggressive Caching with React Query
I use TanStack Query to cache Supabase responses. The first time a user loads their captures, the query runs. Subsequent times, the response comes from cache instantly while checking for new data in the background (stale-while-revalidate):
const { data: captures } = useQuery({
queryKey: ['captures', userId],
queryFn: () => fetchCaptures(userId),
staleTime: 1000 * 60, // 1 minute fresh
});
The user never sees a blank screen when navigating between routes they’ve already visited.
5. Supabase Realtime Instead of Polling
Polling (querying the server every N seconds) is expensive for performance and UX. With Supabase Realtime, the server pushes changes to the client only when they happen. The app is always up to date without wasting a single network request:
supabase
.channel('captures')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'captures' },
(payload) => {
queryClient.invalidateQueries({ queryKey: ['captures'] });
}
)
.subscribe();
6. Optimized Images and Assets
- Images served in WebP to reduce file size.
- Static assets with hashed filenames, enabling immutable cache in the browser.
- Fonts loaded with
font-display: swapso text is visible from the first frame.
The Result
| Metric | Value |
|---|---|
| First Contentful Paint | < 0.8s |
| Time to Interactive | < 1.2s |
| Lighthouse Performance | 95+ |
It’s not about micro-optimizations. It’s about making the right decisions from the start.