Jul 23, 2025

Performance yang Kerasa (Bukan Optimasi Sok Keren)

App bisa kelihatan cakep di Figma tapi kerasa berat di HP, dan itu bukan “minor issue”. Performance itu UX, karena user gak peduli kamu pakai stack apa kalau tap pertama aja delay.

Maarif Alawi

Full-Stack Web App Developer

Yang user inget itu: loading pertama, interaksi pertama, sama momen “kok ngelag sih?”. Dalam 3 tahun dan ~13 project shipped (Next.js + Supabase + Vercel + Sentry), mayoritas speed-up yang beneran kerasa datang dari hal boring: gambar, bundle JS, dan cara fetch data.

Cerita singkat: halaman cepat di laptop, ambyar di HP

Pernah ada app yang “fine” di laptop dev, tapi di HP mid-range rasanya kaya jalan di lumpur. Setelah dicek, hero image ternyata ukuran monster, terus list page fetch kebanyakan data sekaligus, dan UI kebanyakan client component.

Hasilnya: initial load berat, lalu interaksi pertama ada delay karena JS numpuk. Sentry ngebantu ngasih sinyal: beberapa route punya error timeout dan user drop pas halaman tertentu.

Lesson-nya: performance itu bukan angka di Lighthouse doang. Kalau user merasa lambat, berarti lambat, titik.

3 Fix yang paling kerasa

1) Image strategy

Paling sering, “berat” itu karena image. Di Next.js, pakai next/image biar otomatis responsive dan optimal format, bukan render <img> mentah terus berharap keajaiban. Set sizes biar browser tau resolusi yang dibutuhin, pakai format modern (WebP/AVIF kalau bisa), dan lazy-load untuk gambar non-hero. Untuk hero yang penting, kasih priority biar gak nunggu lama.

2) Bundle bloat

Bundle bloat biasanya datang dari import yang salah dan “semua jadi client component”. Kalau satu page cuma butuh interaksi kecil, jangan jadikan seluruh layout client. Split code: dynamic import untuk komponen berat, dan keep logic di server component kalau bisa. Banyak library UI/utility itu diam-diam bawa koper besar, jadi audit import itu bukan perf fetish, itu survival.

3) Data fetching & caching

Fetch kebanyakan itu sama aja kaya kamu nyuruh waiter bolak-balik buat tiap sendok. Paginate list, ambil kolom yang perlu aja, dan hindari N+1 (fetch per item). Di Next.js, manfaatkan caching/revalidate untuk data yang gak perlu real-time per detik. Dan kalau data sensitif/tenant-based, caching tetap boleh, tapi harus bener scope dan key-nya.

Anti-patterns performance yang sering disangka normal

  • Upload hero image 5MB karena “biar tajem”.
    Solusinya: compress, pakai next/image, dan atur sizes yang bener.

  • Render semua data tanpa pagination karena “biar lengkap”.
    Solusinya: paginate + fetch minimal fields, load more jika perlu.

  • Semua komponen jadi client karena mau cepat develop.
    Solusinya: server components default, client cuma buat interaksi yang butuh state/event.

  • Fetch di tiap rerender / effect yang gak dikontrol.
    Solusinya: pindahin fetch ke server, atau memoize + dedupe request.

  • Kebanyakan analytics/3rd-party script di awal page.
    Solusinya: load setelah interaksi/idle, dan buang yang gak kepake.

Kode: Pagination query ke Supabase (biar list gak berat)

import { NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const page = Math.max(1, Number(searchParams.get("page") ?? "1"));
  const limit = Math.min(50, Math.max(10, Number(searchParams.get("limit") ?? "20")));
  const from = (page - 1) * limit;
  const to = from + limit - 1;

  const tenantId = req.headers.get("x-tenant-id");
  if (!tenantId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { data, error, count } = await supabase
    .from("projects")
    .select("id,name,status,updated_at", { count: "exact" })
    .eq("tenant_id", tenantId)
    .order("updated_at", { ascending: false })
    .range(from, to);

  if (error) return NextResponse.json({ error: "Server error" }, { status: 500 });

  return NextResponse.json({
    ok: true,
    page,
    limit,
    total: count ?? null,
    data,
  });
}

COPY THIS

  • Hero image pakai next/image + sizes yang bener

  • Compress image, hindari upload mentah dari designer

  • Non-hero images lazy-load, hero pakai priority

  • Default server components, client cuma yang butuh interaksi

  • Audit import: hindari whole-library import kalau gak perlu

  • Split komponen berat pakai dynamic import

  • List data selalu paginate (limit/range), bukan load semua

  • Select fields minimal, jangan select * kebiasaan buruk

  • Hindari N+1: join/IN query atau fetch batch

  • Tunda 3rd-party scripts sampai after load/idle

Performance itu bukan proyek “sehari optimize terus selesai”. Ini kebiasaan kecil yang konsisten, dan hasilnya kerasa banget di device biasa, bukan cuma laptop dev.

Kalau app kamu enteng, user lebih sabar, conversion lebih waras, dan support gak jadi tempat curhat “kok lemot”.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

  • ALAWI

    CONTACT

( 00-09 )

CONTACT

Let’s talk about what you’re building
and how I can help.

  • Want to chat fast?

+62 817-7577-7778

  • Prefer email?

maarifalaawi@gmail.com

  • LinkedIn

maarifalawi

Start with a brief.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

I build and maintain production-ready web apps. Clean UI, stable backend, calm releases.

Create a free website with Framer, the website builder loved by startups, designers and agencies.