Mobile itu mayoritas trafik storefront Shopify, dan sekaligus kondisi performa paling berat: CPU lebih lambat, memori terbatas, jaringan berubah-ubah. Core Web Vitals diukur di mobile secara default, jadi performa mobile = performa theme. Bab ini ngumpulin masalah usability mobile yang langsung ngaruh ke skor CWV dan ranking.
Prinsip mobile first dari 54 Best Practices Umum dan 56 Design dan Color System bukan cuma soal desain. Ini soal di mana skor kamu diukur.
Kenapa Mobile Lebih Berat
Perbedaan hardware dan jaringan ngedorong gap performa desktop-mobile, dan ngaruh ke semua metrik, terutama LCP, INP, dan TBT:
- CPU: prosesor mobile parse dan eksekusi JavaScript jauh lebih lambat. DevTools pakai 4× slowdown buat ngedekati ponsel Android kelas menengah, dan itu mencerminkan gap nyata. Script yang 50ms di desktop bisa 200ms+ di ponsel, ngubah biaya kecil jadi jeda yang terasa.
- Latensi jaringan: bahkan di seluler cepat, round-trip lebih tinggi dari broadband kabel. Tiap request bawa overhead lebih besar, jadi jumlah request dan prioritas resource lebih berdampak di mobile.
- Thermal throttling: ponsel nurunin performa CPU/GPU waktu panas. Halaman yang bagus di load pertama bisa memburuk selama sesi browsing panjang, terutama di halaman koleksi padat produk atau fitur interaktif kompleks.
- Memori: browser mobile punya RAM lebih sedikit. DOM besar, banyak gambar resolusi tinggi yang di-decode bersamaan, dan heap JavaScript berat naikin peluang browser ngebuang resource cache atau ngelambatin garbage collection.
Tes dengan Throttling Nyata
Tes lab di desktop cepat tanpa throttling gak ngasih tau apa-apa soal mobile. Di DevTools:
- Panel Performance: CPU throttling 4x slowdown (kelas Moto G Power).
- Panel Network: throttling Slow 4G (1,6 Mbps download, 750 Kbps upload, 150ms RTT).
- Device Mode dengan viewport ponsel (Moto G Power, Galaxy S8).
Aktifin dua-duanya sekaligus: throttling CPU dan jaringan bertumpuk. 4× CPU aja masih pakai jaringan desktop kamu, throttling jaringan aja masih pakai CPU desktop.
Dan tes di perangkat nyata: emulasi mendekati tapi gak mereplikasi. Thermal throttling, proses latar OS, dan latensi input sentuh nyata cuma muncul di hardware asli. Simpan satu ponsel Android kelas menengah buat tes, seri Moto G atau Samsung Galaxy A mewakili porsi besar trafik ecommerce nyata. Ini yang bikin field data dari 57 Performance Dasar, Metrik, dan Cara Ngukur sering beda dari lab.
Kurangi JavaScript di Mobile
Biaya eksekusi JavaScript itu pengali terbesar antara desktop dan mobile. Gap CPU ngalikan tiap milidetik parsing, kompilasi, dan eksekusi. Teknik individualnya di 61 Performance JavaScript dan INP, di mobile prioritasnya ngurangin berapa banyak JavaScript yang jalan saat page load.
Satu pola khusus: load kode spesifik mobile secara kondisional lewat import() dinamis, biar kode khusus desktop gak dimuat di mobile dan sebaliknya:
if (window.matchMedia('(max-width: 749px)').matches) {
import('./mobile-nav.js').then(({ init }) => init());
}Di mobile, long task (di atas 50ms) ngeblokir main thread dan nunda interaktivitas. Sumber umum di theme: script app pihak ketiga yang eksekusi sinkron saat load, blok <script> inline besar yang nge-serialize object setting, dan event listener yang dipasang ke ratusan elemen DOM daripada pakai delegation.
Prioritaskan Konten yang Kelihatan di Mobile
Viewport mobile lebih kecil, jadi lebih sedikit gambar yang kelihatan di load awal. Pakai sizes mobile-first biar browser milih source terkecil yang cukup:
{{ product.featured_image
| image_url: width: 800
| image_tag:
loading: image_loading,
widths: '200, 400, 600, 800',
sizes: '(min-width: 750px) calc(50vw - 2rem), calc(100vw - 2rem)'
}}Artinya: di layar di bawah 750px, gambar ngisi hampir seluruh lebar viewport; di lebih lebar, sekitar setengah. Ini nyegah mobile download gambar ukuran layout desktop. Dan buat section, di mobile lebih sedikit section yang above the fold, jadi section.index (dengan nil yang jatuh ke eager, dari 58 Performance Liquid > section.index: Atas vs Bawah Lipatan):
{%- liquid
# Test the below-the-fold case positively so a nil section.index stays eager.
if section.index > 2
assign section_loading = 'lazy'
else
assign section_loading = 'eager'
endif
-%}
{{ section.settings.image
| image_url: width: 1200
| image_tag:
loading: section_loading,
widths: '400, 600, 800, 1200',
sizes: '(min-width: 750px) 50vw, 100vw'
}}Interstitial Intrusif
Interstitial intrusif itu dialog dan overlay yang nutupin konten utama segera setelah halaman dimuat. Di ponsel, overlay yang muncul waktu tiba nutupin hampir seluruh layar, dan pembeli harus nemuin dan ngetuk tombol tutup (sering tap target terkecil di halaman) sebelum bisa baca apa-apa.
Masalahnya:
- Konten keblokir sampai overlay ditutup.
- Panduan Google secara spesifik nyebut konten yang ditutupin segera setelah pengunjung datang dari pencarian, dan ada penalti ranking-nya.
- Metrik loading: dialog yang disuntik saat load bisa jadi elemen LCP, dari 59 Performance Gambar dan LCP > Dialog yang Membajak LCP.
- Aksesibilitas: dialog tanpa peringatan, tanpa focus trap, tanpa pengembalian fokus, susah dipakai screen reader dan keyboard (55 Accessibility).
Definisi Google: dialog yang nutupin konten utama segera setelah navigasi dari pencarian, interstitial mandiri yang harus ditutup sebelum akses konten, dan layout di mana konten viewport awal kelihatan kaya interstitial mandiri.
1. Jangan Tutupin Konten Waktu Tiba
Versi terkuat: gak nampilin dialog saat load sama sekali. Apapun yang pembeli lihat sebelum mereka minta itu bersaing sama konten. Tanya dulu: bisa gak pesannya hidup di dalam halaman, bukan di atasnya? Kalau tetap pakai dialog, gate di interaksi (scroll, tap, klik), bukan timer. Reveal bertimer di halaman yang belum disentuh tetap nutupin konten dan tetap bisa jadi LCP.
2. Signup Inline
Taruh di alur konten, gak ngeblokir apa-apa:
<section class="newsletter-signup">
<div class="newsletter-content">
<h3>{{ section.settings.heading }}</h3>
<p>{{ section.settings.subheading }}</p>
{% form 'customer' %}
<div class="newsletter-form">
<input
type="email"
name="contact[email]"
placeholder="Your email"
required
>
<button type="submit">Subscribe</button>
</div>
{% endform %}
</div>
</section>Form customer-nya dari 46 Customer Sign-in, Account Component, Contact Form, Email Consent > Persetujuan Email Marketing. Alternatif lain: sticky banner tipis di atas/bawah (bukan overlay), atau prompt setelah pembeli lihat beberapa halaman.
❌ overlay layar penuh saat load
<div class="popup-overlay" id="email-popup">...</div>
<script>setTimeout(() => document.getElementById('email-popup').hidden = false, 3000);</script>✅ section inline di alur konten
<section class="newsletter-signup">{% form 'customer' %}...{% endform %}</section>Layout Responsif yang Performan
Layout responsif yang performan nyajiin gambar berukuran tepat, gak ngunduh resource tersembunyi, dan meminimalkan kompleksitas DOM di semua ukuran layar. Tiga kategori pemborosan dari implementasi responsif yang buruk:
- Gambar oversized di mobile: tanpa
sizesyang bener, browser download gambar ukuran desktop di ponsel. Grid yang berubah dari 4 kolom ke 2 = gambar dua kali lebih besar dari perlu di tiap kartu. - Download resource tersembunyi:
display: nonedi elemen khusus desktop tetap ngunduh gambar, font, dan resource-nya. Browser fetch semua yang dirujuk HTML apapun visibilitas CSS-nya. - DOM duplikat: markup terpisah mobile dan desktop (dua pohon navigasi, dua hero) ngegandain node yang harus di-parse, di-style, dan disimpan di memori. Ngaruh ke LCP, CLS, dan INP.
1. sizes per Layout
sizes itu tuas performa responsif paling efektif di theme Shopify. Kalau salah atau gak ada, browser nganggap gambar 100vw dan download source terbesar: di grid 4 kolom, gambar 4× lebih besar dari perlu. Cocokkan sizes sama breakpoint CSS, perhitungkan padding dan gap. Rumusnya per breakpoint: calc(<fraksi-kolom>vw - <total-padding-horizontal>), dan kalau grid punya wadah max-width, pakai nilai piksel tetap buat viewport besar.
Hero selebar viewport:
{{ section.settings.image
| image_url: width: 1600
| image_tag:
widths: '600, 900, 1200, 1600',
sizes: '100vw'
}}Grid 2 kolom, 1 di mobile:
{{ product.featured_image
| image_url: width: 800
| image_tag:
widths: '300, 400, 600, 800',
sizes: '(min-width: 768px) calc(50vw - 2rem), calc(100vw - 2rem)'
}}Grid 4 kolom, 2 di tablet, 1 di mobile:
{{ product.featured_image
| image_url: width: 600
| image_tag:
widths: '200, 300, 400, 600',
sizes: '(min-width: 1200px) calc(25vw - 2rem), (min-width: 768px) calc(50vw - 2rem), calc(100vw - 2rem)'
}}Buat gambar lazy, sizes: 'auto' dari 59 Performance Gambar dan LCP > 3. sizes=“auto” buat Gambar Lazy ngilangin daftar breakpoint yang bisa melenceng dari CSS. Buat crop yang beda (bukan cuma ukuran) antara mobile dan desktop, <picture> dari bab yang sama. Dan buat DOM duplikat, satu menu responsif dari 60 Performance CSS dan Font > Satu Menu Responsif.
Tap Target
Pembeli berinteraksi dengan jari, yang jauh kurang presisi dari kursor. Target kecil atau terlalu rapat bikin mis-tap (salah tap, undo, coba lagi), interaksi ekstra (tiap koreksi = tap lagi yang harus diproses, jadi layout yang rawan mis-tap ngerjain lebih banyak dari perlu), harus zoom, hambatan aksesibilitas buat gangguan motorik, dan abandonment.
Ujung jari sekitar 10mm = ~38 CSS px di 96 px/inci, dan semua panduan platform ngebuletin ke atas. Tiga ukuran yang sering dikutip, gak bisa dipertukarkan:
- WCAG 2.2 SC 2.5.8 Target Size (Minimum): 24×24 px, Level AA, yang diminta kebanyakan target konformansi, dengan pengecualian (spasi, target inline, ukuran esensial, alternatif setara).
- WCAG 2.1 SC 2.5.5 Target Size: 44×44 px, Level AAA, sama dengan Apple HIG 44×44 pt.
- Material Design: 48×48 dp, panduan platform.
Rekomendasi 48×48 px memenuhi ketiganya (dan 44px dari 55 Accessibility > Layar Sentuh dan Mobile):
/* Buttons */
button,
.button,
input[type="submit"],
input[type="button"] {
min-height: 48px;
min-width: 48px;
padding: 12px 24px;
font-size: 16px;
}
/* Links in navigation */
.site-nav a {
min-height: 48px;
padding: 12px 16px;
display: flex;
align-items: center;
}
/* Icon buttons */
.icon-button {
min-height: 48px;
min-width: 48px;
padding: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Form inputs */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="number"],
select {
min-height: 48px;
padding: 12px;
font-size: 16px;
}
/* Checkboxes and radio buttons */
input[type="checkbox"],
input[type="radio"] {
min-height: 24px;
min-width: 24px;
margin: 12px; /* Separates neighboring targets. Margin isn't part of the hit area. */
}
/* Enlarge the hit area itself by making the label the target */
.checkbox-field label {
display: flex;
align-items: center;
gap: 12px;
min-height: 48px;
}Dua catatan: font-size: 16px di input nyegah iOS auto-zoom waktu fokus. Dan buat checkbox/radio, margin bukan bagian hit area, jadi bikin label-nya yang jadi target 48px. Jarak antar target minimal 8px. Ini langsung relevan buat swatch varian dari 39 Produk Variants dan High-variant Products yang sering cuma 20–24px.
Konten Lebih Lebar dari Viewport
Konten yang melebihi lebar viewport maksa scroll horizontal, yang pembeli gak harapkan: konten tersembunyi di luar layar, layout sempit yang reflow tak terduga waktu rotasi, hambatan screen reader, dan tombol/link terdorong keluar layar yang susah atau mustahil dijangkau. Penyebab umum: elemen lebar tetap (width: 1200px), gambar besar tanpa max-width, tabel banyak kolom, blok teks pre-formatted, posisi absolut dengan koordinat tetap, transform yang keluar viewport. Gampang kelewat di desktop, cek tiap layout di viewport mobile lewat emulasi.
1. Meta Viewport
<!-- Prevents content from overflowing the viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">Tanpa ini, browser mobile nganggap lebar desktop (980px), semua konten mengecil dan berpotensi overflow. Ingat dari bab 55: tanpa maximum-scale dan user-scalable=no.
2. Lebar Relatif
/* Anti-pattern: fixed width */
.container {
width: 1200px;
}
/* Recommended: fluid width with max-width */
.container {
box-sizing: border-box;
width: 100%;
max-width: 1200px;
padding: 0 20px;
margin: 0 auto;
}
/* Anti-pattern: fixed element */
.sidebar {
width: 300px;
}
/* Recommended: flexible element */
.sidebar {
width: 100%;
max-width: 300px;
}
@media (min-width: 768px) {
.sidebar {
width: 30%;
}
}3. Batasi Gambar dan Media
/* Global image constraint */
img {
max-width: 100%;
height: auto;
display: block;
}
/* Video and iframe constraint */
video,
iframe {
max-width: 100%;
height: auto;
}Buat iframe (YouTube/Vimeo dari 40 Produk Media), wadah responsif dengan padding-bottom: 56.25% (16:9) dan iframe absolut di dalamnya, atau aspect-ratio: 16 / 9. Tabel lebar dibungkus overflow-x: auto biar tabelnya yang scroll, bukan halamannya.
Paritas Konten Mobile-Desktop
Google pakai mobile-first indexing: versi mobile situs kamu itu versi utama buat ranking. Kalau konten penting cuma ada di desktop, Google mungkin gak lihat, dan ranking buat query terkait turun. Masalahnya: ranking turun, gap indexing, pengalaman gak konsisten, CTA/detail produk hilang di mobile nurunin konversi, dan sinyal SEO hilang (structured data, link, teks yang gak ada di mobile gak dihitung).
Kesalahan umum: nyembunyiin konten di accordion/tab yang tertutup, ngehapus section di mobile, URL beda buat mobile, lazy-load konten yang gak pernah render, teks di balik “Read more”, ngehapus deskripsi/spesifikasi produk di mobile. John Mueller dari Google ngonfirmasi konten yang tersembunyi di tab/accordion di mobile dikasih bobot lebih rendah dari yang kelihatan, dan riset nunjukin 30–40% situs ecommerce punya perbedaan konten signifikan.
1. Audit
Bandingin mobile dan desktop berdampingan: section yang hilang, semua teks ada, structured data identik, link internal ada, gambar dan media ada. Pakai URL Inspection di Google Search Console buat bandingin HTML yang dirender kedua versi.
2. Bikin Konten Kelihatan
❌ deskripsi tersembunyi di mobile
<div class="product-description mobile-hidden">
<button onclick="toggleDescription()">Read More</button>
<div id="description" style="display: none;">
{{ product.description }}
</div>
</div>✅ kelihatan default, opsional bisa dilipat
<div class="product-description">
<div class="description-content">
{{ product.description }}
</div>
{% if product.description.size > 500 %}
<button class="description-toggle" onclick="toggleDescription()">
Show Less
</button>
{% endif %}
</div>Kalau butuh accordion, implementasi yang aksesibel dan bisa diindeks: konten ada di DOM (bukan display: none sampai diklik), header pakai <button aria-expanded aria-controls>, panel pertama terbuka default. Konten yang di DOM tapi dilipat secara visual tetap diindeks, cuma bobotnya lebih rendah dari yang kelihatan, jadi buat deskripsi produk dan spesifikasi, kelihatan itu pilihan teraman.
Ringkasan
- Mobile = performa theme: CPU 4× lebih lambat (50ms → 200ms+), latensi lebih tinggi (jumlah request lebih berdampak), thermal throttling di sesi panjang, memori terbatas.
- Tes: CPU 4x dan Slow 4G bersamaan + Device Mode, plus ponsel Android kelas menengah nyata (Moto G, Galaxy A).
- JavaScript itu pengali terbesar: kurangi yang jalan saat load,
import()kondisional lewatmatchMedia, hindari long task (script app sinkron,<script>inline yang serialize setting, listener di ratusan elemen). - Konten kelihatan:
sizesmobile-first,section.index > Nbuat lazy (nil tetap eager). - Interstitial intrusif dihukum Google dan ngerusak LCP/aksesibilitas: jangan tutupin konten saat tiba, gate di interaksi bukan timer, ganti dengan signup inline atau sticky banner.
- Responsif performan:
sizessesuai breakpoint (calc(fraksi vw - padding), piksel tetap buat wadah max-width),sizes: 'auto'buat lazy,<picture>buat crop beda, tanpadisplay: noneyang tetap download, tanpa DOM duplikat. - Tap target 48×48px (memenuhi WCAG 24 AA, 44 AAA/Apple, 48 Material), jarak 8px,
font-size: 16pxdi input (anti auto-zoom iOS), label sebagai hit area checkbox. - Tanpa overflow: meta viewport
width=device-width, lebar relatif +max-width,img { max-width: 100%; height: auto }, iframe/tabel dalam wadah responsif. - Paritas konten: mobile-first indexing, konten yang hilang/tersembunyi di mobile turun ranking, audit lewat URL Inspection, deskripsi kelihatan default, accordion yang kontennya tetap di DOM.
Bab selanjutnya kita bahas sumber regresi terbesar yang bukan kode kamu: app dan script pihak ketiga. App yang ngeblokir render, cara audit dan ngehapus script, anti-flicker A/B testing, app performa palsu yang nipu Lighthouse, reverse proxy, dan checkout extension.